commit 5c1f88b2902a19bb6a16af60c05de80670f5cc2b
Author: Octol1ttle <l1ttleofficial@outlook.com>
Date:   Mon Oct 30 12:54:40 2023 +0500

    Initial commit

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..2ca3795
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,40 @@
+# Automatically build the project and run any configured tests for every push
+# and submitted pull request. This can help catch issues that only occur on
+# certain platforms or Java versions, and provides a first line of defence
+# against bad commits.
+
+name: build
+on: [pull_request, push]
+
+jobs:
+  build:
+    strategy:
+      matrix:
+        # Use these Java versions
+        java: [
+          17,    # Current Java LTS & minimum supported by Minecraft
+        ]
+        # and run on both Linux and Windows
+        os: [ubuntu-22.04, windows-2022]
+    runs-on: ${{ matrix.os }}
+    steps:
+      - name: checkout repository
+        uses: actions/checkout@v3
+      - name: validate gradle wrapper
+        uses: gradle/wrapper-validation-action@v1
+      - name: setup jdk ${{ matrix.java }}
+        uses: actions/setup-java@v3
+        with:
+          java-version: ${{ matrix.java }}
+          distribution: 'microsoft'
+      - name: make gradle wrapper executable
+        if: ${{ runner.os != 'Windows' }}
+        run: chmod +x ./gradlew
+      - name: build
+        run: ./gradlew build
+      - name: capture build artifacts
+        if: ${{ runner.os == 'Linux' && matrix.java == '17' }} # Only upload artifacts built from latest java on one OS
+        uses: actions/upload-artifact@v3
+        with:
+          name: Artifacts
+          path: build/libs/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c476faf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,40 @@
+# gradle
+
+.gradle/
+build/
+out/
+classes/
+
+# eclipse
+
+*.launch
+
+# idea
+
+.idea/
+*.iml
+*.ipr
+*.iws
+
+# vscode
+
+.settings/
+.vscode/
+bin/
+.classpath
+.project
+
+# macos
+
+*.DS_Store
+
+# fabric
+
+run/
+
+# java
+
+hs_err_*.log
+replay_*.log
+*.hprof
+*.jfr
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1625c17
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..ac4bfa3
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,88 @@
+plugins {
+	id 'fabric-loom' version '1.4-SNAPSHOT'
+	id 'maven-publish'
+}
+
+version = project.mod_version
+group = project.maven_group
+
+base {
+	archivesName = project.archives_base_name
+}
+
+repositories {
+	// Add repositories to retrieve artifacts from in here.
+	// You should only use this when depending on other mods because
+	// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
+	// See https://docs.gradle.org/current/userguide/declaring_repositories.html
+	// for more information about repositories.
+}
+
+loom {
+    splitEnvironmentSourceSets()
+
+	mods {
+		"knockdowns" {
+			sourceSet sourceSets.main
+			sourceSet sourceSets.client
+		}
+	}
+
+}
+
+dependencies {
+	// To change the versions see the gradle.properties file
+	minecraft "com.mojang:minecraft:${project.minecraft_version}"
+	mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
+	modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
+
+	// Fabric API. This is technically optional, but you probably want it anyway.
+	modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
+
+	include(implementation(annotationProcessor("io.github.llamalad7:mixinextras-fabric:0.2.0")))
+}
+
+processResources {
+	inputs.property "version", project.version
+
+	filesMatching("fabric.mod.json") {
+		expand "version": project.version
+	}
+}
+
+tasks.withType(JavaCompile).configureEach {
+	it.options.release = 17
+}
+
+java {
+	// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
+	// if it is present.
+	// If you remove this line, sources will not be generated.
+	withSourcesJar()
+
+	sourceCompatibility = JavaVersion.VERSION_17
+	targetCompatibility = JavaVersion.VERSION_17
+}
+
+jar {
+	from("LICENSE") {
+		rename { "${it}_${project.base.archivesName.get()}"}
+	}
+}
+
+// configure the maven publication
+publishing {
+	publications {
+		mavenJava(MavenPublication) {
+			from components.java
+		}
+	}
+
+	// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
+	repositories {
+		// Add repositories to publish to here.
+		// Notice: This block does NOT have the same function as the block in the top level.
+		// The repositories here will be used for publishing your artifact, not for
+		// retrieving dependencies.
+	}
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..0ddbc52
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Done to increase the memory available to gradle.
+org.gradle.jvmargs=-Xmx1G
+org.gradle.parallel=true
+
+# Fabric Properties
+# check these on https://fabricmc.net/develop
+minecraft_version=1.20.2
+yarn_mappings=1.20.2+build.4
+loader_version=0.14.24
+
+# Mod Properties
+mod_version=1.0.0
+maven_group=ru.octol1ttle.knockdowns
+archives_base_name=knockdowns
+
+# Dependencies
+fabric_version=0.90.4+1.20.2
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7f93135
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ac72c34
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..0adc8e1
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..93e3f59
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..75c4d72
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,10 @@
+pluginManagement {
+	repositories {
+		maven {
+			name = 'Fabric'
+			url = 'https://maven.fabricmc.net/'
+		}
+		mavenCentral()
+		gradlePluginPortal()
+	}
+}
\ No newline at end of file
diff --git a/src/client/java/ru/octol1ttle/knockdowns/KnockdownsClient.java b/src/client/java/ru/octol1ttle/knockdowns/KnockdownsClient.java
new file mode 100644
index 0000000..889224a
--- /dev/null
+++ b/src/client/java/ru/octol1ttle/knockdowns/KnockdownsClient.java
@@ -0,0 +1,166 @@
+package ru.octol1ttle.knockdowns;
+
+import java.util.UUID;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
+import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
+import net.fabricmc.fabric.api.event.player.UseEntityCallback;
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
+import net.minecraft.SharedConstants;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.network.PacketByteBuf;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.hit.EntityHitResult;
+import net.minecraft.util.hit.HitResult;
+import ru.octol1ttle.knockdowns.api.IKnockableDown;
+import ru.octol1ttle.knockdowns.network.KnockdownsNetworkingConstants;
+
+public class KnockdownsClient implements ClientModInitializer {
+    private static final int REVIVAL_WAIT_TIME = 10 * SharedConstants.TICKS_PER_SECOND;
+    private static IKnockableDown reviving = null;
+    private static int revivalTimer = -1;
+
+    @Override
+    public void onInitializeClient() {
+        registerOnReceiveKnockedDown();
+        registerOnReceiveReviving();
+        registerOnEntityLoad();
+        registerOnEntityUse();
+        registerOnStartWorldTick();
+        registerOnHudRender();
+    }
+
+    private static void registerOnReceiveKnockedDown() {
+        ClientPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, (client, handler, buf, responseSender) -> {
+            UUID uuid = buf.readUuid();
+            boolean knockedDown = buf.readBoolean();
+
+            client.execute(() -> {
+                IKnockableDown knockableDown = (IKnockableDown) handler.getWorld().getPlayerByUuid(uuid);
+                if (knockableDown != null) {
+                    knockableDown.knockdowns$setKnockedDown(knockedDown);
+                }
+            });
+        });
+    }
+
+    private static void registerOnReceiveReviving() {
+        ClientPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, (client, handler, buf, responseSender) -> {
+            UUID uuid = buf.readUuid();
+            boolean beingRevived = buf.readBoolean();
+
+            client.execute(() -> {
+                IKnockableDown knockableDown = (IKnockableDown) handler.getWorld().getPlayerByUuid(uuid);
+                if (knockableDown != null) {
+                    knockableDown.knockdowns$setBeingRevived(beingRevived);
+
+                    if (client.player != null && uuid.equals(client.player.getUuid())) {
+                        if (knockableDown.knockdowns$isBeingRevived()) {
+                            revivalTimer = REVIVAL_WAIT_TIME;
+                        } else {
+                            revivalTimer = -1;
+                        }
+                    }
+
+                    if (reviving != null && knockableDown.knockdowns$getUuid().equals(reviving.knockdowns$getUuid())
+                            && !knockableDown.knockdowns$isBeingRevived()) {
+                        reviving = null;
+                        revivalTimer = -1;
+                    }
+                }
+            });
+        });
+    }
+
+    private static void registerOnEntityLoad() {
+        ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> {
+            if (entity instanceof IKnockableDown) {
+                PacketByteBuf buf = PacketByteBufs.create();
+                buf.writeUuid(entity.getUuid());
+
+                ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_KNOCKED_DOWN, buf);
+                ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_REVIVING, buf);
+            }
+        });
+    }
+
+    private static void registerOnEntityUse() {
+        UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
+            if (!(entity instanceof IKnockableDown knockableDown) || !knockableDown.knockdowns$isKnockedDown()
+                    || knockableDown.knockdowns$isBeingRevived()) {
+                return ActionResult.PASS;
+            }
+            if (((IKnockableDown)player).knockdowns$isKnockedDown()) {
+                return ActionResult.FAIL;
+            }
+
+            knockableDown.knockdowns$setBeingRevived(true);
+
+            PacketByteBuf buf = PacketByteBufs.create();
+            buf.writeUuid(entity.getUuid());
+            buf.writeBoolean(true);
+
+            ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVING, buf);
+
+            reviving = knockableDown;
+            revivalTimer = REVIVAL_WAIT_TIME;
+
+            return ActionResult.SUCCESS;
+        });
+    }
+
+    private static void registerOnStartWorldTick() {
+        ClientTickEvents.START_WORLD_TICK.register(world -> {
+            boolean revived = false;
+            revivalTimer--;
+            if (revivalTimer <= 0) {
+                revivalTimer = -1;
+                revived = true;
+            }
+            
+            if (reviving == null) {
+                return;
+            }
+
+            HitResult crosshairTarget = MinecraftClient.getInstance().crosshairTarget;
+            if (revived || crosshairTarget == null || crosshairTarget.getType() != HitResult.Type.ENTITY
+                    || !((EntityHitResult) crosshairTarget).getEntity().getUuid().equals(reviving.knockdowns$getUuid())) {
+                reviving.knockdowns$setBeingRevived(false);
+
+                PacketByteBuf buf = PacketByteBufs.create();
+                buf.writeUuid(reviving.knockdowns$getUuid());
+                buf.writeBoolean(false);
+
+                ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVING, buf);
+                if (revived) {
+                    reviving.knockdowns$setKnockedDown(false);
+
+                    PacketByteBuf revivedBuf = PacketByteBufs.create();
+                    revivedBuf.writeUuid(reviving.knockdowns$getUuid());
+
+                    ClientPlayNetworking.send(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVED, revivedBuf);
+                }
+
+                reviving = null;
+                revivalTimer = -1;
+            }
+        });
+    }
+
+    private static void registerOnHudRender() {
+        HudRenderCallback.EVENT.register((drawContext, tickDelta) -> {
+            if (revivalTimer == -1) {
+                return;
+            }
+
+            TextRenderer renderer = MinecraftClient.getInstance().textRenderer;
+            String text = String.format("%.1f", revivalTimer / (float) SharedConstants.TICKS_PER_SECOND);
+            int x = (drawContext.getScaledWindowWidth() - renderer.getWidth(text)) / 2;
+
+            drawContext.drawTextWithShadow(renderer, text, x, drawContext.getScaledWindowHeight() / 2 + 15, 0xFFFFFF);
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/client/java/ru/octol1ttle/knockdowns/mixin/client/ClientPlayerEntityMixin.java b/src/client/java/ru/octol1ttle/knockdowns/mixin/client/ClientPlayerEntityMixin.java
new file mode 100644
index 0000000..aed34f1
--- /dev/null
+++ b/src/client/java/ru/octol1ttle/knockdowns/mixin/client/ClientPlayerEntityMixin.java
@@ -0,0 +1,15 @@
+package ru.octol1ttle.knockdowns.mixin.client;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import net.minecraft.client.network.ClientPlayerEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import ru.octol1ttle.knockdowns.api.IKnockableDown;
+
+@Mixin(ClientPlayerEntity.class)
+public abstract class ClientPlayerEntityMixin {
+    @ModifyReturnValue(method = "shouldSlowDown", at = @At("RETURN"))
+    private boolean shouldSlowDown(boolean original) {
+        return original || ((IKnockableDown) this).knockdowns$isKnockedDown();
+    }
+}
diff --git a/src/client/resources/knockdowns.client.mixins.json b/src/client/resources/knockdowns.client.mixins.json
new file mode 100644
index 0000000..ccd33aa
--- /dev/null
+++ b/src/client/resources/knockdowns.client.mixins.json
@@ -0,0 +1,11 @@
+{
+  "required": true,
+  "package": "ru.octol1ttle.knockdowns.mixin.client",
+  "compatibilityLevel": "JAVA_17",
+  "client": [
+    "ClientPlayerEntityMixin"
+  ],
+  "injectors": {
+    "defaultRequire": 1
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/ru/octol1ttle/knockdowns/Knockdowns.java b/src/main/java/ru/octol1ttle/knockdowns/Knockdowns.java
new file mode 100644
index 0000000..39c228f
--- /dev/null
+++ b/src/main/java/ru/octol1ttle/knockdowns/Knockdowns.java
@@ -0,0 +1,215 @@
+package ru.octol1ttle.knockdowns;
+
+import java.util.UUID;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
+import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
+import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseItemCallback;
+import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
+import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
+import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.network.PacketByteBuf;
+import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.Registry;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.sound.SoundCategory;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.text.Text;
+import net.minecraft.text.TranslatableTextContent;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Hand;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.TypedActionResult;
+import ru.octol1ttle.knockdowns.api.IKnockableDown;
+import ru.octol1ttle.knockdowns.network.KnockdownsNetworkingConstants;
+
+public class Knockdowns implements ModInitializer {
+    public static final String MOD_ID = "knockdowns";
+    private static final Identifier KNOCKED_DOWN_ID = new Identifier("knockdowns:knocked_down");
+    private static final SoundEvent KNOCKED_DOWN = SoundEvent.of(KNOCKED_DOWN_ID);
+
+    @Override
+    public void onInitialize() {
+        registerSoundEvents();
+        registerOnDeath();
+        registerOnRequestKnockedDown();
+        registerOnRequestReviving();
+        registerOnReceiveReviving();
+        registerOnReceiveRevived();
+        registerOnPlayerInteractions();
+    }
+
+    private static void registerSoundEvents() {
+        Registry.register(Registries.SOUND_EVENT, KNOCKED_DOWN_ID, KNOCKED_DOWN);
+    }
+
+    private static void registerOnDeath() {
+        ServerLivingEntityEvents.ALLOW_DEATH.register((entity, damageSource, damageAmount) -> {
+            if (!(entity instanceof IKnockableDown knockableDown) || knockableDown.knockdowns$isKnockedDown()) {
+                return true;
+            }
+
+            // TODO: timer
+            entity.setHealth(1.0f);
+            entity.setInvulnerable(true);
+            entity.setGlowing(true);
+            entity.setAir(entity.getMaxAir());
+            entity.setFireTicks(0);
+            entity.setFrozenTicks(0);
+            entity.clearStatusEffects();
+
+            knockableDown.knockdowns$setKnockedDown(true);
+
+            PacketByteBuf buf = PacketByteBufs.create();
+            buf.writeUuid(entity.getUuid());
+            buf.writeBoolean(true);
+
+            ServerPlayerEntity serverPlayer = (ServerPlayerEntity) entity;
+
+            ServerPlayNetworking.send(serverPlayer, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, buf);
+            for (ServerPlayerEntity player : PlayerLookup.tracking(entity)) {
+                ServerPlayNetworking.send(player, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, buf);
+            }
+
+            for (ServerPlayerEntity player : serverPlayer.getServerWorld().getPlayers()) {
+                player.networkHandler.sendPacket(new PlaySoundS2CPacket(Registries.SOUND_EVENT.getEntry(KNOCKED_DOWN), SoundCategory.PLAYERS,
+                        player.getX(), player.getY(), player.getZ(), 1.0f, 1.0f, 0L));
+            }
+
+            TranslatableTextContent content = (TranslatableTextContent) entity.getDamageTracker().getDeathMessage().getContent();
+            Text replaced = Text.translatable(content.getKey().replace("death.", "knockdown."), content.getArgs());
+            MinecraftServer server = serverPlayer.getServer();
+            if (server != null) {
+                server.getPlayerManager().broadcast(replaced, false);
+            }
+
+            return false;
+        });
+    }
+
+    private static void registerOnRequestKnockedDown() {
+        ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_KNOCKED_DOWN, (server, player, handler, buf, responseSender) -> {
+            UUID uuid = buf.readUuid();
+
+            server.execute(() -> {
+                IKnockableDown knockableDown = (IKnockableDown) player.getWorld().getPlayerByUuid(uuid);
+                if (knockableDown == null) {
+                    return;
+                }
+
+                PacketByteBuf responseBuf = PacketByteBufs.create();
+                responseBuf.writeUuid(uuid);
+                responseBuf.writeBoolean(knockableDown.knockdowns$isKnockedDown());
+
+                responseSender.sendPacket(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, responseBuf);
+            });
+        });
+    }
+
+    private static void registerOnRequestReviving() {
+        ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_REQUEST_PLAYER_REVIVING, (server, player, handler, buf, responseSender) -> {
+            UUID uuid = buf.readUuid();
+
+            server.execute(() -> {
+                IKnockableDown knockableDown = (IKnockableDown) player.getWorld().getPlayerByUuid(uuid);
+                if (knockableDown == null) {
+                    return;
+                }
+
+                PacketByteBuf responseBuf = PacketByteBufs.create();
+                responseBuf.writeUuid(uuid);
+                responseBuf.writeBoolean(knockableDown.knockdowns$isBeingRevived());
+
+                responseSender.sendPacket(KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, responseBuf);
+            });
+        });
+    }
+
+    private static void registerOnReceiveReviving() {
+        ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVING, (server, player, handler, buf, responseSender) -> {
+            UUID uuid = buf.readUuid();
+            boolean beingRevived = buf.readBoolean();
+
+            // TODO: revival by multiple players
+            server.execute(() -> {
+                PlayerEntity reviving = player.getWorld().getPlayerByUuid(uuid);
+                IKnockableDown knockableDown = (IKnockableDown) reviving;
+                if (knockableDown == null) {
+                    return;
+                }
+
+                knockableDown.knockdowns$setBeingRevived(beingRevived);
+
+                PacketByteBuf broadcastBuf = PacketByteBufs.create();
+                broadcastBuf.writeUuid(uuid);
+                broadcastBuf.writeBoolean(beingRevived);
+
+                ServerPlayNetworking.send((ServerPlayerEntity) reviving, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, broadcastBuf);
+                for (ServerPlayerEntity entity : PlayerLookup.tracking(reviving)) {
+                    ServerPlayNetworking.send(entity, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_REVIVING, broadcastBuf);
+                }
+            });
+        });
+    }
+
+    private static void registerOnReceiveRevived() {
+        ServerPlayNetworking.registerGlobalReceiver(KnockdownsNetworkingConstants.C2S_SEND_PLAYER_REVIVED, (server, player, handler, buf, responseSender) -> {
+            UUID uuid = buf.readUuid();
+
+            server.execute(() -> {
+                PlayerEntity reviving = player.getWorld().getPlayerByUuid(uuid);
+                IKnockableDown knockableDown = (IKnockableDown) reviving;
+                if (knockableDown == null || !knockableDown.knockdowns$isKnockedDown()) {
+                    return;
+                }
+
+                reviving.setInvulnerable(false);
+                reviving.setGlowing(false);
+                reviving.setHealth(reviving.getHealth() + 5.0f);
+
+                knockableDown.knockdowns$setKnockedDown(false);
+
+                PacketByteBuf sendBuf = PacketByteBufs.create();
+                sendBuf.writeUuid(reviving.getUuid());
+                sendBuf.writeBoolean(false);
+
+                ServerPlayNetworking.send((ServerPlayerEntity) reviving, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, sendBuf);
+                for (ServerPlayerEntity entity : PlayerLookup.tracking(reviving)) {
+                    ServerPlayNetworking.send(entity, KnockdownsNetworkingConstants.S2C_SEND_PLAYER_KNOCKED_DOWN, sendBuf);
+                }
+            });
+        });
+    }
+
+    private static void registerOnPlayerInteractions() {
+        AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
+            if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
+                return ActionResult.FAIL;
+            }
+            return ActionResult.PASS;
+        });
+        AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
+            if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
+                return ActionResult.FAIL;
+            }
+            return ActionResult.PASS;
+        });
+        UseItemCallback.EVENT.register((player, world, hand) -> {
+            if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
+                return TypedActionResult.fail(hand == Hand.MAIN_HAND ? player.getMainHandStack() : player.getOffHandStack());
+            }
+            return TypedActionResult.pass(hand == Hand.MAIN_HAND ? player.getMainHandStack() : player.getOffHandStack());
+        });
+        UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
+            if (player instanceof IKnockableDown && ((IKnockableDown) player).knockdowns$isKnockedDown()) {
+                return ActionResult.FAIL;
+            }
+            return ActionResult.PASS;
+        });
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/octol1ttle/knockdowns/api/IKnockableDown.java b/src/main/java/ru/octol1ttle/knockdowns/api/IKnockableDown.java
new file mode 100644
index 0000000..b06d4a4
--- /dev/null
+++ b/src/main/java/ru/octol1ttle/knockdowns/api/IKnockableDown.java
@@ -0,0 +1,15 @@
+package ru.octol1ttle.knockdowns.api;
+
+import java.util.UUID;
+
+public interface IKnockableDown {
+    boolean knockdowns$isKnockedDown();
+
+    void knockdowns$setKnockedDown(boolean knockedDown);
+
+    boolean knockdowns$isBeingRevived();
+
+    void knockdowns$setBeingRevived(boolean beingRevived);
+
+    UUID knockdowns$getUuid();
+}
diff --git a/src/main/java/ru/octol1ttle/knockdowns/mixin/PlayerEntityMixin.java b/src/main/java/ru/octol1ttle/knockdowns/mixin/PlayerEntityMixin.java
new file mode 100644
index 0000000..28a70a1
--- /dev/null
+++ b/src/main/java/ru/octol1ttle/knockdowns/mixin/PlayerEntityMixin.java
@@ -0,0 +1,60 @@
+package ru.octol1ttle.knockdowns.mixin;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import java.util.UUID;
+import net.minecraft.entity.player.PlayerEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import ru.octol1ttle.knockdowns.api.IKnockableDown;
+
+@Mixin(PlayerEntity.class)
+public abstract class PlayerEntityMixin implements IKnockableDown {
+    @Unique
+    private boolean knockedDown;
+    @Unique
+    private boolean beingRevived;
+    @Unique
+    private int reviveAt;
+
+    @ModifyExpressionValue(method = "updatePose", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;isSwimming()Z"))
+    private boolean isKnockedDown(boolean original) {
+        PlayerEntity player = (PlayerEntity)(Object)this;
+        if (!(player instanceof IKnockableDown knockableDown)) {
+            throw new IllegalStateException();
+        }
+
+        return original || knockableDown.knockdowns$isKnockedDown();
+    }
+
+    @ModifyReturnValue(method = "canFoodHeal", at = @At("RETURN"))
+    private boolean canFoodHeal(boolean original) {
+        return original && !this.knockdowns$isKnockedDown();
+    }
+
+    @Override
+    public boolean knockdowns$isKnockedDown() {
+        return knockedDown;
+    }
+
+    @Override
+    public void knockdowns$setKnockedDown(boolean knockedDown) {
+        this.knockedDown = knockedDown;
+    }
+
+    @Override
+    public boolean knockdowns$isBeingRevived() {
+        return beingRevived;
+    }
+
+    @Override
+    public void knockdowns$setBeingRevived(boolean beingRevived) {
+        this.beingRevived = beingRevived;
+    }
+
+    @Override
+    public UUID knockdowns$getUuid() {
+        return ((PlayerEntity)(Object)this).getUuid();
+    }
+}
diff --git a/src/main/java/ru/octol1ttle/knockdowns/network/KnockdownsNetworkingConstants.java b/src/main/java/ru/octol1ttle/knockdowns/network/KnockdownsNetworkingConstants.java
new file mode 100644
index 0000000..a618797
--- /dev/null
+++ b/src/main/java/ru/octol1ttle/knockdowns/network/KnockdownsNetworkingConstants.java
@@ -0,0 +1,13 @@
+package ru.octol1ttle.knockdowns.network;
+
+import net.minecraft.util.Identifier;
+import ru.octol1ttle.knockdowns.Knockdowns;
+
+public class KnockdownsNetworkingConstants {
+    public static final Identifier S2C_SEND_PLAYER_KNOCKED_DOWN = new Identifier(Knockdowns.MOD_ID, "s2c_send_player_knocked_down");
+    public static final Identifier S2C_SEND_PLAYER_REVIVING = new Identifier(Knockdowns.MOD_ID, "s2c_send_player_reviving");
+    public static final Identifier C2S_REQUEST_PLAYER_KNOCKED_DOWN = new Identifier(Knockdowns.MOD_ID, "c2s_request_player_knocked_down");
+    public static final Identifier C2S_REQUEST_PLAYER_REVIVING = new Identifier(Knockdowns.MOD_ID, "c2s_request_player_reviving");
+    public static final Identifier C2S_SEND_PLAYER_REVIVING = new Identifier(Knockdowns.MOD_ID, "c2s_send_player_reviving");
+    public static final Identifier C2S_SEND_PLAYER_REVIVED = new Identifier(Knockdowns.MOD_ID, "c2s_send_player_revived");
+}
diff --git a/src/main/resources/assets/knockdowns/icon.png b/src/main/resources/assets/knockdowns/icon.png
new file mode 100644
index 0000000..047b91f
Binary files /dev/null and b/src/main/resources/assets/knockdowns/icon.png differ
diff --git a/src/main/resources/assets/knockdowns/lang/en_us.json b/src/main/resources/assets/knockdowns/lang/en_us.json
new file mode 100644
index 0000000..256bd9b
--- /dev/null
+++ b/src/main/resources/assets/knockdowns/lang/en_us.json
@@ -0,0 +1,102 @@
+{
+  "knockdown.attack.anvil": "%1$s was knocked down by a falling anvil",
+  "knockdown.attack.anvil.player": "%1$s was knocked down by a falling anvil while fighting %2$s",
+  "knockdown.attack.arrow": "%1$s was knocked down due to an arrow fired by %2$s",
+  "knockdown.attack.arrow.item": "%1$s was knocked down due to an arrow fired by %2$s using %3$s",
+  "knockdown.attack.badRespawnPoint.link": "Intentional Game Design",
+  "knockdown.attack.badRespawnPoint.message": "%1$s was knocked down by %2$s",
+  "knockdown.attack.cactus": "%1$s was knocked down by a cactus",
+  "knockdown.attack.cactus.player": "%1$s was knocked down into a cactus while trying to escape %2$s",
+  "knockdown.attack.cramming": "%1$s was knocked down by squishing",
+  "knockdown.attack.cramming.player": "%1$s was knocked down due to being squashed by %2$s",
+  "knockdown.attack.dragonBreath": "%1$s was knocked down by dragon's breath",
+  "knockdown.attack.dragonBreath.player": "%1$s was knocked down by dragon's breath by %2$s",
+  "knockdown.attack.drown": "%1$s was knocked down by drowning",
+  "knockdown.attack.drown.player": "%1$s was knocked down by drowning while trying to escape %2$s",
+  "knockdown.attack.dryout": "%1$s was knocked down by dehydration",
+  "knockdown.attack.dryout.player": "%1$s was knocked down by dehydration while trying to escape %2$s",
+  "knockdown.attack.even_more_magic": "%1$s was knocked down by even more magic",
+  "knockdown.attack.explosion": "%1$s was knocked down due to an explosion",
+  "knockdown.attack.explosion.player": "%1$s was knocked down due to an explosion caused by %2$s",
+  "knockdown.attack.explosion.player.item": "%1$s was knocked down due to an explosion caused by %2$s using %3$s",
+  "knockdown.attack.fall": "%1$s was knocked down by a fall",
+  "knockdown.attack.fall.player": "%1$s was knocked down by a fall while trying to escape %2$s",
+  "knockdown.attack.fallingBlock": "%1$s was knocked down by a falling block",
+  "knockdown.attack.fallingBlock.player": "%1$s was knocked down by a falling block while fighting %2$s",
+  "knockdown.attack.fallingStalactite": "%1$s was knocked down by a falling stalactite",
+  "knockdown.attack.fallingStalactite.player": "%1$s was knocked down by a falling stalactite while fighting %2$s",
+  "knockdown.attack.fireball": "%1$s was knocked down due to a fireball fired by %2$s",
+  "knockdown.attack.fireball.item": "%1$s was knocked down due to a fireball fired by %2$s using %3$s",
+  "knockdown.attack.fireworks": "%1$s was knocked down with a bang",
+  "knockdown.attack.fireworks.item": "%1$s was knocked down with a bang due to a firework fired from %3$s by %2$s",
+  "knockdown.attack.fireworks.player": "%1$s was knocked down with a bang while fighting %2$s",
+  "knockdown.attack.flyIntoWall": "%1$s was knocked down by kinetic energy",
+  "knockdown.attack.flyIntoWall.player": "%1$s was knocked down by kinetic energy while trying to escape %2$s",
+  "knockdown.attack.freeze": "%1$s was knocked down by freezing",
+  "knockdown.attack.freeze.player": "%1$s was knocked down by freezing by %2$s",
+  "knockdown.attack.generic": "%1$s was knocked down",
+  "knockdown.attack.generic.player": "%1$s was knocked down because of %2$s",
+  "knockdown.attack.genericKill": "%1$s was knocked down",
+  "knockdown.attack.genericKill.player": "%1$s was knocked down while fighting %2$s",
+  "knockdown.attack.hotFloor": "%1$s was knocked down by the lava floor",
+  "knockdown.attack.hotFloor.player": "%1$s was knocked down by the lava floor due to %2$s",
+  "knockdown.attack.indirectMagic": "%1$s was knocked down by %2$s using magic",
+  "knockdown.attack.indirectMagic.item": "%1$s was knocked down by %2$s using %3$s",
+  "knockdown.attack.inFire": "%1$s was knocked down by fire",
+  "knockdown.attack.inFire.player": "%1$s was knocked down by fire while fighting %2$s",
+  "knockdown.attack.inWall": "%1$s was knocked down by suffocation",
+  "knockdown.attack.inWall.player": "%1$s was knocked down by suffocation while fighting %2$s",
+  "knockdown.attack.lava": "%1$s was knocked down by lava",
+  "knockdown.attack.lava.player": "%1$s was knocked down by lava while trying to escape %2$s",
+  "knockdown.attack.lightningBolt": "%1$s was knocked down by lightning",
+  "knockdown.attack.lightningBolt.player": "%1$s was knocked down by lightning while fighting %2$s",
+  "knockdown.attack.magic": "%1$s was knocked down by magic",
+  "knockdown.attack.magic.player": "%1$s was knocked down by magic while trying to escape %2$s",
+  "knockdown.attack.message_too_long": "Actually, the message was too long to deliver fully. Sorry! Here's a stripped version: %s",
+  "knockdown.attack.mob": "%1$s was knocked down by %2$s",
+  "knockdown.attack.mob.item": "%1$s was knocked down by %2$s using %3$s",
+  "knockdown.attack.onFire": "%1$s was knocked down by fire",
+  "knockdown.attack.onFire.item": "%1$s was knocked down by fire while fighting %2$s wielding %3$s",
+  "knockdown.attack.onFire.player": "%1$s was knocked down by fire while fighting %2$s",
+  "knockdown.attack.outOfWorld": "%1$s was knocked down by the void",
+  "knockdown.attack.outOfWorld.player": "%1$s was knocked down by the void while fighting %2$s",
+  "knockdown.attack.outsideBorder": "%1$s was knocked down by the world border",
+  "knockdown.attack.outsideBorder.player": "%1$s was knocked down by the world border while fighting %2$s",
+  "knockdown.attack.player": "%1$s was knocked down by %2$s",
+  "knockdown.attack.player.item": "%1$s was knocked down by %2$s using %3$s",
+  "knockdown.attack.sonic_boom": "%1$s was knocked down by a sonically-charged shriek",
+  "knockdown.attack.sonic_boom.item": "%1$s was knocked down by a sonically-charged shriek while trying to escape %2$s wielding %3$s",
+  "knockdown.attack.sonic_boom.player": "%1$s was knocked down by a sonically-charged shriek while trying to escape %2$s",
+  "knockdown.attack.stalagmite": "%1$s was knocked down by a stalagmite",
+  "knockdown.attack.stalagmite.player": "%1$s was knocked down by a stalagmite while fighting %2$s",
+  "knockdown.attack.starve": "%1$s was knocked down by starving",
+  "knockdown.attack.starve.player": "%1$s was knocked down by starving while fighting %2$s",
+  "knockdown.attack.sting": "%1$s was knocked down by stings",
+  "knockdown.attack.sting.item": "%1$s was knocked down by stings by %2$s using %3$s",
+  "knockdown.attack.sting.player": "%1$s was knocked down by stings by %2$s",
+  "knockdown.attack.sweetBerryBush": "%1$s was knocked down by a sweet berry bush",
+  "knockdown.attack.sweetBerryBush.player": "%1$s was knocked down by a sweet berry bush while trying to escape %2$s",
+  "knockdown.attack.thorns": "%1$s was knocked down while trying to hurt %2$s",
+  "knockdown.attack.thorns.item": "%1$s was knocked down by %3$s while trying to hurt %2$s",
+  "knockdown.attack.thrown": "%1$s was knocked down due to being thrown by %2$s",
+  "knockdown.attack.thrown.item": "%1$s was knocked down due to being thrown by %2$s using %3$s",
+  "knockdown.attack.trident": "%1$s was knocked down due to being impaled by %2$s",
+  "knockdown.attack.trident.item": "%1$s was knocked down due to being impaled by %2$s with %3$s",
+  "knockdown.attack.wither": "%1$s was knocked down by withering",
+  "knockdown.attack.wither.player": "%1$s was knocked down by withering while fighting %2$s",
+  "knockdown.attack.witherSkull": "%1$s was knocked down by a skull from %2$s",
+  "knockdown.attack.witherSkull.item": "%1$s was knocked down by a skull from %2$s using %3$s",
+  "knockdown.fell.accident.generic": "%1$s was knocked down by a fall",
+  "knockdown.fell.accident.ladder": "%1$s was knocked down by a fall off a ladder",
+  "knockdown.fell.accident.other_climbable": "%1$s was knocked down by a fall while climbing",
+  "knockdown.fell.accident.scaffolding": "%1$s was knocked down by a fall off scaffolding",
+  "knockdown.fell.accident.twisting_vines": "%1$s was knocked down by a fall off some twisting vines",
+  "knockdown.fell.accident.vines": "%1$s was knocked down by a fall off some vines",
+  "knockdown.fell.accident.weeping_vines": "%1$s was knocked down by a fall off some weeping vines",
+  "knockdown.fell.assist": "%1$s was doomed to get knocked down by %2$s",
+  "knockdown.fell.assist.item": "%1$s was doomed to get knocked down by %2$s using %3$s",
+  "knockdown.fell.finish": "%1$s fell too far and was knocked down by %2$s",
+  "knockdown.fell.finish.item": "%1$s fell too far and was knocked down by %2$s using %3$s",
+  "knockdown.fell.killer": "%1$s was doomed to get knocked down by a fall",
+  "subtitles.knockdowns.knocked_down": "Player knocked down"
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/knockdowns/lang/ru_ru.json b/src/main/resources/assets/knockdowns/lang/ru_ru.json
new file mode 100644
index 0000000..5cdd965
--- /dev/null
+++ b/src/main/resources/assets/knockdowns/lang/ru_ru.json
@@ -0,0 +1,102 @@
+{
+  "knockdown.attack.anvil": "%1$s был тяжело ранен упавшей наковальней",
+  "knockdown.attack.anvil.player": "%1$s был тяжело ранен упавшей наковальней, пока сражался с %2$s",
+  "knockdown.attack.arrow": "%1$s тяжело ранен стрелой %2$s",
+  "knockdown.attack.arrow.item": "%1$s тяжело ранен стрелой %2$s из %3$s",
+  "knockdown.attack.badRespawnPoint.link": "жестокими правилами игры",
+  "knockdown.attack.badRespawnPoint.message": "%1$s был тяжело ранен %2$s",
+  "knockdown.attack.cactus": "%1$s был тяжело ранен кактусом",
+  "knockdown.attack.cactus.player": "%1$s был тяжело ранен кактусом, спасаясь от %2$s",
+  "knockdown.attack.cramming": "%1$s был тяжело ранен расплющиванием",
+  "knockdown.attack.cramming.player": "%1$s был тяжело ранен расплющиванием %2$s",
+  "knockdown.attack.dragonBreath": "%1$s был тяжело ранен в драконьем дыхании",
+  "knockdown.attack.dragonBreath.player": "%1$s был тяжело ранен в драконьем дыхании из-за %2$s",
+  "knockdown.attack.drown": "%1$s был тяжело ранен утоплением",
+  "knockdown.attack.drown.player": "%1$s был тяжело ранен утоплением, спасаясь от %2$s",
+  "knockdown.attack.dryout": "%1$s был тяжело ранен обезвоживанием",
+  "knockdown.attack.dryout.player": "%1$s был тяжело ранен обезвоживанием, спасаясь от %2$s",
+  "knockdown.attack.even_more_magic": "%1$s был тяжело ранен неизведанной магией",
+  "knockdown.attack.explosion": "%1$s был тяжело ранен взрывом",
+  "knockdown.attack.explosion.player": "%1$s был тяжело ранен взрывом %2$s",
+  "knockdown.attack.explosion.player.item": "%1$s был тяжело ранен взрывом %2$s с помощью %3$s",
+  "knockdown.attack.fall": "%1$s был тяжело ранен падением",
+  "knockdown.attack.fall.player": "%1$s был тяжело ранен падением, спасаясь от %2$s",
+  "knockdown.attack.fallingBlock": "%1$s был тяжело ранен упавшим блоком",
+  "knockdown.attack.fallingBlock.player": "%1$s был тяжело ранен упавшим блоком, пока боролся с %2$s",
+  "knockdown.attack.fallingStalactite": "%1$s был тяжело ранен обрушившимся сталактитом",
+  "knockdown.attack.fallingStalactite.player": "%1$s был тяжело ранен обрушившимся сталактитом, пока боролся с %2$s",
+  "knockdown.attack.fireball": "%1$s был тяжело ранен файерболом %2$s",
+  "knockdown.attack.fireball.item": "%1$s был тяжело ранен файерболом %2$s с помощью %3$s",
+  "knockdown.attack.fireworks": "%1$s был тяжело ранен взрывом фейерверка",
+  "knockdown.attack.fireworks.item": "%1$s был тяжело ранен взрывом фейерверка %2$s, выпущенного из %3$s",
+  "knockdown.attack.fireworks.player": "%1$s был тяжело ранен взрывом фейерверка, пока боролся с %2$s",
+  "knockdown.attack.flyIntoWall": "%1$s был тяжело ранен кинетической энергией",
+  "knockdown.attack.flyIntoWall.player": "%1$s был тяжело ранен кинетической энергией, спасаясь от %2$s",
+  "knockdown.attack.freeze": "%1$s был тяжело ранен замерзанием",
+  "knockdown.attack.freeze.player": "%1$s был тяжело ранен замерзанием благодаря %2$s",
+  "knockdown.attack.generic": "%1$s тяжело ранен",
+  "knockdown.attack.generic.player": "%1$s тяжело ранен из-за %2$s",
+  "knockdown.attack.genericKill": "%1$s тяжело ранен",
+  "knockdown.attack.genericKill.player": "%1$s был тяжело ранен, пока боролся с %2$s",
+  "knockdown.attack.hotFloor": "%1$s был тяжело ранен, обнаружив, что пол — это лава",
+  "knockdown.attack.hotFloor.player": "%1$s был тяжело ранен, так как зашёл в опасную зону из-за %2$s",
+  "knockdown.attack.inFire": "%1$s был тяжело ранен в огне",
+  "knockdown.attack.inFire.player": "%1$s был тяжело ранен в огне, пока боролся с %2$s",
+  "knockdown.attack.inWall": "%1$s был тяжело ранен погребением заживо",
+  "knockdown.attack.inWall.player": "%1$s был тяжело ранен погребением заживо, пока боролся с %2$s",
+  "knockdown.attack.indirectMagic": "%1$s был тяжело ранен %2$s с помощью магии",
+  "knockdown.attack.indirectMagic.item": "%1$s был тяжело ранен %2$s с помощью %3$s",
+  "knockdown.attack.lava": "%1$s был тяжело ранен лавой",
+  "knockdown.attack.lava.player": "%1$s был тяжело ранен лавой, убегая от %2$s",
+  "knockdown.attack.lightningBolt": "%1$s был тяжело ранен молнией",
+  "knockdown.attack.lightningBolt.player": "%1$s был тяжело ранен молнией, пока сражался с %2$s",
+  "knockdown.attack.magic": "%1$s был тяжело ранен магией",
+  "knockdown.attack.magic.player": "%1$s был тяжело ранен магией, убегая от %2$s",
+  "knockdown.attack.message_too_long": "Сообщение слишком длинное для доставки. Извините! Вот сокращённая версия: %s",
+  "knockdown.attack.mob": "%1$s был тяжело ранен %2$s",
+  "knockdown.attack.mob.item": "%1$s был тяжело ранен %2$s с помощью %3$s",
+  "knockdown.attack.onFire": "%1$s был тяжело ранен огнём",
+  "knockdown.attack.onFire.item": "%1$s был тяжело ранен огнём, пока боролся с %2$s, с помощью %3$s",
+  "knockdown.attack.onFire.player": "%1$s был тяжело ранен огнём, пока боролся с %2$s",
+  "knockdown.attack.outOfWorld": "%1$s был тяжело ранен пустотой",
+  "knockdown.attack.outOfWorld.player": "%1$s был тяжело ранен пустотой, благодаря %2$s",
+  "knockdown.attack.outsideBorder": "%1$s был тяжело ранен пределами этого мира",
+  "knockdown.attack.outsideBorder.player": "%1$s был тяжело ранен пределами этого мира, пока боролся с %2$s",
+  "knockdown.attack.player": "%1$s был тяжело ранен %2$s",
+  "knockdown.attack.player.item": "%1$s был тяжело ранен %2$s с помощью %3$s",
+  "knockdown.attack.sonic_boom": "%1$s был тяжело ранен звуковым зарядом",
+  "knockdown.attack.sonic_boom.item": "%1$s был тяжело ранен звуковым ударом, спасаясь от %2$s c %3$s",
+  "knockdown.attack.sonic_boom.player": "%1$s был тяжело ранен звуковым ударом, спасаясь от %2$s",
+  "knockdown.attack.stalagmite": "%1$s был тяжело ранен сталагмитом",
+  "knockdown.attack.stalagmite.player": "%1$s был тяжело ранен сталагмитом, пока боролся с %2$s",
+  "knockdown.attack.starve": "%1$s был тяжело ранен голодом",
+  "knockdown.attack.starve.player": "%1$s был тяжело ранен голодом, пока боролся с %2$s",
+  "knockdown.attack.sting": "%1$s был тяжело ранен изжалением",
+  "knockdown.attack.sting.item": "%1$s был тяжело ранен изжалением %2$s с помощью %3$s",
+  "knockdown.attack.sting.player": "%1$s был тяжело ранен изжалением %2$s",
+  "knockdown.attack.sweetBerryBush": "%1$s был тяжело ранен исколением в кустах сладких ягод",
+  "knockdown.attack.sweetBerryBush.player": "%1$s был тяжело ранен исколением в кустах сладких ягод, спасаясь от %2$s",
+  "knockdown.attack.thorns": "%1$s был тяжело ранен, пытаясь навредить %2$s",
+  "knockdown.attack.thorns.item": "%1$s был тяжело ранен %3$s, пытаясь навредить %2$s",
+  "knockdown.attack.thrown": "%1$s был тяжело ранен %2$s",
+  "knockdown.attack.thrown.item": "%1$s был тяжело ранен %2$s с помощью %3$s",
+  "knockdown.attack.trident": "%1$s был тяжело ранен пронзанием %2$s",
+  "knockdown.attack.trident.item": "%1$s был тяжело ранен пронзанием %2$s с помощью %3$s",
+  "knockdown.attack.wither": "%1$s был тяжело ранен иссушением",
+  "knockdown.attack.wither.player": "%1$s был тяжело ранен иссушением, пока боролся с %2$s",
+  "knockdown.attack.witherSkull": "%1$s был тяжело ранен поражением черепом из %2$s",
+  "knockdown.attack.witherSkull.item": "%1$s был тяжело ранен поражением черепом из %2$s с помощью %3$s",
+  "knockdown.fell.accident.generic": "%1$s был тяжело ранен падением",
+  "knockdown.fell.accident.ladder": "%1$s был тяжело ранен падением с лестницы",
+  "knockdown.fell.accident.other_climbable": "%1$s был тяжело ранен падением",
+  "knockdown.fell.accident.scaffolding": "%1$s был тяжело ранен падением с подмосток",
+  "knockdown.fell.accident.twisting_vines": "%1$s был тяжело ранен падением с вьющейся лозы",
+  "knockdown.fell.accident.vines": "%1$s был тяжело ранен падением с лианы",
+  "knockdown.fell.accident.weeping_vines": "%1$s был тяжело ранен падением с плакучей лозы",
+  "knockdown.fell.assist": "%1$s был тяжело ранен падением благодаря %2$s",
+  "knockdown.fell.assist.item": "%1$s был тяжело ранен падением благодаря %2$s с помощью %3$s",
+  "knockdown.fell.finish": "%1$s упал с высоты и был тяжело ранен %2$s",
+  "knockdown.fell.finish.item": "%1$s упал с высоты и был тяжело ранен %2$s с помощью %3$s",
+  "knockdown.fell.killer": "%1$s был тяжело ранен падением",
+  "subtitles.knockdowns.knocked_down": "Игрок тяжело ранен"
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/knockdowns/sounds.json b/src/main/resources/assets/knockdowns/sounds.json
new file mode 100644
index 0000000..52d77b8
--- /dev/null
+++ b/src/main/resources/assets/knockdowns/sounds.json
@@ -0,0 +1,10 @@
+{
+	"knocked_down": {
+		"subtitle": "subtitles.knockdowns.knocked_down",
+		"sounds": [
+			{
+				"name": "knockdowns:knocked_down"
+			}
+		]
+	}
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg b/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg
new file mode 100644
index 0000000..a01bae8
Binary files /dev/null and b/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg differ
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..c4330c1
--- /dev/null
+++ b/src/main/resources/fabric.mod.json
@@ -0,0 +1,38 @@
+{
+	"schemaVersion": 1,
+	"id": "knockdowns",
+	"version": "${version}",
+	"name": "Knockdowns",
+	"description": "man hardcode is difficult",
+	"authors": [
+		"Octol1ttle"
+	],
+	"contact": {
+		"homepage": "https://fabricmc.net/",
+		"sources": "https://github.com/FabricMC/fabric-example-mod"
+	},
+	"license": "ARR",
+	"icon": "assets/knockdowns/icon.png",
+	"environment": "*",
+	"entrypoints": {
+		"main": [
+			"ru.octol1ttle.knockdowns.Knockdowns"
+		],
+		"client": [
+			"ru.octol1ttle.knockdowns.KnockdownsClient"
+		]
+	},
+	"mixins": [
+		"knockdowns.mixins.json",
+		{
+			"config": "knockdowns.client.mixins.json",
+			"environment": "client"
+		}
+	],
+	"depends": {
+		"fabricloader": ">=0.14.23",
+		"minecraft": "~1.20.2",
+		"java": ">=17",
+		"fabric-api": "*"
+	}
+}
\ No newline at end of file
diff --git a/src/main/resources/knockdowns.mixins.json b/src/main/resources/knockdowns.mixins.json
new file mode 100644
index 0000000..81139b2
--- /dev/null
+++ b/src/main/resources/knockdowns.mixins.json
@@ -0,0 +1,11 @@
+{
+	"required": true,
+	"package": "ru.octol1ttle.knockdowns.mixin",
+	"compatibilityLevel": "JAVA_17",
+	"mixins": [
+		"PlayerEntityMixin"
+	],
+	"injectors": {
+		"defaultRequire": 1
+	}
+}
\ No newline at end of file