From ccd5df815972502cf1ee4db5b2f8f89f363b1a92 Mon Sep 17 00:00:00 2001 From: brokestar233 <3765589194@qq.com> Date: Mon, 7 Sep 2026 04:10:42 +0800 Subject: [PATCH] fix: hide ysm head in first person on 26.1 without breaking mod captures Ysm builds its render data from render states and checks FirstPersonAPI#isRenderingPlayer while evaluating its animation, which on 26.1 happens lazily during the submit of the state instead of during the synchronous render. The flag was only raised around firstperson's own extraction, so the evaluation always ran with it down and the ysm head stayed visible. - widen the flag while the camera entity render state is extracted (without marking those states), so ysm attaches its first person context on every extraction, including its own re-extractions - keep a submit window only for the state firstperson extracted itself; that evaluation runs with the flag up and bakes the model headless for the frame render, while evaluations triggered by other mods run with it down, explicitly resetting the head state and keeping their captures intact (realcamera bindings, model view screen, guis) - drive the render state marker and helmet removal via the un-widened flag so only states extracted by firstperson are affected Signed-off-by: brokestar233 <3765589194@qq.com> --- .../firstperson/FirstPersonModelCore.java | 22 +++++++ .../mixins/LivingEntityRendererMixin.java | 4 +- .../tr7zw/firstperson/mixins/PlayerMixin.java | 2 +- .../mixins/RenderDispatcherMixin.java | 58 ++++++++++++++++++- 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/tr7zw/firstperson/FirstPersonModelCore.java b/src/main/java/dev/tr7zw/firstperson/FirstPersonModelCore.java index d454f00..544e361 100644 --- a/src/main/java/dev/tr7zw/firstperson/FirstPersonModelCore.java +++ b/src/main/java/dev/tr7zw/firstperson/FirstPersonModelCore.java @@ -24,6 +24,15 @@ public abstract class FirstPersonModelCore extends FirstPersonBase { public static boolean enabled = true; @Deprecated public static boolean isRenderingPlayer = false; + /** + * True while the render state of the camera entity is being extracted and + * the mod is active. Widens {@link #isRenderingPlayer()} for mods that build + * their render data at extraction time (e.g. YSM), without marking the + * extracted states themselves, so captures of other mods (RealCamera, GUIs) + * evaluate with the flag down and keep rendering the full model with its + * head. + */ + public static boolean cameraEntityExtract = false; private CameraType lastCameraType = null; @Deprecated private int tickCounterWorkaround = 0; @@ -96,6 +105,19 @@ public void onTick() { tickCounterWorkaround++; } + @Override + public boolean isRenderingPlayer() { + return super.isRenderingPlayer() || cameraEntityExtract; + } + + /** + * The un-widened flag. Only true while FirstPerson itself extracts/renders + * the camera entity, not for third party extractions. + */ + public boolean isRenderingPlayerRaw() { + return super.isRenderingPlayer(); + } + @Override public void setRenderingPlayer(boolean isRenderingPlayer) { super.setRenderingPlayer(isRenderingPlayer); diff --git a/src/main/java/dev/tr7zw/firstperson/mixins/LivingEntityRendererMixin.java b/src/main/java/dev/tr7zw/firstperson/mixins/LivingEntityRendererMixin.java index 7c347ac..9a6b6c6 100644 --- a/src/main/java/dev/tr7zw/firstperson/mixins/LivingEntityRendererMixin.java +++ b/src/main/java/dev/tr7zw/firstperson/mixins/LivingEntityRendererMixin.java @@ -181,8 +181,10 @@ public void renderPostAnim(LivingEntity livingEntity, float f, float g, PoseStac @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V", at = @At("HEAD")) private void checkIfCameraEntity(LivingEntity livingEntity, LivingEntityRenderState livingEntityRenderState, float f, CallbackInfo ci) { + // Raw flag: only states extracted by FirstPerson itself are marked, so + // third party captures (RealCamera, GUIs) keep rendering the head. ((LivingEntityRenderStateAccess) livingEntityRenderState) - .setIsCameraEntity(FirstPersonModelCore.instance.isRenderingPlayer()); + .setIsCameraEntity(FirstPersonModelCore.instance.isRenderingPlayerRaw()); } //? } diff --git a/src/main/java/dev/tr7zw/firstperson/mixins/PlayerMixin.java b/src/main/java/dev/tr7zw/firstperson/mixins/PlayerMixin.java index 27a8a30..39b9b44 100644 --- a/src/main/java/dev/tr7zw/firstperson/mixins/PlayerMixin.java +++ b/src/main/java/dev/tr7zw/firstperson/mixins/PlayerMixin.java @@ -19,7 +19,7 @@ public class PlayerMixin { @Inject(method = "getItemBySlot", at = @At("HEAD"), cancellable = true) public void getItemBySlot(EquipmentSlot slot, CallbackInfoReturnable ci) { - if (FirstPersonModelCore.instance.isRenderingPlayer() && Minecraft.getInstance().isSameThread() + if (FirstPersonModelCore.instance.isRenderingPlayerRaw() && Minecraft.getInstance().isSameThread() && (Object) this instanceof Player player) { if (slot == EquipmentSlot.HEAD) { ci.setReturnValue(ItemStack.EMPTY); diff --git a/src/main/java/dev/tr7zw/firstperson/mixins/RenderDispatcherMixin.java b/src/main/java/dev/tr7zw/firstperson/mixins/RenderDispatcherMixin.java index 948218a..9bd9852 100644 --- a/src/main/java/dev/tr7zw/firstperson/mixins/RenderDispatcherMixin.java +++ b/src/main/java/dev/tr7zw/firstperson/mixins/RenderDispatcherMixin.java @@ -2,12 +2,16 @@ import com.mojang.blaze3d.vertex.*; import dev.tr7zw.firstperson.*; +import dev.tr7zw.firstperson.access.*; import lombok.*; import net.minecraft.client.*; import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.entity.*; //? if >= 1.21.2 import net.minecraft.client.renderer.entity.state.*; +//? if >= 26.1 { +import net.minecraft.client.renderer.state.level.*; +//? } import net.minecraft.world.level.*; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.*; @@ -33,8 +37,60 @@ public abstract class RenderDispatcherMixin { private static Minecraft fpmMcInstance = Minecraft.getInstance(); - //? if >= 1.21.9 { + //? if >= 26.1 { + private boolean fpmSubmittingCameraEntity = false; + + /** + * YSM (and potentially other mods) defer their animation evaluation to the + * submit of the render state and check {@link FirstPersonModelCore#isRenderingPlayer()} + * at that point to decide when to hide the head. Two windows keep the flag + * up for exactly those moments: + * - extraction of the camera entity: widens the flag (without marking the + * states themselves, so third party captures stay unflagged) which makes + * YSM attach its first person context and re-evaluate the head visibility + * on every submit, + * - the submit of the state FPM extracted (marked via isCameraEntity): the + * only evaluation that must see the flag up, hiding the head for the + * actual frame render. Evaluations triggered by other mods (RealCamera, + * GUIs) run with the flag down and keep the model headed. + */ + @Inject(method = "extractEntity(Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/entity/state/EntityRenderState;", + at = @At("HEAD"), expect = 0, require = 0) + private void fpmExtractStart(Entity entity, float f, CallbackInfoReturnable cir) { + Minecraft mc = Minecraft.getInstance(); + if (entity == mc.getCameraEntity() && mc.options.getCameraType() == CameraType.FIRST_PERSON + && FirstPersonModelCore.instance.getLogicHandler().shouldApplyThirdPerson(false)) { + FirstPersonModelCore.cameraEntityExtract = true; + } + } + + @Inject(method = "extractEntity(Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/entity/state/EntityRenderState;", + at = @At("TAIL"), expect = 0, require = 0) + private void fpmExtractEnd(Entity entity, float f, CallbackInfoReturnable cir) { + FirstPersonModelCore.cameraEntityExtract = false; + } + + @Inject(method = "submit(Lnet/minecraft/client/renderer/entity/state/EntityRenderState;Lnet/minecraft/client/renderer/state/level/CameraRenderState;DDDLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;)V", + at = @At("HEAD"), expect = 0, require = 0) + private void fpmSubmitStart(EntityRenderState renderState, CameraRenderState camera, double x, double y, double z, + PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CallbackInfo ci) { + if (renderState instanceof LivingEntityRenderState livingState + && ((LivingEntityRenderStateAccess) (Object) livingState).isCameraEntity()) { + fpmSubmittingCameraEntity = true; + FirstPersonModelCore.instance.setRenderingPlayer(true); + } + } + + @Inject(method = "submit(Lnet/minecraft/client/renderer/entity/state/EntityRenderState;Lnet/minecraft/client/renderer/state/level/CameraRenderState;DDDLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;)V", + at = @At("TAIL"), expect = 0, require = 0) + private void fpmSubmitEnd(EntityRenderState renderState, CameraRenderState camera, double x, double y, double z, + PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CallbackInfo ci) { + if (fpmSubmittingCameraEntity) { + fpmSubmittingCameraEntity = false; + FirstPersonModelCore.instance.setRenderingPlayer(false); + } + } //? } else if >= 1.21.3 { /*