From 9c6f94c10b72504301dcec740d27e84cc9e08edd Mon Sep 17 00:00:00 2001 From: Axionize <154778082+Axionize@users.noreply.github.com> Date: Wed, 17 Dec 2025 22:53:11 -0500 Subject: [PATCH] fix: 1.21.11 Fabric support --- fabric/build.gradle.kts | 2 +- .../mixin/EntityTrackerEntryMixin.java | 59 +++++++++++++++++++ .../knockbacksync/mixin/PlayerMixin.java | 55 ++++++++++++----- .../mixin/ServerEntityMixin.java | 59 ------------------- .../main/resources/knockbacksync.mixins.json | 2 +- 5 files changed, 102 insertions(+), 75 deletions(-) create mode 100644 fabric/src/main/java/me/caseload/knockbacksync/mixin/EntityTrackerEntryMixin.java delete mode 100644 fabric/src/main/java/me/caseload/knockbacksync/mixin/ServerEntityMixin.java diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index c134c35..e00bdb7 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { modImplementation(fabricApi.module("fabric-lifecycle-events-v1", "${rootProject.property("fabric_version")}")) modImplementation(fabricApi.module("fabric-events-interaction-v0", "${rootProject.property("fabric_version")}")) - include(modImplementation("me.lucko:fabric-permissions-api:0.3.1")!!) + include(modImplementation("me.lucko:fabric-permissions-api:0.6.1")!!) include(modImplementation("com.github.retrooper:packetevents-fabric:2.11.1+60a2c34-SNAPSHOT")!!) include(modImplementation("org.incendo:cloud-fabric:2.0.0-beta.15")!!) diff --git a/fabric/src/main/java/me/caseload/knockbacksync/mixin/EntityTrackerEntryMixin.java b/fabric/src/main/java/me/caseload/knockbacksync/mixin/EntityTrackerEntryMixin.java new file mode 100644 index 0000000..ffc2b90 --- /dev/null +++ b/fabric/src/main/java/me/caseload/knockbacksync/mixin/EntityTrackerEntryMixin.java @@ -0,0 +1,59 @@ +package me.caseload.knockbacksync.mixin; + +import me.caseload.knockbacksync.callback.PlayerVelocityEvent; +import net.minecraft.entity.Entity; +import net.minecraft.server.network.EntityTrackerEntry; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.Vec3d; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(EntityTrackerEntry.class) +public class EntityTrackerEntryMixin { + + @Shadow @Final private Entity entity; + + // We inject exactly where 'this.entity.knockedBack = false' is about to happen. + @Inject(method = "tick", + at = @At(value = "FIELD", + target = "Lnet/minecraft/entity/Entity;knockedBack:Z", + opcode = Opcodes.PUTFIELD), + cancellable = true) + private void onKnockbackSync(CallbackInfo ci) { + // This check ensures we are in the "if (entity.knockedBack)" block + // and operating on a Player. + if (this.entity instanceof ServerPlayerEntity serverPlayer) { + + // 1. Capture the velocity CURRENTLY on the player + Vec3d currentVelocity = serverPlayer.getVelocity(); + + // 2. Fire the event + ActionResult result = PlayerVelocityEvent.EVENT.invoker().onVelocityChange(serverPlayer, currentVelocity); + + if (result == ActionResult.FAIL) { + // CASE: CANCELLED + // We want to STOP the packet from sending. + // We also want to keep 'knockedBack' as true (technically), + // effectively acting like the block never happened. + ci.cancel(); + } + else if (result == ActionResult.SUCCESS) { + // CASE: MODIFIED + // The event handler has already updated the player's velocity: + // player.setVelocity(newVel); + + // We do NOT cancel. We let Vanilla proceed. + // Vanilla will: + // 1. Set knockedBack = false; + // 2. Create a NEW packet reading the current velocity (which is now your custom velocity). + // 3. Send it. + } + } + } +} \ No newline at end of file diff --git a/fabric/src/main/java/me/caseload/knockbacksync/mixin/PlayerMixin.java b/fabric/src/main/java/me/caseload/knockbacksync/mixin/PlayerMixin.java index bebf7a3..612f79d 100644 --- a/fabric/src/main/java/me/caseload/knockbacksync/mixin/PlayerMixin.java +++ b/fabric/src/main/java/me/caseload/knockbacksync/mixin/PlayerMixin.java @@ -14,23 +14,50 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(PlayerEntity.class) public class PlayerMixin { - @Inject(method = "attack", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityVelocityUpdateS2CPacket;(Lnet/minecraft/entity/Entity;)V"), cancellable = true) - private void onAttack(Entity target, CallbackInfo ci) { - if (target instanceof ServerPlayerEntity serverPlayer && target.velocityDirty) { - Vec3d velocity = target.getVelocity(); - ActionResult result = PlayerVelocityEvent.EVENT.invoker().onVelocityChange(serverPlayer, velocity); + @Inject(method = "knockbackTarget", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;)V"), + cancellable = true) + private void onKnockback(Entity target, float strength, Vec3d playerTargetVelocity, CallbackInfo ci) { + if (target instanceof ServerPlayerEntity serverPlayer && target.knockedBack) { + + // 1. Get the "Proposed" Velocity (The knockback calculated lines above) + Vec3d proposedVelocity = target.getVelocity(); + + // 2. Fire the Event + ActionResult result = PlayerVelocityEvent.EVENT.invoker().onVelocityChange(serverPlayer, proposedVelocity); if (result == ActionResult.FAIL) { - target.velocityDirty = false; - ci.cancel(); // Prevent sending the velocity packet - } else if (result == ActionResult.SUCCESS) { - // Currently unnecessary since we do this in the handler, will move later - target.setVelocity(velocity); - serverPlayer.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(target)); - target.velocityDirty = false; - ci.cancel(); // Prevent sending the original velocity packet + // --- REPLICATING PAPER: CANCELLED --- + // In Paper: if (event.isCancelled()) { cancelled = true; } + // In Paper: if (!cancelled) { sendPacket; cleanup; } + + // So, if cancelled, we stop EVERYTHING. We do not send packet, we do not reset velocity. + ci.cancel(); + return; } + + if (result == ActionResult.SUCCESS) { + // --- REPLICATING PAPER: MODIFIED --- + // In Paper: if (!velocity.equals(event.getVelocity())) { player.setVelocity(...) } + // In Paper: if (!cancelled) { sendPacket; setDeltaMovement(currentMovement); } + + // 1. The event listener should have already set the new velocity on the entity. + // 2. We must send a NEW packet (because the one on the stack is old). + serverPlayer.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(target)); + + // 3. Replicate the Cleanup: Paper resets velocity to 'currentMovement' (playerTargetVelocity) + // This prevents the server from thinking the player is flying, maintaining Client Authority. + target.knockedBack = false; + target.setVelocity(playerTargetVelocity); + + // 4. Cancel the original logic so Vanilla doesn't send the old packet or do double cleanup. + ci.cancel(); + } + + // CASE: PASS + // Vanilla behaves normally (Sends packet, does cleanup). } } -} +} \ No newline at end of file diff --git a/fabric/src/main/java/me/caseload/knockbacksync/mixin/ServerEntityMixin.java b/fabric/src/main/java/me/caseload/knockbacksync/mixin/ServerEntityMixin.java deleted file mode 100644 index 7f4d2c4..0000000 --- a/fabric/src/main/java/me/caseload/knockbacksync/mixin/ServerEntityMixin.java +++ /dev/null @@ -1,59 +0,0 @@ -package me.caseload.knockbacksync.mixin; - -import me.caseload.knockbacksync.callback.PlayerVelocityEvent; -import net.minecraft.entity.Entity; -import net.minecraft.server.network.EntityTrackerEntry; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.ActionResult; -import net.minecraft.util.math.Vec3d; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(EntityTrackerEntry.class) -public class ServerEntityMixin { - - @Shadow - @Final - private Entity entity; - - // Velocity event - @Inject(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/Entity;velocityModified:Z", ordinal = 1), cancellable = true) - private void onSendChanges(CallbackInfo ci) { - if (this.entity.velocityDirty && this.entity instanceof ServerPlayerEntity player) { - Vec3d velocity = player.getVelocity(); - - ActionResult result = PlayerVelocityEvent.EVENT.invoker().onVelocityChange(player, velocity); - - if (result == ActionResult.FAIL) { - this.entity.velocityDirty = false; - ci.cancel(); - } else if (result == ActionResult.SUCCESS) { - // Currently unnecessary since we do this in the handler, will move later - player.setVelocity(velocity); - } - } - } - - // Custom tick intervals -// @Shadow -// private int updateInterval; - -// @Accessor("entity") -// Entity getEntity() { -// return null; -// } - -// @Invoker("sendChanges") -// void invokeSendChanges() { -// -// } - -// @Override -// public void setUpdateInterval(int interval) { -// this.updateInterval = interval; -// } -} \ No newline at end of file diff --git a/fabric/src/main/resources/knockbacksync.mixins.json b/fabric/src/main/resources/knockbacksync.mixins.json index f809b1d..a4a1b00 100644 --- a/fabric/src/main/resources/knockbacksync.mixins.json +++ b/fabric/src/main/resources/knockbacksync.mixins.json @@ -5,8 +5,8 @@ "compatibilityLevel": "JAVA_21", "mixins": [ "ChunkMapMixin", + "EntityTrackerEntryMixin", "PlayerMixin", - "ServerEntityMixin", "TickRateManagerMixin" ], "injectors": {