diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 2ca3795..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,40 +0,0 @@ -# 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 index c476faf..ccb0c56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,19 @@ -# gradle - -.gradle/ build/ -out/ -classes/ - -# eclipse - -*.launch - -# idea - -.idea/ -*.iml *.ipr +run/ *.iws - -# vscode - -.settings/ -.vscode/ +out/ +*.iml +.gradle/ +output/ bin/ +libs/ + .classpath .project - -# macos - -*.DS_Store - -# fabric - -run/ - -# java - -hs_err_*.log -replay_*.log -*.hprof -*.jfr +.idea/ +classes/ +.metadata +.vscode +.settings +*.launch \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1625c17..0000000 --- a/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -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 index ac4bfa3..df015f7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,88 +1,52 @@ plugins { - id 'fabric-loom' version '1.4-SNAPSHOT' - id 'maven-publish' + id "architectury-plugin" version "3.4-SNAPSHOT" + id "dev.architectury.loom" version "1.4-SNAPSHOT" apply false } -version = project.mod_version -group = project.maven_group - -base { - archivesName = project.archives_base_name +architectury { + minecraft = rootProject.minecraft_version } -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. +subprojects { + apply plugin: "dev.architectury.loom" + + loom { + silentMojangMappingsLicense() + } + + dependencies { + minecraft "com.mojang:minecraft:${rootProject.minecraft_version}" + // The following line declares the yarn mappings you may select this one as well. + mappings "net.fabricmc:yarn:1.20.1+build.10:v2" + } } -loom { - splitEnvironmentSourceSets() +allprojects { + apply plugin: "java" + apply plugin: "architectury-plugin" + apply plugin: "maven-publish" - mods { - "knockdowns" { - sourceSet sourceSets.main - sourceSet sourceSets.client - } - } + base { + archivesName = rootProject.archives_base_name + } + version = rootProject.mod_version + group = rootProject.maven_group + + 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. + } + + tasks.withType(JavaCompile).configureEach { + options.encoding = "UTF-8" + options.release = 17 + } + + java { + withSourcesJar() + } } - -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/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..a545d0d --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,31 @@ +architectury { + common(rootProject.enabled_platforms.split(",")) +} + +loom { + accessWidenerPath = file("src/main/resources/knockdowns.accesswidener") +} + +dependencies { + // We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies + // Do NOT use other classes from fabric loader + modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + // Remove the next line if you don't want to depend on the API + modApi "dev.architectury:architectury:${rootProject.architectury_version}" + + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.3")) +} + +publishing { + publications { + mavenCommon(MavenPublication) { + artifactId = rootProject.archives_base_name + 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. + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsClient.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsClient.java new file mode 100644 index 0000000..1b4272f --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsClient.java @@ -0,0 +1,63 @@ +package ru.octol1ttle.knockdowns.common; + +import dev.architectury.event.EventResult; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; +import ru.octol1ttle.knockdowns.common.events.KnockdownsClientEvents; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.RequestStartRevivingC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.StopRevivingC2SPacket; +import ru.octol1ttle.knockdowns.common.registries.KnockdownsSoundEvents; +import ru.octol1ttle.knockdowns.common.registries.KnockedDownSoundInstance; + +public class KnockdownsClient { + @Nullable + public static Entity reviving; + + public static void init() { + KnockdownsClientEvents.registerCallbacks(); + } + + public static void playKnockedDownSound(Vec3d pos) { + MinecraftClient.getInstance().getSoundManager().play( + new KnockedDownSoundInstance(KnockdownsSoundEvents.KNOCKED_DOWN.get(), pos) + ); + } + + public static EventResult onEntityUse(PlayerEntity player, Entity entity) { + if (KnockdownsUtils.isKnockedOrReviving(player) || !(entity instanceof IKnockableDown knockable) || !knockable.is_KnockedDown()) { + return EventResult.pass(); + } + + KnockdownsNetwork.sendToServer(new RequestStartRevivingC2SPacket(entity.getUuid())); + reviving = entity; + + return EventResult.interruptTrue(); + } + + public static void onPlayerTick(PlayerEntity player) { + MinecraftClient client = MinecraftClient.getInstance(); + if (!player.equals(client.player) || reviving == null) { + return; + } + + boolean playerKnocked = ((IKnockableDown) player).is_KnockedDown(); + boolean revivingTargeted = client.crosshairTarget != null && client.crosshairTarget.getType() == HitResult.Type.ENTITY + && ((EntityHitResult) client.crosshairTarget).getEntity().equals(reviving); + + if (!(reviving instanceof IKnockableDown knockable)) { + return; + } + + if (!knockable.is_KnockedDown() || playerKnocked || !revivingTargeted) { + KnockdownsNetwork.sendToServer(new StopRevivingC2SPacket(reviving.getUuid())); + reviving = null; + } + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommon.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommon.java new file mode 100644 index 0000000..d36f5d7 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsCommon.java @@ -0,0 +1,17 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraft.SharedConstants; +import ru.octol1ttle.knockdowns.common.events.KnockdownsEvents; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.registries.KnockdownsSoundEvents; + +public class KnockdownsCommon { + public static final String MOD_ID = "knockdowns"; + public static final int REVIVE_WAIT_TIME = 10 * SharedConstants.TICKS_PER_SECOND; + + public static void init() { + KnockdownsSoundEvents.register(); + KnockdownsNetwork.registerPackets(); + KnockdownsEvents.registerCallbacks(); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java new file mode 100644 index 0000000..8715bd8 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/KnockdownsUtils.java @@ -0,0 +1,31 @@ +package ru.octol1ttle.knockdowns.common; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +public class KnockdownsUtils { + public static boolean isKnockedOrReviving(PlayerEntity player) { + return player instanceof IKnockableDown knockable && (knockable.is_KnockedDown() || knockable.is_Reviving()); + } + + public static boolean allTeammatesKnocked(MinecraftServer server, PlayerEntity player) { + for (PlayerEntity teammate : server.getPlayerManager().getPlayerList()) { + if (teammate.equals(player)) { + continue; + } + IKnockableDown knockable = (IKnockableDown) teammate; + if (!knockable.is_KnockedDown() && !player.isDead()) { + return false; + } + } + return true; + } + + public static void hurtTenacity(PlayerEntity player, float damage) { + player.setInvulnerable(false); + //DamageSource recent = player.getRecentDamageSource(); + player.damage(/*Objects.requireNonNullElse(recent, */player.getDamageSources().generic()/*)*/, damage); + player.velocityModified = false; + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/api/IKnockableDown.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/api/IKnockableDown.java new file mode 100644 index 0000000..17c4308 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/api/IKnockableDown.java @@ -0,0 +1,23 @@ +package ru.octol1ttle.knockdowns.common.api; + +public interface IKnockableDown { + boolean is_KnockedDown(); + + void set_KnockedDown(boolean knockedDown); + + int get_ReviverCount(); + + void set_ReviverCount(int reviverCount); + + boolean is_Reviving(); + + void set_Reviving(boolean reviving); + + int get_ReviveTimer(); + + void set_ReviveTimer(int reviveTimer); + + int get_KnockedAge(); + + void set_KnockedAge(int knockedAge); +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/events/KnockdownsClientEvents.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/events/KnockdownsClientEvents.java new file mode 100644 index 0000000..47ec31d --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/events/KnockdownsClientEvents.java @@ -0,0 +1,44 @@ +package ru.octol1ttle.knockdowns.common.events; + +import dev.architectury.event.events.client.ClientGuiEvent; +import net.minecraft.SharedConstants; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.util.Formatting; +import ru.octol1ttle.knockdowns.common.KnockdownsClient; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +public class KnockdownsClientEvents { + public static void registerCallbacks() { + registerOnHudRender(); + } + + private static void registerOnHudRender() { + ClientGuiEvent.RENDER_HUD.register((drawContext, tickDelta) -> { + IKnockableDown reviving = (IKnockableDown) KnockdownsClient.reviving; + MinecraftClient client = MinecraftClient.getInstance(); + if (reviving == null) { + reviving = (IKnockableDown) client.player; + if (reviving == null || reviving.get_ReviverCount() == 0) { + return; + } + } + + TextRenderer renderer = client.textRenderer; + + String timerText = String.format("%.1f", reviving.get_ReviveTimer() / (float) SharedConstants.TICKS_PER_SECOND); + int timerX = (drawContext.getScaledWindowWidth() - renderer.getWidth(timerText)) / 2; + + int reviverCount = reviving.get_ReviverCount(); + Integer color = reviverCount > 1 ? Formatting.GREEN.getColorValue() : Formatting.WHITE.getColorValue(); + + String reviverCountText = "x" + reviverCount; + int reviveCountX = (drawContext.getScaledWindowWidth() - renderer.getWidth(reviverCountText)) / 2; + + if (color != null) { + drawContext.drawTextWithShadow(renderer, timerText, timerX, drawContext.getScaledWindowHeight() / 2 + 5, color); + drawContext.drawTextWithShadow(renderer, reviverCountText, reviveCountX, drawContext.getScaledWindowHeight() / 2 + 14, color); + } + }); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/events/KnockdownsEvents.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/events/KnockdownsEvents.java new file mode 100644 index 0000000..8f46b96 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/events/KnockdownsEvents.java @@ -0,0 +1,155 @@ +package ru.octol1ttle.knockdowns.common.events; + +import dev.architectury.event.CompoundEventResult; +import dev.architectury.event.EventResult; +import dev.architectury.event.events.common.EntityEvent; +import dev.architectury.event.events.common.InteractionEvent; +import dev.architectury.event.events.common.PlayerEvent; +import dev.architectury.event.events.common.TickEvent; +import net.minecraft.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; +import net.minecraft.util.Hand; +import net.minecraft.util.math.MathHelper; +import ru.octol1ttle.knockdowns.common.KnockdownsClient; +import ru.octol1ttle.knockdowns.common.KnockdownsCommon; +import ru.octol1ttle.knockdowns.common.KnockdownsUtils; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; +import ru.octol1ttle.knockdowns.common.network.KnockdownsNetwork; +import ru.octol1ttle.knockdowns.common.network.packets.PlayKnockedDownSoundS2CPacket; + +public class KnockdownsEvents { + private static final float KNOCKED_INVULNERABILITY_TICKS = 3.0f * SharedConstants.TICKS_PER_SECOND; + private static final float KNOCKED_HURT_PERIOD = 1.2f; + private static final float KNOCKED_TENACITY = 60.0f; + + public static void registerCallbacks() { + registerOnLivingDeath(); + registerOnPlayerTick(); + registerOnPlayerInteractions(); + registerOnEntityUse(); + } + + private static void registerOnLivingDeath() { + EntityEvent.LIVING_DEATH.register((entity, source) -> { + MinecraftServer server = entity.getServer(); + if (server == null || !(entity instanceof IKnockableDown knockable)) { + return EventResult.pass(); + } + + ServerPlayerEntity player = (ServerPlayerEntity) entity; + + if (KnockdownsUtils.allTeammatesKnocked(server, player)) { + return EventResult.pass(); + } + + if (knockable.is_KnockedDown()) { + knockable.set_KnockedDown(false); + knockable.set_ReviverCount(0); + knockable.set_ReviveTimer(KnockdownsCommon.REVIVE_WAIT_TIME); + knockable.set_KnockedAge(0); + + return EventResult.pass(); + } + + entity.clearStatusEffects(); + entity.setInvulnerable(true); + entity.setGlowing(true); + entity.setHealth(entity.getMaxHealth()); + entity.extinguish(); + entity.setAir(entity.getMaxAir()); + entity.setFrozenTicks(0); + player.stopFallFlying(); + + knockable.set_KnockedDown(true); + knockable.set_ReviveTimer(KnockdownsCommon.REVIVE_WAIT_TIME); + knockable.set_KnockedAge(0); + + KnockdownsNetwork.sendToWorld(player.getServerWorld(), new PlayKnockedDownSoundS2CPacket(player.getX(), player.getY(), player.getZ())); + + Text deathMessage = entity.getDamageTracker().getDeathMessage(); + TranslatableTextContent content = (TranslatableTextContent) deathMessage.getContent(); + Text replaced = Text.translatableWithFallback(content.getKey().replace("death.", "knockdown."), deathMessage.getString(), content.getArgs()); + + server.getPlayerManager().broadcast(replaced, false); + + return EventResult.interruptFalse(); + }); + } + + private static void registerOnPlayerTick() { + TickEvent.PLAYER_POST.register(player -> { + MinecraftServer server = player.getServer(); + if (server == null) { + KnockdownsClient.onPlayerTick(player); + return; + } + if (!(player instanceof IKnockableDown knockable) || !knockable.is_KnockedDown()) { + return; + } + + if (KnockdownsUtils.allTeammatesKnocked(server, player)) { + KnockdownsUtils.hurtTenacity(player, player.getMaxHealth()); + return; + } + + if (knockable.get_ReviverCount() > 0) { + knockable.set_ReviveTimer(knockable.get_ReviveTimer() - knockable.get_ReviverCount()); + + if (knockable.get_ReviveTimer() <= 0) { + knockable.set_KnockedDown(false); + knockable.set_ReviverCount(0); + knockable.set_ReviveTimer(KnockdownsCommon.REVIVE_WAIT_TIME); + knockable.set_KnockedAge(0); + + player.setInvulnerable(false); + player.setGlowing(false); + player.setHealth(6.0f); + } + return; + } + knockable.set_ReviveTimer(Math.min(KnockdownsCommon.REVIVE_WAIT_TIME, knockable.get_ReviveTimer() + 1)); + + knockable.set_KnockedAge(knockable.get_KnockedAge() + 1); + + int period = MathHelper.floor(KNOCKED_HURT_PERIOD * SharedConstants.TICKS_PER_SECOND); + if (knockable.get_KnockedAge() >= KNOCKED_INVULNERABILITY_TICKS && knockable.get_KnockedAge() % period == 0) { + KnockdownsUtils.hurtTenacity(player, player.getMaxHealth() / (KNOCKED_TENACITY / KNOCKED_HURT_PERIOD)); + } + }); + } + + private static void registerOnPlayerInteractions() { + InteractionEvent.LEFT_CLICK_BLOCK.register((player, hand, pos, direction) -> { + if (KnockdownsUtils.isKnockedOrReviving(player)) { + return EventResult.interruptFalse(); + } + return EventResult.pass(); + }); + PlayerEvent.ATTACK_ENTITY.register((player, world, hand, entity, hitResult) -> { + if (KnockdownsUtils.isKnockedOrReviving(player)) { + return EventResult.interruptFalse(); + } + return EventResult.pass(); + }); + InteractionEvent.RIGHT_CLICK_ITEM.register((player, hand) -> { + if (KnockdownsUtils.isKnockedOrReviving(player)) { + return CompoundEventResult.interruptFalse(hand == Hand.MAIN_HAND ? player.getMainHandStack() : player.getOffHandStack()); + } + return CompoundEventResult.pass(); + }); + InteractionEvent.RIGHT_CLICK_BLOCK.register((player, hand, pos, direction) -> { + if (KnockdownsUtils.isKnockedOrReviving(player)) { + return EventResult.interruptFalse(); + } + return EventResult.pass(); + }); + } + + private static void registerOnEntityUse() { + InteractionEvent.INTERACT_ENTITY.register((player, entity, hand) + -> player.getWorld().isClient() ? KnockdownsClient.onEntityUse(player, entity) : EventResult.pass()); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/LivingEntityMixin.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/LivingEntityMixin.java new file mode 100644 index 0000000..369a8b4 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/LivingEntityMixin.java @@ -0,0 +1,15 @@ +package ru.octol1ttle.knockdowns.common.mixin; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +@Mixin(LivingEntity.class) +public abstract class LivingEntityMixin { + @ModifyReturnValue(method = "canTarget(Lnet/minecraft/entity/LivingEntity;)Z", at = @At("RETURN")) + private boolean dontTargetKnockedPlayers(boolean original, LivingEntity target) { + return original && !(target instanceof IKnockableDown knockable && knockable.is_KnockedDown()); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/MobEntityMixin.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/MobEntityMixin.java new file mode 100644 index 0000000..17540a0 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/MobEntityMixin.java @@ -0,0 +1,19 @@ +package ru.octol1ttle.knockdowns.common.mixin; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.MobEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +@Mixin(MobEntity.class) +public abstract class MobEntityMixin { + @Inject(method = "setTarget", at = @At("HEAD"), cancellable = true) + private void setTarget(LivingEntity target, CallbackInfo ci) { + if (target instanceof IKnockableDown knockable && knockable.is_KnockedDown()) { + ci.cancel(); + } + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/PlayerEntityMixin.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/PlayerEntityMixin.java new file mode 100644 index 0000000..ce8f9f1 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/PlayerEntityMixin.java @@ -0,0 +1,129 @@ +package ru.octol1ttle.knockdowns.common.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import ru.octol1ttle.knockdowns.common.KnockdownsCommon; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +@SuppressWarnings("WrongEntityDataParameterClass") +@Mixin(PlayerEntity.class) +public abstract class PlayerEntityMixin extends Entity implements IKnockableDown { + @Unique + private static final TrackedData KNOCKED_DOWN = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + @Unique + private static final TrackedData IS_REVIVING = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + @Unique + private static final TrackedData REVIVER_COUNT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER); + @Unique + private static final TrackedData REVIVE_TIMER = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER); + @Unique + public int knocked_age; + + private PlayerEntityMixin(EntityType type, World world) { + super(type, world); + throw new AssertionError(); + } + + @ModifyExpressionValue(method = "updatePose", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;isSwimming()Z")) + private boolean enterSwimmingIfKnockedDown(boolean original) { + return original || this.is_KnockedDown(); + } + + @ModifyReturnValue(method = "canFoodHeal", at = @At("RETURN")) + private boolean dontHealIfKnockedDown(boolean original) { + return original && !this.is_KnockedDown(); + } + + @Inject(method = "checkFallFlying", at = @At("HEAD"), cancellable = true) + private void dontOpenElytraIfKnockedDown(CallbackInfoReturnable cir) { + if (this.is_KnockedDown()) { + cir.setReturnValue(false); + } + } + + @Inject(method = "initDataTracker", at = @At("TAIL")) + private void initCustomDataTracker(CallbackInfo ci) { + this.dataTracker.startTracking(KNOCKED_DOWN, false); + this.dataTracker.startTracking(IS_REVIVING, false); + this.dataTracker.startTracking(REVIVER_COUNT, 0); + this.dataTracker.startTracking(REVIVE_TIMER, KnockdownsCommon.REVIVE_WAIT_TIME); + } + + @Inject(method = "readCustomDataFromNbt", at = @At("TAIL")) + private void readKnockedDownFromNbt(NbtCompound nbt, CallbackInfo ci) { + this.set_KnockedDown(nbt.getBoolean("KnockedDown")); + this.set_ReviveTimer(nbt.getInt("ReviveTimer")); + this.set_KnockedAge(nbt.getInt("KnockedAge")); + } + + @Inject(method = "writeCustomDataToNbt", at = @At("TAIL")) + private void writeKnockedDownToNbt(NbtCompound nbt, CallbackInfo ci) { + nbt.putBoolean("KnockedDown", this.is_KnockedDown()); + nbt.putInt("ReviveTimer", this.get_ReviveTimer()); + nbt.putInt("KnockedAge", this.get_KnockedAge()); + } + + @Override + public boolean is_KnockedDown() { + return this.dataTracker.get(KNOCKED_DOWN); + } + + @Override + public void set_KnockedDown(boolean knockedDown) { + this.dataTracker.set(KNOCKED_DOWN, knockedDown); + } + + @Override + public boolean is_Reviving() { + return this.dataTracker.get(IS_REVIVING); + } + + @Override + public void set_Reviving(boolean reviving) { + this.dataTracker.set(IS_REVIVING, reviving); + } + + @Override + public int get_ReviverCount() { + return this.dataTracker.get(REVIVER_COUNT); + } + + @Override + public void set_ReviverCount(int reviverCount) { + this.dataTracker.set(REVIVER_COUNT, reviverCount); + } + + @Override + public int get_ReviveTimer() { + return this.dataTracker.get(REVIVE_TIMER); + } + + @Override + public void set_ReviveTimer(int reviveTimer) { + this.dataTracker.set(REVIVE_TIMER, reviveTimer); + } + + @Override + public int get_KnockedAge() { + return knocked_age; + } + + @Override + public void set_KnockedAge(int knockedAge) { + this.knocked_age = knockedAge; + } +} diff --git a/src/client/java/ru/octol1ttle/knockdowns/mixin/client/ClientPlayerEntityMixin.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/client/ClientPlayerEntityMixin.java similarity index 61% rename from src/client/java/ru/octol1ttle/knockdowns/mixin/client/ClientPlayerEntityMixin.java rename to common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/client/ClientPlayerEntityMixin.java index aed34f1..440c2ef 100644 --- a/src/client/java/ru/octol1ttle/knockdowns/mixin/client/ClientPlayerEntityMixin.java +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/mixin/client/ClientPlayerEntityMixin.java @@ -1,15 +1,15 @@ -package ru.octol1ttle.knockdowns.mixin.client; +package ru.octol1ttle.knockdowns.common.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; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; @Mixin(ClientPlayerEntity.class) -public abstract class ClientPlayerEntityMixin { +public abstract class ClientPlayerEntityMixin implements IKnockableDown { @ModifyReturnValue(method = "shouldSlowDown", at = @At("RETURN")) private boolean shouldSlowDown(boolean original) { - return original || ((IKnockableDown) this).knockdowns$isKnockedDown(); + return original || this.is_KnockedDown(); } -} +} \ No newline at end of file diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java new file mode 100644 index 0000000..8544efb --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/KnockdownsNetwork.java @@ -0,0 +1,57 @@ +package ru.octol1ttle.knockdowns.common.network; + +import dev.architectury.networking.NetworkChannel; +import dev.architectury.networking.NetworkManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import ru.octol1ttle.knockdowns.common.KnockdownsCommon; +import ru.octol1ttle.knockdowns.common.network.packets.PlayKnockedDownSoundS2CPacket; +import ru.octol1ttle.knockdowns.common.network.packets.RequestStartRevivingC2SPacket; +import ru.octol1ttle.knockdowns.common.network.packets.StopRevivingC2SPacket; + +public class KnockdownsNetwork { + private static final NetworkChannel CHANNEL = NetworkChannel.create(new Identifier(KnockdownsCommon.MOD_ID, "main")); + public static void registerPackets() { + CHANNEL.register(PlayKnockedDownSoundS2CPacket.class, PlayKnockedDownSoundS2CPacket::encode, PlayKnockedDownSoundS2CPacket::new, PlayKnockedDownSoundS2CPacket::apply); + CHANNEL.register(RequestStartRevivingC2SPacket.class, RequestStartRevivingC2SPacket::encode, RequestStartRevivingC2SPacket::new, RequestStartRevivingC2SPacket::apply); + CHANNEL.register(StopRevivingC2SPacket.class, StopRevivingC2SPacket::encode, StopRevivingC2SPacket::new, StopRevivingC2SPacket::apply); + } + + public static void sendToServer(T message) { + if (CHANNEL.canServerReceive(message.getClass())) { + CHANNEL.sendToServer(message); + } + } + + public static void sendToPlayer(PlayerEntity player, T message) { + Packet packet = CHANNEL.toPacket(NetworkManager.Side.S2C, message); + Class messageClass = message.getClass(); + + sendToPlayer(player, packet, messageClass); + } + + public static void sendToPlayer(PlayerEntity player, Packet packet, Class messageClass) { + if (!(player instanceof ServerPlayerEntity serverPlayer)) { + throw new IllegalArgumentException("Cannot send to client players"); + } + if (CHANNEL.canPlayerReceive(serverPlayer, messageClass)) { + serverPlayer.networkHandler.sendPacket(packet); + } + } + + public static void sendToWorld(ServerWorld world, T message) { + Packet packet = CHANNEL.toPacket(NetworkManager.Side.S2C, message); + Class messageClass = message.getClass(); + + sendToWorld(world, packet, messageClass); + } + + private static void sendToWorld(ServerWorld world, Packet packet, Class messageClass) { + for (ServerPlayerEntity player : world.getPlayers()) { + sendToPlayer(player, packet, messageClass); + } + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/KnockdownsPacket.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/KnockdownsPacket.java new file mode 100644 index 0000000..f9288d7 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/KnockdownsPacket.java @@ -0,0 +1,15 @@ +package ru.octol1ttle.knockdowns.common.network.packets; + +import dev.architectury.networking.NetworkManager; +import java.util.function.Supplier; +import net.minecraft.network.PacketByteBuf; + +public abstract class KnockdownsPacket { + public KnockdownsPacket(/* args here */) { + // Message creation + } + + public abstract void encode(PacketByteBuf buf); + + public abstract void apply(Supplier contextSupplier); +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/PlayKnockedDownSoundS2CPacket.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/PlayKnockedDownSoundS2CPacket.java new file mode 100644 index 0000000..fa9f4fb --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/PlayKnockedDownSoundS2CPacket.java @@ -0,0 +1,36 @@ +package ru.octol1ttle.knockdowns.common.network.packets; + +import dev.architectury.networking.NetworkManager; +import java.util.function.Supplier; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.math.Vec3d; +import ru.octol1ttle.knockdowns.common.KnockdownsClient; + +public class PlayKnockedDownSoundS2CPacket extends KnockdownsPacket { + private final double x; + private final double y; + private final double z; + + public PlayKnockedDownSoundS2CPacket(PacketByteBuf buf) { + this(buf.readDouble(), buf.readDouble(), buf.readDouble()); + } + + public PlayKnockedDownSoundS2CPacket(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public void encode(PacketByteBuf buf) { + buf.writeDouble(this.x); + buf.writeDouble(this.y); + buf.writeDouble(this.z); + } + + @Override + public void apply(Supplier contextSupplier) { + NetworkManager.PacketContext context = contextSupplier.get(); + context.queue(() -> KnockdownsClient.playKnockedDownSound(new Vec3d(this.x, this.y, this.z))); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/RequestStartRevivingC2SPacket.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/RequestStartRevivingC2SPacket.java new file mode 100644 index 0000000..31a7de0 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/RequestStartRevivingC2SPacket.java @@ -0,0 +1,39 @@ +package ru.octol1ttle.knockdowns.common.network.packets; + +import dev.architectury.networking.NetworkManager; +import java.util.UUID; +import java.util.function.Supplier; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +public class RequestStartRevivingC2SPacket extends KnockdownsPacket { + private final UUID targetUuid; + + public RequestStartRevivingC2SPacket(PacketByteBuf buf) { + this(buf.readUuid()); + } + + public RequestStartRevivingC2SPacket(UUID targetUuid) { + this.targetUuid = targetUuid; + } + + @Override + public void encode(PacketByteBuf buf) { + buf.writeUuid(this.targetUuid); + } + + @Override + public void apply(Supplier contextSupplier) { + NetworkManager.PacketContext context = contextSupplier.get(); + context.queue(() -> { + PlayerEntity player = context.getPlayer(); + IKnockableDown playerKnockable = (IKnockableDown) player; + IKnockableDown targetKnockable = (IKnockableDown) player.getWorld().getPlayerByUuid(this.targetUuid); + if (!playerKnockable.is_Reviving() && targetKnockable != null && targetKnockable.is_KnockedDown()) { + playerKnockable.set_Reviving(true); + targetKnockable.set_ReviverCount(targetKnockable.get_ReviverCount() + 1); + } + }); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/StopRevivingC2SPacket.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/StopRevivingC2SPacket.java new file mode 100644 index 0000000..260cc1e --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/network/packets/StopRevivingC2SPacket.java @@ -0,0 +1,41 @@ +package ru.octol1ttle.knockdowns.common.network.packets; + +import dev.architectury.networking.NetworkManager; +import java.util.UUID; +import java.util.function.Supplier; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import ru.octol1ttle.knockdowns.common.api.IKnockableDown; + +public class StopRevivingC2SPacket extends KnockdownsPacket { + private final UUID targetUuid; + + public StopRevivingC2SPacket(PacketByteBuf buf) { + this(buf.readUuid()); + } + + public StopRevivingC2SPacket(UUID targetUuid) { + this.targetUuid = targetUuid; + } + + @Override + public void encode(PacketByteBuf buf) { + buf.writeUuid(this.targetUuid); + } + + @Override + public void apply(Supplier contextSupplier) { + NetworkManager.PacketContext context = contextSupplier.get(); + context.queue(() -> { + PlayerEntity player = context.getPlayer(); + IKnockableDown playerKnockable = (IKnockableDown) player; + IKnockableDown targetKnockable = (IKnockableDown) player.getWorld().getPlayerByUuid(this.targetUuid); + if (playerKnockable.is_Reviving() && targetKnockable != null) { + playerKnockable.set_Reviving(false); + if (targetKnockable.is_KnockedDown()) { + targetKnockable.set_ReviverCount(targetKnockable.get_ReviverCount() - 1); + } + } + }); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/registries/KnockdownsSoundEvents.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/registries/KnockdownsSoundEvents.java new file mode 100644 index 0000000..a207925 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/registries/KnockdownsSoundEvents.java @@ -0,0 +1,18 @@ +package ru.octol1ttle.knockdowns.common.registries; + +import dev.architectury.registry.registries.DeferredRegister; +import dev.architectury.registry.registries.RegistrySupplier; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.Identifier; +import ru.octol1ttle.knockdowns.common.KnockdownsCommon; + +public class KnockdownsSoundEvents { + private static final DeferredRegister SOUND_EVENTS = DeferredRegister.create(KnockdownsCommon.MOD_ID, RegistryKeys.SOUND_EVENT); + public static final RegistrySupplier KNOCKED_DOWN = SOUND_EVENTS.register(KnockdownsCommon.MOD_ID, + () -> SoundEvent.of(new Identifier(KnockdownsCommon.MOD_ID, "knocked_down"))); + + public static void register() { + SOUND_EVENTS.register(); + } +} diff --git a/common/src/main/java/ru/octol1ttle/knockdowns/common/registries/KnockedDownSoundInstance.java b/common/src/main/java/ru/octol1ttle/knockdowns/common/registries/KnockedDownSoundInstance.java new file mode 100644 index 0000000..7781165 --- /dev/null +++ b/common/src/main/java/ru/octol1ttle/knockdowns/common/registries/KnockedDownSoundInstance.java @@ -0,0 +1,31 @@ +package ru.octol1ttle.knockdowns.common.registries; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.sound.MovingSoundInstance; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.random.Random; + +public class KnockedDownSoundInstance extends MovingSoundInstance { + private final Vec3d pos; + + public KnockedDownSoundInstance(SoundEvent sound, Vec3d pos) { + super(sound, SoundCategory.MASTER, Random.create(0L)); + this.pos = pos; + this.relative = true; + } + + @Override + public void tick() { + ClientPlayerEntity player = MinecraftClient.getInstance().player; + if (player == null) { + throw new IllegalStateException(); + } + Vec3d vec = pos.subtract(player.getPos()).normalize(); + this.x = vec.x; + this.y = vec.y; + this.z = vec.z; + } +} diff --git a/common/src/main/resources/architectury.common.json b/common/src/main/resources/architectury.common.json new file mode 100644 index 0000000..2fc0d72 --- /dev/null +++ b/common/src/main/resources/architectury.common.json @@ -0,0 +1,3 @@ +{ + "accessWidener": "knockdowns.accesswidener" +} \ No newline at end of file diff --git a/src/main/resources/assets/knockdowns/lang/en_us.json b/common/src/main/resources/assets/knockdowns/lang/en_us.json similarity index 99% rename from src/main/resources/assets/knockdowns/lang/en_us.json rename to common/src/main/resources/assets/knockdowns/lang/en_us.json index 256bd9b..57cacc4 100644 --- a/src/main/resources/assets/knockdowns/lang/en_us.json +++ b/common/src/main/resources/assets/knockdowns/lang/en_us.json @@ -1,4 +1,6 @@ { + "subtitles.knockdowns.knocked_down": "Player knocked down", + "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", @@ -97,6 +99,5 @@ "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" + "knockdown.fell.killer": "%1$s was doomed to get knocked down by a fall" } \ No newline at end of file diff --git a/src/main/resources/assets/knockdowns/lang/ru_ru.json b/common/src/main/resources/assets/knockdowns/lang/ru_ru.json similarity index 99% rename from src/main/resources/assets/knockdowns/lang/ru_ru.json rename to common/src/main/resources/assets/knockdowns/lang/ru_ru.json index 5cdd965..9fd0739 100644 --- a/src/main/resources/assets/knockdowns/lang/ru_ru.json +++ b/common/src/main/resources/assets/knockdowns/lang/ru_ru.json @@ -1,4 +1,6 @@ { + "subtitles.knockdowns.knocked_down": "Игрок тяжело ранен", + "knockdown.attack.anvil": "%1$s был тяжело ранен упавшей наковальней", "knockdown.attack.anvil.player": "%1$s был тяжело ранен упавшей наковальней, пока сражался с %2$s", "knockdown.attack.arrow": "%1$s тяжело ранен стрелой %2$s", @@ -97,6 +99,5 @@ "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": "Игрок тяжело ранен" + "knockdown.fell.killer": "%1$s был тяжело ранен падением" } \ No newline at end of file diff --git a/src/main/resources/assets/knockdowns/sounds.json b/common/src/main/resources/assets/knockdowns/sounds.json similarity index 100% rename from src/main/resources/assets/knockdowns/sounds.json rename to common/src/main/resources/assets/knockdowns/sounds.json diff --git a/common/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg b/common/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg new file mode 100644 index 0000000..ccb17cc Binary files /dev/null and b/common/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg differ diff --git a/common/src/main/resources/knockdowns-common.mixins.json b/common/src/main/resources/knockdowns-common.mixins.json new file mode 100644 index 0000000..b157b56 --- /dev/null +++ b/common/src/main/resources/knockdowns-common.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "package": "ru.octol1ttle.knockdowns.common.mixin", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "client": [ + "client.ClientPlayerEntityMixin" + ], + "mixins": [ + "LivingEntityMixin", + "MobEntityMixin", + "PlayerEntityMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/common/src/main/resources/knockdowns.accesswidener b/common/src/main/resources/knockdowns.accesswidener new file mode 100644 index 0000000..13268c3 --- /dev/null +++ b/common/src/main/resources/knockdowns.accesswidener @@ -0,0 +1 @@ +accessWidener v2 named \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 0000000..5ca331b --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,77 @@ +plugins { + id "com.github.johnrengelman.shadow" version "7.1.2" +} + +architectury { + platformSetupLoomIde() + fabric() +} + +loom { + accessWidenerPath = project(":common").loom.accessWidenerPath +} + +configurations { + common + shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files. + compileClasspath.extendsFrom common + runtimeClasspath.extendsFrom common + developmentFabric.extendsFrom common +} + +dependencies { + modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + modApi "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}" + // Remove the next line if you don't want to depend on the API + modApi "dev.architectury:architectury-fabric:${rootProject.architectury_version}" + + common(project(path: ":common", configuration: "namedElements")) { transitive false } + shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +shadowJar { + exclude "architectury.common.json" + + configurations = [project.configurations.shadowCommon] + archiveClassifier = "dev-shadow" +} + +remapJar { + injectAccessWidener = true + input.set shadowJar.archiveFile + dependsOn shadowJar +} + +sourcesJar { + def commonSources = project(":common").sourcesJar + dependsOn commonSources + from commonSources.archiveFile.map { zipTree(it) } +} + +components.java { + withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { + skip() + } +} + +publishing { + publications { + mavenFabric(MavenPublication) { + artifactId = rootProject.archives_base_name + "-" + project.name + 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. + } +} diff --git a/fabric/src/main/java/ru/octol1ttle/knockdowns/fabric/KnockdownsFabric.java b/fabric/src/main/java/ru/octol1ttle/knockdowns/fabric/KnockdownsFabric.java new file mode 100644 index 0000000..6a6fa40 --- /dev/null +++ b/fabric/src/main/java/ru/octol1ttle/knockdowns/fabric/KnockdownsFabric.java @@ -0,0 +1,11 @@ +package ru.octol1ttle.knockdowns.fabric; + +import ru.octol1ttle.knockdowns.common.KnockdownsCommon; +import net.fabricmc.api.ModInitializer; + +public class KnockdownsFabric implements ModInitializer { + @Override + public void onInitialize() { + KnockdownsCommon.init(); + } +} diff --git a/fabric/src/main/java/ru/octol1ttle/knockdowns/fabric/KnockdownsFabricClient.java b/fabric/src/main/java/ru/octol1ttle/knockdowns/fabric/KnockdownsFabricClient.java new file mode 100644 index 0000000..2a651c4 --- /dev/null +++ b/fabric/src/main/java/ru/octol1ttle/knockdowns/fabric/KnockdownsFabricClient.java @@ -0,0 +1,11 @@ +package ru.octol1ttle.knockdowns.fabric; + +import net.fabricmc.api.ClientModInitializer; +import ru.octol1ttle.knockdowns.common.KnockdownsClient; + +public class KnockdownsFabricClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + KnockdownsClient.init(); + } +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..5e30f2e --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "knockdowns", + "version": "${version}", + "name": "Knockdowns", + "description": "DBNO mechanic from Fortnite, ported to Minecraft", + "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.fabric.KnockdownsFabric" + ], + "client": [ + "ru.octol1ttle.knockdowns.fabric.KnockdownsFabricClient" + ] + }, + "mixins": [ + "knockdowns.mixins.json", + "knockdowns-common.mixins.json" + ], + "depends": { + "fabric": "*", + "minecraft": ">=1.20.1", + "architectury": ">=9.1.12" + } +} \ No newline at end of file diff --git a/fabric/src/main/resources/knockdowns.mixins.json b/fabric/src/main/resources/knockdowns.mixins.json new file mode 100644 index 0000000..76a92ed --- /dev/null +++ b/fabric/src/main/resources/knockdowns.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "ru.octol1ttle.knockdowns.fabric.mixin", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/forge/build.gradle b/forge/build.gradle new file mode 100644 index 0000000..490b2f1 --- /dev/null +++ b/forge/build.gradle @@ -0,0 +1,86 @@ +plugins { + id "com.github.johnrengelman.shadow" version "7.1.2" +} + +architectury { + platformSetupLoomIde() + forge() +} + +loom { + accessWidenerPath = project(":common").loom.accessWidenerPath + + forge { + convertAccessWideners = true + extraAccessWideners.add loom.accessWidenerPath.get().asFile.name + + mixinConfig "knockdowns-common.mixins.json" + mixinConfig "knockdowns.mixins.json" + } +} + +configurations { + common + shadowCommon // Don't use shadow from the shadow plugin since it *excludes* files. + compileClasspath.extendsFrom common + runtimeClasspath.extendsFrom common + developmentForge.extendsFrom common +} + +dependencies { + forge "net.minecraftforge:forge:${rootProject.forge_version}" + // Remove the next line if you don't want to depend on the API + modApi "dev.architectury:architectury-forge:${rootProject.architectury_version}" + + common(project(path: ":common", configuration: "namedElements")) { transitive false } + shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false } + + implementation(include("io.github.llamalad7:mixinextras-forge:0.3.3")) +} + +processResources { + inputs.property "version", project.version + + filesMatching("META-INF/mods.toml") { + expand "version": project.version + } +} + +shadowJar { + exclude "fabric.mod.json" + exclude "architectury.common.json" + + configurations = [project.configurations.shadowCommon] + archiveClassifier = "dev-shadow" +} + +remapJar { + input.set shadowJar.archiveFile + dependsOn shadowJar +} + +sourcesJar { + def commonSources = project(":common").sourcesJar + dependsOn commonSources + from commonSources.archiveFile.map { zipTree(it) } +} + +components.java { + withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) { + skip() + } +} + +publishing { + publications { + mavenForge(MavenPublication) { + artifactId = rootProject.archives_base_name + "-" + project.name + 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. + } +} diff --git a/forge/gradle.properties b/forge/gradle.properties new file mode 100644 index 0000000..32f842a --- /dev/null +++ b/forge/gradle.properties @@ -0,0 +1 @@ +loom.platform=forge \ No newline at end of file diff --git a/forge/src/main/java/ru/octol1ttle/knockdowns/forge/KnockdownsForge.java b/forge/src/main/java/ru/octol1ttle/knockdowns/forge/KnockdownsForge.java new file mode 100644 index 0000000..83a53a8 --- /dev/null +++ b/forge/src/main/java/ru/octol1ttle/knockdowns/forge/KnockdownsForge.java @@ -0,0 +1,26 @@ +package ru.octol1ttle.knockdowns.forge; + +import dev.architectury.platform.forge.EventBuses; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import ru.octol1ttle.knockdowns.common.KnockdownsClient; +import ru.octol1ttle.knockdowns.common.KnockdownsCommon; + +@SuppressWarnings("unused") +@Mod(KnockdownsCommon.MOD_ID) +public class KnockdownsForge { + public KnockdownsForge() { + // Submit our event bus to let architectury register our content on the right time + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + EventBuses.registerModEventBus(KnockdownsCommon.MOD_ID, modEventBus); + modEventBus.addListener(this::onInitializeClient); + + KnockdownsCommon.init(); + } + + public void onInitializeClient(FMLClientSetupEvent event) { + KnockdownsClient.init(); + } +} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..9e1ece6 --- /dev/null +++ b/forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,35 @@ +modLoader = "javafml" +loaderVersion = "[47,)" +#issueTrackerURL = "" +license = "ARR" + +[[mods]] +modId = "knockdowns" +version = "${version}" +displayName = "Knockdowns" +authors = "Octol1ttle" +description = ''' +DBNO mechanic from Fortnite, ported to Minecraft +''' +#logoFile = "" + +[[dependencies.knockdowns]] +modId = "forge" +mandatory = true +versionRange = "[47,)" +ordering = "NONE" +side = "BOTH" + +[[dependencies.knockdowns]] +modId = "minecraft" +mandatory = true +versionRange = "[1.20.1,)" +ordering = "NONE" +side = "BOTH" + +[[dependencies.knockdowns]] +modId = "architectury" +mandatory = true +versionRange = "[9.1.12,)" +ordering = "AFTER" +side = "BOTH" \ No newline at end of file diff --git a/src/client/resources/knockdowns.client.mixins.json b/forge/src/main/resources/knockdowns.mixins.json similarity index 54% rename from src/client/resources/knockdowns.client.mixins.json rename to forge/src/main/resources/knockdowns.mixins.json index ccd33aa..bc08964 100644 --- a/src/client/resources/knockdowns.client.mixins.json +++ b/forge/src/main/resources/knockdowns.mixins.json @@ -1,11 +1,13 @@ { "required": true, - "package": "ru.octol1ttle.knockdowns.mixin.client", + "package": "ru.octol1ttle.knockdowns.forge.mixin", "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", "client": [ - "ClientPlayerEntityMixin" + ], + "mixins": [ ], "injectors": { "defaultRequire": 1 - } + } } \ No newline at end of file diff --git a/forge/src/main/resources/pack.mcmeta b/forge/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..edf178a --- /dev/null +++ b/forge/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "Knockdowns", + "pack_format": 15 + } +} diff --git a/gradle.properties b/gradle.properties index f5d3061..f07c82c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,17 @@ -# Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx1G -org.gradle.parallel=true +org.gradle.jvmargs=-Xmx2048M -# 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 +minecraft_version=1.20.1 +enabled_platforms=fabric,forge -# Mod Properties -mod_version=1.1.1 -maven_group=ru.octol1ttle.knockdowns archives_base_name=knockdowns +mod_version=2.1.0 +maven_group=ru.octol1ttle.knockdowns + +architectury_version=9.1.12 +fabric_api_version=0.90.4+1.20.1 + +fabric_loader_version=0.15.5 +forge_version=1.20.1-47.2.0 + + -# 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 index 7f93135..ccebba7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..744c64d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip networkTimeout=10000 -validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 0adc8e1..79a61d4 100644 --- a/gradlew +++ b/gradlew @@ -83,8 +83,10 @@ 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 +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# 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"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -131,13 +133,10 @@ 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. + which java >/dev/null 2>&1 || 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. @@ -198,10 +197,6 @@ if "$cygwin" || "$msys" ; then 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 diff --git a/settings.gradle b/settings.gradle index 75c4d72..163e9f2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,10 +1,14 @@ pluginManagement { - repositories { - maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' - } - mavenCentral() - gradlePluginPortal() - } -} \ No newline at end of file + repositories { + maven { url "https://maven.fabricmc.net/" } + maven { url "https://maven.architectury.dev/" } + maven { url "https://maven.minecraftforge.net/" } + gradlePluginPortal() + } +} + +include("common") +include("fabric") +include("forge") + +rootProject.name = "knockdowns-modern" diff --git a/src/client/java/ru/octol1ttle/knockdowns/KnockdownsClient.java b/src/client/java/ru/octol1ttle/knockdowns/KnockdownsClient.java deleted file mode 100644 index 25ff0de..0000000 --- a/src/client/java/ru/octol1ttle/knockdowns/KnockdownsClient.java +++ /dev/null @@ -1,166 +0,0 @@ -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, PacketByteBufs.copy(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/main/java/ru/octol1ttle/knockdowns/Knockdowns.java b/src/main/java/ru/octol1ttle/knockdowns/Knockdowns.java deleted file mode 100644 index a59282c..0000000 --- a/src/main/java/ru/octol1ttle/knockdowns/Knockdowns.java +++ /dev/null @@ -1,219 +0,0 @@ -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 net.minecraft.world.GameRules; -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; - } - - ServerPlayerEntity serverPlayer = (ServerPlayerEntity) entity; - // TODO: timer - if (!serverPlayer.getWorld().getGameRules().getBoolean(GameRules.KEEP_INVENTORY)) { - serverPlayer.getInventory().dropAll(); - } - entity.setHealth(1.0f); - entity.setInvulnerable(true); - entity.setGlowing(true); - entity.setAir(entity.getMaxAir()); - entity.extinguish(); - entity.setFrozenTicks(0); - entity.setOnFire(false); - entity.clearStatusEffects(); - - knockableDown.knockdowns$setKnockedDown(true); - - PacketByteBuf buf = PacketByteBufs.create(); - buf.writeUuid(entity.getUuid()); - buf.writeBoolean(true); - - 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 deleted file mode 100644 index b06d4a4..0000000 --- a/src/main/java/ru/octol1ttle/knockdowns/api/IKnockableDown.java +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 3852bee..0000000 --- a/src/main/java/ru/octol1ttle/knockdowns/mixin/PlayerEntityMixin.java +++ /dev/null @@ -1,71 +0,0 @@ -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 net.minecraft.nbt.NbtCompound; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import ru.octol1ttle.knockdowns.api.IKnockableDown; - -@Mixin(PlayerEntity.class) -public abstract class PlayerEntityMixin implements IKnockableDown { - @Unique - private boolean knockedDown; - @Unique - private boolean beingRevived; - - @ModifyExpressionValue(method = "updatePose", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerEntity;isSwimming()Z")) - private boolean enterSwimmingIfKnockedDown(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 dontHealIfKnockedDown(boolean original) { - return original && !this.knockdowns$isKnockedDown(); - } - - @Inject(method = "readCustomDataFromNbt", at = @At("TAIL")) - public void readKnockedDownFromNbt(NbtCompound nbt, CallbackInfo ci) { - this.knockedDown = nbt.getBoolean("KnockedDown"); - } - - @Inject(method = "writeCustomDataToNbt", at = @At("TAIL")) - public void writeKnockedDownToNbt(NbtCompound nbt, CallbackInfo ci) { - nbt.putBoolean("KnockedDown", this.knockedDown); - } - - @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 deleted file mode 100644 index a618797..0000000 --- a/src/main/java/ru/octol1ttle/knockdowns/network/KnockdownsNetworkingConstants.java +++ /dev/null @@ -1,13 +0,0 @@ -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 deleted file mode 100644 index 047b91f..0000000 Binary files a/src/main/resources/assets/knockdowns/icon.png and /dev/null differ diff --git a/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg b/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg deleted file mode 100644 index a01bae8..0000000 Binary files a/src/main/resources/assets/knockdowns/sounds/knocked_down.ogg and /dev/null differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json deleted file mode 100644 index c4330c1..0000000 --- a/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "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 deleted file mode 100644 index 81139b2..0000000 --- a/src/main/resources/knockdowns.mixins.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "required": true, - "package": "ru.octol1ttle.knockdowns.mixin", - "compatibilityLevel": "JAVA_17", - "mixins": [ - "PlayerEntityMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} \ No newline at end of file