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 { /*