diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index e1e4187..05cd608 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("fabric-loom") version "1.11.7" + id("fabric-loom") version "1.14-SNAPSHOT" id("spectatorplus.platform") } @@ -22,6 +22,20 @@ loom { } } + runs { + getByName("client") { + client() + ideConfigGenerated(true) + runDir("run/client") + } + + getByName("server") { + server() + ideConfigGenerated(true) + runDir("run") + } + } + accessWidenerPath = file("src/main/resources/spectatorplus.accesswidener") } @@ -32,7 +46,6 @@ dependencies { officialMojangMappings() parchment("org.parchmentmc.data:parchment-${property("parchment_minecraft_version")}:${property("parchment_version")}@zip") }) - modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}") include(modImplementation("me.lucko:fabric-permissions-api:${property("fabric_permissions_api_version")}")!!) diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 29facc9..b932122 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,13 +1,13 @@ -minecraft_version=1.21.10 -yarn_mappings=1.21.10+build.1 -loader_version=0.17.2 +minecraft_version=1.21.11 +yarn_mappings=1.21.11+build.3 +loader_version=0.18.4 # Fabric API -fabric_version=0.138.3+1.21.10 +fabric_version=0.140.2+1.21.11 -parchment_minecraft_version=1.21.10 -parchment_version=2025.10.12 +parchment_minecraft_version=1.21.11 +parchment_version=2025.12.20 -fabric_permissions_api_version=0.4.0 -cloth_config_version=19.0.147 -modmenu_version=16.0.0-rc.1 +fabric_permissions_api_version=0.6.1 +cloth_config_version=21.11.153 +modmenu_version=17.0.0-beta.1 diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java index fc3c452..bccc9d7 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/SpectatorKeybinds.java @@ -7,7 +7,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.minecraft.ChatFormatting; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.spectator.SpectatorGui; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java deleted file mode 100644 index 31562dc..0000000 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererAccessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.hpfxd.spectatorplus.fabric.client.mixin; - -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.phys.HitResult; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -@Mixin(GameRenderer.class) -public interface GameRendererAccessor { - @Invoker - HitResult invokePick(Entity entity, double blockRange, double entityRange, float partialTicks); -} diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java index de281b2..5165202 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GameRendererMixin.java @@ -16,8 +16,10 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.level.GameType; import net.minecraft.world.phys.Vec3; import org.joml.Matrix4f; +import org.joml.Matrix4fc; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -45,12 +47,24 @@ public abstract class GameRendererMixin { @Unique private float xBobO; @Unique private float yBobO; +// @Redirect(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Matrix4fc;)V", ordinal = 0)) +// private void redirectMulPose(PoseStack poseStack, Matrix4fc matrix) { +// // In spectator mode, the projection matrix contains rotation data from the spectated player, +// // while arm rendering calculations are based on the localPlayer's viewpoint. +// // We must skip this mulPose operation when spectating to avoid rendering arms with incorrect orientation. +// if (this.minecraft.player == null +// || this.minecraft.player.gameMode() != GameType.SPECTATOR +// || !this.minecraft.options.getCameraType().isFirstPerson()) { +// poseStack.mulPose(matrix); +// } +// } + @Inject(method = "renderItemInHand", at = @At(value = "INVOKE", target = "Lorg/joml/Matrix4fStack;popMatrix()Lorg/joml/Matrix4fStack;", remap = false)) public void spectatorplus$renderItemInHand(float partialTicks, boolean sleeping, Matrix4f projectionMatrix, CallbackInfo ci, @Local PoseStack poseStackIn) { if (SpectatorClientMod.config.renderArms && this.minecraft.player != null && this.minecraft.options.getCameraType().isFirstPerson() && !this.minecraft.options.hideGui) { final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); if (spectated != null && !spectated.isSpectator()) { - this.lightTexture.turnOnLightLayer(); + //this.lightTexture.turnOnLightLayer(); float attackAnim = spectated.getAttackAnim(partialTicks); final InteractionHand interactionHand = MoreObjects.firstNonNull(spectated.swingingArm, InteractionHand.MAIN_HAND); @@ -67,7 +81,7 @@ public abstract class GameRendererMixin { if (handRenderSelection.renderMainHand) { final float swingProgress = interactionHand == InteractionHand.MAIN_HAND ? attackAnim : 0.0F; - final float equippedProgress = 1F - Mth.lerp(partialTicks, accessor.getOMainHandHeight(), accessor.getMainHandHeight()); + final float equippedProgress = accessor.getItemModelResolver().swapAnimationScale(accessor.getMainHandItem()) * (1F - Mth.lerp(partialTicks, accessor.getOMainHandHeight(), accessor.getMainHandHeight())); accessor.invokeRenderArmWithItem(spectated, partialTicks, pitch, InteractionHand.MAIN_HAND, swingProgress, accessor.getMainHandItem(), equippedProgress, @@ -76,14 +90,15 @@ public abstract class GameRendererMixin { if (handRenderSelection.renderOffHand) { final float swingProgress = interactionHand == InteractionHand.OFF_HAND ? attackAnim : 0.0F; - final float equippedProgress = 1F - Mth.lerp(partialTicks, accessor.getOOffHandHeight(), accessor.getOffHandHeight()); + final float equippedProgress = accessor.getItemModelResolver().swapAnimationScale(accessor.getOffHandItem()) * (1F - Mth.lerp(partialTicks, accessor.getOOffHandHeight(), accessor.getOffHandHeight())); accessor.invokeRenderArmWithItem(spectated, partialTicks, pitch, InteractionHand.OFF_HAND, swingProgress, accessor.getOffHandItem(), equippedProgress, poseStackIn, submitNodeCollector, packedLightCoords); } - this.lightTexture.turnOffLightLayer(); + //this.lightTexture.turnOffLightLayer(); + this.minecraft.gameRenderer.getFeatureRenderDispatcher().renderAllFeatures(); this.renderBuffers.bufferSource().endBatch(); } } @@ -161,25 +176,4 @@ private static ItemInHandRenderer.HandRenderSelection evaluateWhichHandsToRender if (minecraft.getCameraEntity() == this.minecraft.player) return instance.getInterpolatedBob(partialTick); return Mth.lerp(partialTick, this.bobO, this.bob); } - - @ModifyExpressionValue(method = "pick(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;blockInteractionRange()D")) - private double spectatorplus$modifyBlockInteractionRange(double original) { - final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); - if (spectated != null) { - return spectated.blockInteractionRange(); - } - - return original; - } - - @ModifyExpressionValue(method = "pick(F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;entityInteractionRange()D")) - private double spectatorplus$modifyEntityInteractionRange(double original) { - final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(this.minecraft); - if (spectated != null) { - return spectated.entityInteractionRange(); - } - - return original; - } - } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java index cb37db1..07f0272 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/GuiMixin.java @@ -40,7 +40,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context.Local; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.core.Holder; @Mixin(Gui.class) @@ -54,15 +54,15 @@ public abstract class GuiMixin { @Shadow @Final private SpectatorGui spectatorGui; - // Use correct ResourceLocations for vanilla empty armor slot icons from the GUI atlas - private static final ResourceLocation EMPTY_ARMOR_SLOT_HELMET = ResourceLocation.withDefaultNamespace("container/slot/helmet"); - private static final ResourceLocation EMPTY_ARMOR_SLOT_CHESTPLATE = ResourceLocation.withDefaultNamespace("container/slot/chestplate"); - private static final ResourceLocation EMPTY_ARMOR_SLOT_LEGGINGS = ResourceLocation.withDefaultNamespace("container/slot/leggings"); - private static final ResourceLocation EMPTY_ARMOR_SLOT_BOOTS = ResourceLocation.withDefaultNamespace("container/slot/boots"); - private static final ResourceLocation EFFECT_BACKGROUND_AMBIENT_SPRITE = ResourceLocation.withDefaultNamespace("hud/effect_background_ambient"); - private static final ResourceLocation EFFECT_BACKGROUND_SPRITE = ResourceLocation.withDefaultNamespace("hud/effect_background"); + // Use correct Identifiers for vanilla empty armor slot icons from the GUI atlas + private static final Identifier EMPTY_ARMOR_SLOT_HELMET = Identifier.withDefaultNamespace("container/slot/helmet"); + private static final Identifier EMPTY_ARMOR_SLOT_CHESTPLATE = Identifier.withDefaultNamespace("container/slot/chestplate"); + private static final Identifier EMPTY_ARMOR_SLOT_LEGGINGS = Identifier.withDefaultNamespace("container/slot/leggings"); + private static final Identifier EMPTY_ARMOR_SLOT_BOOTS = Identifier.withDefaultNamespace("container/slot/boots"); + private static final Identifier EFFECT_BACKGROUND_AMBIENT_SPRITE = Identifier.withDefaultNamespace("hud/effect_background_ambient"); + private static final Identifier EFFECT_BACKGROUND_SPRITE = Identifier.withDefaultNamespace("hud/effect_background"); - private static final ResourceLocation[] TEXTURE_EMPTY_SLOTS = new ResourceLocation[]{ + private static final Identifier[] TEXTURE_EMPTY_SLOTS = new Identifier[]{ EMPTY_ARMOR_SLOT_BOOTS, EMPTY_ARMOR_SLOT_LEGGINGS, EMPTY_ARMOR_SLOT_CHESTPLATE, EMPTY_ARMOR_SLOT_HELMET }; @@ -178,20 +178,19 @@ public abstract class GuiMixin { int effectBaseY = baseY + slots.length * (itemHeight + spacing) + spacing; // start below armor // Render all active effect icons down the right side below armor - LocalPlayer player = this.minecraft.player; - if (player != null && player.getActiveEffects() != null && !player.getActiveEffects().isEmpty()) { + if (ClientSyncController.syncData.effects != null && !ClientSyncController.syncData.effects.isEmpty()) { int effectIndex = 0; - for (var effectInstance : player.getActiveEffects()) { + for (var effectInstance : ClientSyncController.syncData.effects) { int y = effectBaseY + effectIndex * (itemWidth + spacing); // Draw vanilla effect background guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, EFFECT_BACKGROUND_SPRITE, baseX, y, itemWidth, itemHeight); - ResourceLocation effectIcon = Gui.getMobEffectSprite(effectInstance.getEffect()); + Identifier effectIcon = GuiMixin.getEffectIcon(effectInstance.effectKey); guiGraphics.blitSprite(RenderPipelines.GUI_TEXTURED, effectIcon, baseX + 2, y + 2, itemWidth - 4, itemHeight - 4); // Draw effect level as a small white number on the top right of the icon - int level = effectInstance.getAmplifier() + 1; + int level = effectInstance.amplifier + 1; String levelText = String.valueOf(level); int levelTextWidth = this.minecraft.font.width(levelText); int levelTextX = baseX + itemWidth - (int)(levelTextWidth * 0.4F) - 3; // right-align inside top-right corner @@ -202,7 +201,7 @@ public abstract class GuiMixin { guiGraphics.pose().popMatrix(); // Draw duration bar (1px wide) to the left of the effect icon, color changes with percent - int duration = effectInstance.getDuration(); + int duration = effectInstance.duration; int maxDuration = 3600; // 3 minutes, adjust as needed float percent = maxDuration > 0 ? (duration / (float)maxDuration) : 1.0F; int maxBarHeight = itemHeight - 2; @@ -365,8 +364,8 @@ public abstract class GuiMixin { } return instance; } - // Map EffectType to vanilla effect icon ResourceLocation - private static ResourceLocation getEffectIcon(String effectKey) { + // Map EffectType to vanilla effect icon Identifier + private static Identifier getEffectIcon(String effectKey) { // If effectKey contains a namespace (e.g., minecraft:nausea), strip it String key = effectKey; int colonIdx = key.indexOf(":"); @@ -375,7 +374,7 @@ private static ResourceLocation getEffectIcon(String effectKey) { } // Vanilla effect icons are in the GUI atlas as effect/ // The effectKey should be lowercase, matching the registry name - return ResourceLocation.withDefaultNamespace("mob_effect/" + key.toLowerCase()); + return Identifier.withDefaultNamespace("mob_effect/" + key.toLowerCase()); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java index 3872649..f8a7663 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererAccessor.java @@ -4,6 +4,7 @@ import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.item.ItemModelResolver; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; @@ -35,6 +36,9 @@ void invokeRenderArmWithItem(AbstractClientPlayer player, float partialTick, flo @Accessor ItemStack getOffHandItem(); + @Accessor + ItemModelResolver getItemModelResolver(); + @Invoker("isChargedCrossbow") static boolean invokeIsChargedCrossbow(ItemStack stack) { throw new AssertionError(); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java index 04e4c23..96e0817 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/ItemInHandRendererMixin.java @@ -55,8 +55,10 @@ public abstract class ItemInHandRendererMixin { if (this.spectated == spectated) { float f = spectated.getAttackStrengthScale(1.0F); - this.mainHandHeight += Mth.clamp((this.mainHandItem == mainHandItem ? f * f * f : 0.0F) - this.mainHandHeight, -0.4F, 0.4F); - this.offHandHeight += Mth.clamp((float) (this.offHandItem == offHandItem ? 1 : 0) - this.offHandHeight, -0.4F, 0.4F); + float g = this.mainHandItem != mainHandItem ? 0.0F : f * f * f; + float h = this.offHandItem != offHandItem ? 0.0F : 1.0F; + this.mainHandHeight = this.mainHandHeight + Mth.clamp(g - this.mainHandHeight, -0.4F, 0.4F); + this.offHandHeight = this.offHandHeight + Mth.clamp(h - this.offHandHeight, -0.4F, 0.4F); if (this.mainHandHeight < 0.1F) { this.mainHandItem = mainHandItem; diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java new file mode 100644 index 0000000..e318705 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityAccessor.java @@ -0,0 +1,16 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import net.minecraft.core.Holder; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(LivingEntity.class) +public interface LivingEntityAccessor { + @Accessor("activeEffects") + Map, MobEffectInstance> spectatorplus$getActiveEffects(); +} diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java index 2da419c..44a7c14 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LivingEntityMixin.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.client.mixin; +import com.hpfxd.spectatorplus.fabric.client.util.EffectUtil; import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; @@ -7,23 +8,20 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.phys.HitResult; - -import java.util.Collection; - -import org.jetbrains.annotations.Nullable; 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; @Mixin(LivingEntity.class) public abstract class LivingEntityMixin extends Entity { @@ -52,6 +50,20 @@ private boolean isLookingAtBlock() { return false; } - return ((GameRendererAccessor) Minecraft.getInstance().gameRenderer).invokePick(this, player.blockInteractionRange(), player.entityInteractionRange(), 1F).getType() == HitResult.Type.BLOCK; + var spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); + var blockRange = spectated == null ? player.blockInteractionRange() : spectated.blockInteractionRange(); + var entityRange = spectated == null ? player.entityInteractionRange() : spectated.entityInteractionRange(); + return LocalPlayerAccessor.invokePick(this, blockRange, entityRange, 1F).getType() == HitResult.Type.BLOCK; } + + @Redirect(method = {"hasEffect", "getEffect", "getActiveEffects", "tickEffects"}, + at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/LivingEntity;activeEffects:Ljava/util/Map;")) + private Map, MobEffectInstance> spectatorplus$redirectActiveEffects(LivingEntity instance) { + // 只对玩家且满足条件时才重定向 + if (instance.level().isClientSide() && instance instanceof Player && EffectUtil.shouldUseSpectatorData()) { + return EffectUtil.getActiveEffectsMap(); + } + return ((LivingEntityAccessor) instance).spectatorplus$getActiveEffects(); + } + } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java new file mode 100644 index 0000000..3667748 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerAccessor.java @@ -0,0 +1,15 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(LocalPlayer.class) +public interface LocalPlayerAccessor { + @Invoker + static HitResult invokePick(Entity cameraEntity, double blockRange, double entityRange, float partialTick) { + throw new AssertionError(); + } +} \ No newline at end of file diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java new file mode 100644 index 0000000..c5d58ac --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/LocalPlayerMixin.java @@ -0,0 +1,32 @@ +package com.hpfxd.spectatorplus.fabric.client.mixin; + +import com.hpfxd.spectatorplus.fabric.client.util.SpecUtil; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.LocalPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(LocalPlayer.class) +public class LocalPlayerMixin { + @ModifyExpressionValue(method = "raycastHitResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;blockInteractionRange()D")) + private double spectatorplus$modifyBlockInteractionRange(double original) { + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); + if (spectated != null) { + return spectated.blockInteractionRange(); + } + + return original; + } + + @ModifyExpressionValue(method = "raycastHitResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;entityInteractionRange()D")) + private double spectatorplus$modifyEntityInteractionRange(double original) { + final AbstractClientPlayer spectated = SpecUtil.getCameraPlayer(Minecraft.getInstance()); + if (spectated != null) { + return spectated.entityInteractionRange(); + } + + return original; + } +} \ No newline at end of file diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java index 6c1d158..e0cdcc2 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/MinecraftMixin.java @@ -23,7 +23,7 @@ public abstract class MinecraftMixin { @Inject(method = "setCameraEntity(Lnet/minecraft/world/entity/Entity;)V", at = @At(value = "TAIL")) private void spectatorplus$resetSyncDataOnCameraSwitch(Entity viewingEntity, CallbackInfo ci) { - if (ClientSyncController.syncData != null && !ClientSyncController.syncData.playerId.equals(viewingEntity.getUUID())) { + if (ClientSyncController.syncData != null && (viewingEntity == null || !ClientSyncController.syncData.playerId.equals(viewingEntity.getUUID()))) { ClientSyncController.setSyncData(null); } } diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java index 72700ec..248ceb6 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/mixin/screen/AbstractContainerScreenMixin.java @@ -8,7 +8,7 @@ import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ClickType; @@ -46,8 +46,8 @@ public abstract class AbstractContainerScreenMixin { @Unique private boolean mouseMoved; - @Shadow @Final private static ResourceLocation SLOT_HIGHLIGHT_BACK_SPRITE; - @Shadow @Final private static ResourceLocation SLOT_HIGHLIGHT_FRONT_SPRITE; + @Shadow @Final private static Identifier SLOT_HIGHLIGHT_BACK_SPRITE; + @Shadow @Final private static Identifier SLOT_HIGHLIGHT_FRONT_SPRITE; @Shadow protected abstract void renderFloatingItem(GuiGraphics guiGraphics, ItemStack stack, int x, int y, String text); @Shadow @Final protected AbstractContainerMenu menu; @Shadow @Nullable protected abstract Slot getHoveredSlot(double mouseX, double mouseY); diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java index de56cb7..468e659 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/sync/ClientSyncController.java @@ -1,6 +1,7 @@ package com.hpfxd.spectatorplus.fabric.client.sync; import com.hpfxd.spectatorplus.fabric.client.sync.screen.ScreenSyncController; +import com.hpfxd.spectatorplus.fabric.client.util.EffectUtil; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -11,7 +12,7 @@ import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.Holder; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; @@ -42,32 +43,8 @@ public static void init() { private static void handle(ClientboundEffectsSyncPacket packet, ClientPlayNetworking.Context context) { setSyncData(packet.playerId()); - syncData.effects = packet.effects(); // Now List - System.out.println("[SpectatorPlus] Synced effects: " + syncData.effects); - - // var client = Minecraft.getInstance(); - // if (client.player != null) { - // // Remove all current effects from the client player - // List> toRemove = new ArrayList<>(client.player.getActiveEffectsMap().keySet()); - // for (Holder effect : toRemove) { - // System.out.println("[SpectatorPlus] Removing effect: " + BuiltInRegistries.MOB_EFFECT.getKey(effect.value())); - // client.player.removeEffect(effect); - // } - // // Add all synced effects to the client player - // for (SyncedEffect synced : syncData.effects) { - // System.out.println("[SpectatorPlus] Syncing effect: " + synced.effectKey + " duration=" + synced.duration + " amplifier=" + synced.amplifier); - // java.util.Optional> optHolder = BuiltInRegistries.MOB_EFFECT.get(ResourceLocation.tryParse(synced.effectKey)); - // if (optHolder.isPresent()) { - // MobEffect effect = optHolder.get().value(); - // Holder holder = Holder.direct(effect); - // MobEffectInstance instance = new MobEffectInstance(holder, synced.duration, synced.amplifier); - // client.player.forceAddEffect(instance, client.player); - // System.out.println("[SpectatorPlus] Added effect: " + synced.effectKey); - // } else { - // System.out.println("[SpectatorPlus] Effect not found in registry: " + synced.effectKey); - // } - // } - // } + syncData.effects = packet.effects(); + EffectUtil.updateEffectInstances(packet.effects()); } private static void handle(ClientboundExperienceSyncPacket packet, ClientPlayNetworking.Context context) { diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java new file mode 100644 index 0000000..3119807 --- /dev/null +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/EffectUtil.java @@ -0,0 +1,60 @@ +package com.hpfxd.spectatorplus.fabric.client.util; + + +import com.hpfxd.spectatorplus.fabric.client.sync.ClientSyncController; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; +import net.minecraft.client.Minecraft; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; + +import java.util.*; + +public class EffectUtil { + private static final Map, MobEffectInstance> activeEffects = new HashMap<>(); + + public static void updateEffectInstances(List effects) { + // 收集新的效果 + Set> newEffects = new HashSet<>(); + + for (SyncedEffect syncedEffect : effects) { + Holder effect = BuiltInRegistries.MOB_EFFECT.get(Identifier.parse(syncedEffect.effectKey)) + .orElseThrow(() -> new IllegalArgumentException("Unknown effect: " + syncedEffect.effectKey)); + newEffects.add(effect); + } + + // 移除不再存在的效果 + activeEffects.entrySet().removeIf(entry -> !newEffects.contains(entry.getKey())); + + // 添加新效果(保持现有实例的BlendState) + for (SyncedEffect syncedEffect : effects) { + Holder effect = BuiltInRegistries.MOB_EFFECT.get(Identifier.parse(syncedEffect.effectKey)) + .orElseThrow(() -> new IllegalArgumentException("Unknown effect: " + syncedEffect.effectKey)); + + if (!activeEffects.containsKey(effect)) { + MobEffectInstance instance = new MobEffectInstance(effect, syncedEffect.duration, + syncedEffect.amplifier, false, true, true); + activeEffects.put(effect, instance); + } + } + } + + public static boolean hasValidSyncData() { + return ClientSyncController.syncData != null && + ClientSyncController.syncData.effects != null; + } + + public static boolean shouldUseSpectatorData() { + Minecraft mc = Minecraft.getInstance(); + return mc.player != null && + SpecUtil.getCameraPlayer(mc) != null && + hasValidSyncData(); + } + + // 直接返回原版格式的activeEffects + public static Map, MobEffectInstance> getActiveEffectsMap() { + return activeEffects; + } +} \ No newline at end of file diff --git a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java index 3c7fbfc..4192260 100644 --- a/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java +++ b/fabric/src/client/java/com/hpfxd/spectatorplus/fabric/client/util/SpecUtil.java @@ -21,17 +21,17 @@ public static AbstractClientPlayer getCameraPlayer(Minecraft minecraft) { return null; } - /** - * Returns the synced MobEffectInstance for the given entity and effect, or null if not found. - */ - public static net.minecraft.world.effect.MobEffectInstance getSyncedEffect(LivingEntity entity, Holder effect) { - if (ClientSyncController.syncData == null || ClientSyncController.syncData.effects == null) return null; - String queryKey = effect.unwrapKey().get().location().toString(); - for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : ClientSyncController.syncData.effects) { - if (queryKey.equals(synced.effectKey)) { - return new net.minecraft.world.effect.MobEffectInstance(effect, synced.duration, synced.amplifier); - } - } - return null; - } + // /** + // * Returns the synced MobEffectInstance for the given entity and effect, or null if not found. + // */ + // public static net.minecraft.world.effect.MobEffectInstance getSyncedEffect(LivingEntity entity, Holder effect) { + // if (ClientSyncController.syncData == null || ClientSyncController.syncData.effects == null) return null; + // String queryKey = effect.unwrapKey().get().identifier().toString(); + // for (com.hpfxd.spectatorplus.fabric.sync.SyncedEffect synced : ClientSyncController.syncData.effects) { + // if (queryKey.equals(synced.effectKey)) { + // return new net.minecraft.world.effect.MobEffectInstance(effect, synced.duration, synced.amplifier); + // } + // } + // return null; + // } } diff --git a/fabric/src/client/resources/spectatorplus.client.mixins.json b/fabric/src/client/resources/spectatorplus.client.mixins.json index 59c3082..d302c21 100644 --- a/fabric/src/client/resources/spectatorplus.client.mixins.json +++ b/fabric/src/client/resources/spectatorplus.client.mixins.json @@ -9,7 +9,6 @@ "EntityMixin", "EntityRendererMixin", "ExperienceBarRendererMixin", - "GameRendererAccessor", "GameRendererMixin", "GuiMixin", "InventoryAccessor", @@ -18,6 +17,8 @@ "LevelRendererAccessor", "LevelRendererMixin", "LivingEntityMixin", + "LocalPlayerAccessor", + "LocalPlayerMixin", "MinecraftMixin", "PlayerMenuItemAccessor", "PlayerMenuItemMixin", @@ -34,5 +35,8 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + "LivingEntityAccessor" + ] } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java new file mode 100644 index 0000000..4a263c5 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/LivingEntityMixin.java @@ -0,0 +1,40 @@ +package com.hpfxd.spectatorplus.fabric.mixin; + +import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; +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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collection; + +@Mixin(LivingEntity.class) +public class LivingEntityMixin { + + @Inject(method = "onEffectAdded", at = @At("TAIL")) + private void onEffectAdded(MobEffectInstance effectInstance, @Nullable Entity entity, CallbackInfo ci) { + if ((Object) this instanceof ServerPlayer player) { + EffectsSyncHandler.onEffectChanged(player); + } + } + + @Inject(method = "onEffectUpdated", at = @At("TAIL")) + private void onEffectUpdated(MobEffectInstance effectInstance, boolean forced, @Nullable Entity entity, CallbackInfo ci) { + if ((Object) this instanceof ServerPlayer player) { + EffectsSyncHandler.onEffectChanged(player); + } + } + + @Inject(method = "onEffectsRemoved", at = @At("TAIL")) + private void onEffectsRemoved(Collection effects, CallbackInfo ci) { + if ((Object) this instanceof ServerPlayer player) { + EffectsSyncHandler.onEffectChanged(player); + } + } +} \ No newline at end of file diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java index d03126d..15c7b1f 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/mixin/ServerPlayerMixin.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import com.hpfxd.spectatorplus.fabric.SpectatorMod; import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundExperienceSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundFoodSyncPacket; import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundHotbarSyncPacket; @@ -66,6 +67,7 @@ public ServerPlayerMixin(Level level, GameProfile gameProfile) { ServerSyncController.sendPacket(spectator, ClientboundFoodSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundHotbarSyncPacket.initializing(target)); ServerSyncController.sendPacket(spectator, ClientboundSelectedSlotSyncPacket.initializing(target)); + EffectsSyncHandler.onStartSpectating(spectator, target); // Send initial map data patch packet if the target has a map in inventory for (final ItemStack stack : target.getInventory().getNonEquipmentItems()) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java index 141c452..a1a7dfa 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/CustomPacketCodecs.java @@ -1,16 +1,10 @@ package com.hpfxd.spectatorplus.fabric.sync; +import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtAccounter; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtOps; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.item.ItemStack; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +import org.jetbrains.annotations.NotNull; public final class CustomPacketCodecs { private CustomPacketCodecs() { @@ -21,11 +15,7 @@ public static ItemStack[] readItems(RegistryFriendlyByteBuf buf) { final ItemStack[] items = new ItemStack[len]; for (int slot = 0; slot < len; slot++) { - if (buf.readBoolean()) { - final ItemStack stack = readItem(buf); - - items[slot] = stack; - } + items[slot] = buf.readBoolean() ? ItemStack.OPTIONAL_STREAM_CODEC.decode(buf) : null; } return items; @@ -36,53 +26,26 @@ public static void writeItems(RegistryFriendlyByteBuf buf, ItemStack[] items) { for (final ItemStack item : items) { buf.writeBoolean(item != null); - if (item != null) { - writeItem(buf, item); + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item); } } } public static ItemStack readItem(RegistryFriendlyByteBuf buf) { - final int len = buf.readInt(); - if (len == 0) { - return ItemStack.EMPTY; - } - try { - final byte[] in = new byte[len]; - buf.readBytes(in); - - final CompoundTag tag = NbtIo.readCompressed(new ByteArrayInputStream(in), NbtAccounter.unlimitedHeap()); - - var registryOps = buf.registryAccess().createSerializationContext(NbtOps.INSTANCE); - return ItemStack.CODEC.parse(registryOps, tag).resultOrPartial().orElse(ItemStack.EMPTY); - } catch (IOException e) { - throw new EncoderException(e); + return ItemStack.OPTIONAL_STREAM_CODEC.decode(buf); + } catch (Exception e) { + throw new DecoderException("Failed to read ItemStack", e); } } - public static void writeItem(RegistryFriendlyByteBuf buf, ItemStack item) { - if (item.isEmpty()) { - buf.writeInt(0); - return; - } - final byte[] bytes; + public static void writeItem(RegistryFriendlyByteBuf buf, @NotNull ItemStack item) { try { - final CompoundTag tag = new CompoundTag(); - var registryOps = buf.registryAccess().createSerializationContext(NbtOps.INSTANCE); - ItemStack.CODEC.encode(item, registryOps, tag).getOrThrow(); - - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - NbtIo.writeCompressed(tag, out); - - bytes = out.toByteArray(); - } catch (IOException e) { - throw new EncoderException(e); + ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, item); + } catch (Exception e) { + throw new EncoderException("Failed to write ItemStack", e); } - - buf.writeInt(bytes.length); - buf.writeBytes(bytes); } -} +} \ No newline at end of file diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java index a60b466..587735e 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/ServerSyncController.java @@ -1,5 +1,6 @@ package com.hpfxd.spectatorplus.fabric.sync; +import com.hpfxd.spectatorplus.fabric.sync.handler.EffectsSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.HotbarSyncHandler; import com.hpfxd.spectatorplus.fabric.sync.handler.ScreenSyncHandler; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; @@ -15,6 +16,7 @@ public static void init() { HotbarSyncHandler.init(); ScreenSyncHandler.init(); + EffectsSyncHandler.init(); } public static void sendPacket(ServerPlayer serverPlayer, ClientboundSyncPacket packet) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java new file mode 100644 index 0000000..ac878d4 --- /dev/null +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/handler/EffectsSyncHandler.java @@ -0,0 +1,79 @@ +package com.hpfxd.spectatorplus.fabric.sync.handler; + +import com.hpfxd.spectatorplus.fabric.sync.ServerSyncController; +import com.hpfxd.spectatorplus.fabric.sync.SyncedEffect; +import com.hpfxd.spectatorplus.fabric.sync.packet.ClientboundEffectsSyncPacket; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; + +import java.util.*; + +public class EffectsSyncHandler { + private static final Map> EFFECTS = new HashMap<>(); + + public static void init() { + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> EFFECTS.remove(handler.getPlayer().getUUID())); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> EFFECTS.clear()); + } + + // 当玩家开始旁观另一个玩家时调用 + public static void onStartSpectating(ServerPlayer spectator, ServerPlayer target) { + syncPlayerEffects(spectator, target); + } + + // 当玩家的药水效果改变时调用 + public static void onEffectChanged(ServerPlayer player) { + // 获取所有正在旁观此玩家的观察者 + for (ServerPlayer spectator : ServerSyncController.getSpectators(player)) { + syncPlayerEffects(spectator, player); + } + } + + private static void syncPlayerEffects(ServerPlayer spectator, ServerPlayer target) { + final List currentEffects = new ArrayList<>(); + for (MobEffectInstance effectInstance : target.getActiveEffects()) { + String effectKey = BuiltInRegistries.MOB_EFFECT.getKey(effectInstance.getEffect().value()).toString(); + currentEffects.add(new SyncedEffect( + effectKey, + effectInstance.getAmplifier(), + effectInstance.getDuration() + )); + } + + final List cachedEffects = EFFECTS.computeIfAbsent(spectator.getUUID(), k -> new ArrayList<>()); + + if (!effectsEqual(currentEffects, cachedEffects)) { + cachedEffects.clear(); + cachedEffects.addAll(currentEffects); + + // 使用ServerPlayNetworking发送包 + ClientboundEffectsSyncPacket packet = new ClientboundEffectsSyncPacket(target.getUUID(), new ArrayList<>(currentEffects)); + if (packet.canSend(spectator)) { + ServerPlayNetworking.send(spectator, packet); + } + } + } + + private static boolean effectsEqual(List list1, List list2) { + if (list1.size() != list2.size()) { + return false; + } + + final Map map1 = new HashMap<>(); + final Map map2 = new HashMap<>(); + + for (SyncedEffect effect : list1) { + map1.put(effect.effectKey, effect); + } + + for (SyncedEffect effect : list2) { + map2.put(effect.effectKey, effect); + } + + return map1.equals(map2); + } +} \ No newline at end of file diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java index cc9e68c..b8d820f 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundEffectsSyncPacket.java @@ -6,7 +6,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -19,7 +19,7 @@ public record ClientboundEffectsSyncPacket( List effects ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundEffectsSyncPacket::write, ClientboundEffectsSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:effects_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:effects_sync")); private static final String PERMISSION = "spectatorplus.sync.effects"; public ClientboundEffectsSyncPacket(FriendlyByteBuf buf) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java index f866588..2e513fd 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundExperienceSyncPacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public record ClientboundExperienceSyncPacket( int level ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundExperienceSyncPacket::write, ClientboundExperienceSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:experience_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:experience_sync")); private static final String PERMISSION = "spectatorplus.sync.experience"; public static ClientboundExperienceSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java index ec16535..f6e8327 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundFoodSyncPacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -17,7 +17,7 @@ public record ClientboundFoodSyncPacket( float saturation ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundFoodSyncPacket::write, ClientboundFoodSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:food_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:food_sync")); private static final String PERMISSION = "spectatorplus.sync.food"; public static ClientboundFoodSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java index 533410f..e39b7d0 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundHotbarSyncPacket.java @@ -6,7 +6,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public record ClientboundHotbarSyncPacket( ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundHotbarSyncPacket::write, ClientboundHotbarSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:hotbar_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:hotbar_sync")); static final String PERMISSION = "spectatorplus.sync.hotbar"; public static ClientboundHotbarSyncPacket initializing(ServerPlayer target) { diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java index 9741792..88a0aa0 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundInventorySyncPacket.java @@ -6,7 +6,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -23,7 +23,7 @@ public record ClientboundInventorySyncPacket( ItemStack[] items ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundInventorySyncPacket::write, ClientboundInventorySyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:inventory_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:inventory_sync")); public static final int ITEMS_LENGTH = 4 * 9 + 4 + 1; // 36 main + 4 armor + 1 offhand = 41 private static final String PERMISSION = "spectatorplus.sync.inventory"; diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java index 433b518..157a972 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenCursorSyncPacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public record ClientboundScreenCursorSyncPacket( int originSlot ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenCursorSyncPacket::write, ClientboundScreenCursorSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:screen_cursor_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:screen_cursor_sync")); public ClientboundScreenCursorSyncPacket(RegistryFriendlyByteBuf buf) { this(buf.readUUID(), CustomPacketCodecs.readItem(buf), buf.readByte()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java index ba92dde..cb0464d 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundScreenSyncPacket.java @@ -4,7 +4,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -15,7 +15,7 @@ public record ClientboundScreenSyncPacket( int flags ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundScreenSyncPacket::write, ClientboundScreenSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:screen_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:screen_sync")); public ClientboundScreenSyncPacket(FriendlyByteBuf buf) { this(buf.readUUID(), buf.readByte()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java index 756a9fa..2999fa2 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ClientboundSelectedSlotSyncPacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import org.jetbrains.annotations.NotNull; @@ -18,7 +18,7 @@ public record ClientboundSelectedSlotSyncPacket( int selectedSlot ) implements ClientboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundSelectedSlotSyncPacket::write, ClientboundSelectedSlotSyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:selected_slot_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:selected_slot_sync")); public static ClientboundSelectedSlotSyncPacket initializing(ServerPlayer target) { return new ClientboundSelectedSlotSyncPacket(target.getUUID(), target.getInventory().getSelectedSlot()); diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java index e00b7d6..e6aea33 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundOpenedInventorySyncPacket.java @@ -4,11 +4,11 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; public final class ServerboundOpenedInventorySyncPacket implements ServerboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundOpenedInventorySyncPacket::write, ServerboundOpenedInventorySyncPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:opened_inventory_sync")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:opened_inventory_sync")); public ServerboundOpenedInventorySyncPacket() { } diff --git a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java index f9f5c7e..562d9d8 100644 --- a/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java +++ b/fabric/src/main/java/com/hpfxd/spectatorplus/fabric/sync/packet/ServerboundRequestInventoryOpenPacket.java @@ -4,7 +4,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.jetbrains.annotations.NotNull; import java.util.UUID; @@ -13,7 +13,7 @@ public record ServerboundRequestInventoryOpenPacket( UUID playerId ) implements ServerboundSyncPacket { public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundRequestInventoryOpenPacket::write, ServerboundRequestInventoryOpenPacket::new); - public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.parse("spectatorplus:request_inventory_open")); + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(Identifier.parse("spectatorplus:request_inventory_open")); public ServerboundRequestInventoryOpenPacket(FriendlyByteBuf buf) { this(buf.readUUID()); diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 30eadde..502fe31 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -51,7 +51,7 @@ ], "depends": { "fabricloader": ">=0.15.0", - "minecraft": "~1.21.10", + "minecraft": "~1.21.11", "java": ">=21", "fabric-api": "*", "fabric-permissions-api-v0": "*" diff --git a/fabric/src/main/resources/spectatorplus.mixins.json b/fabric/src/main/resources/spectatorplus.mixins.json index 3499f2b..780cf8a 100644 --- a/fabric/src/main/resources/spectatorplus.mixins.json +++ b/fabric/src/main/resources/spectatorplus.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_21", "mixins": [ "ChunkMapAccessor", + "LivingEntityMixin", "ServerGamePacketListenerImplMixin", "ServerPlayerMixin", "TrackedEntityMixin" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/paper/build.gradle.kts b/paper/build.gradle.kts index d068769..0ce70fe 100644 --- a/paper/build.gradle.kts +++ b/paper/build.gradle.kts @@ -32,22 +32,17 @@ tasks { } shadowJar { - archiveVersion = getByName("jar").archiveVersion - archiveClassifier.set("") - from("../LICENSE") - - listOf( - "xyz.jpenilla.reflectionremapper", - "net.fabricmc.mappingio", - ).forEach { relocate(it, "com.hpfxd.spectatorplus.paper.libs.$it") } + enabled = false } jar { - enabled = false // only output shadow jar + enabled = true + archiveClassifier.set("") + from("../LICENSE") } runServer { - minecraftVersion("1.21.10") + minecraftVersion("1.21.11") } named("build") { diff --git a/paper/gradle.properties b/paper/gradle.properties index 9ada3d7..67ca6fa 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,2 +1,2 @@ -paper_version=1.21.7-R0.1-SNAPSHOT +paper_version=1.21.11-R0.1-SNAPSHOT reflection_remapper_version=0.1.3 \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index af71930..ad01527 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,8 +13,8 @@ plugins { rootProject.name = "spectatorplus" includeBuild("build-logic") -this.setupSubproject("paper") -this.setupSubproject("fabric") +setupSubproject("paper") +setupSubproject("fabric") fun setupSubproject(moduleName: String) { val name = "spectatorplus-$moduleName"