Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

[Version 1.18.2, SOLVED] Change rendered mob's model under certain conditions


LeeCrafts
 Share

Recommended Posts

Posted (edited)

Hello,

I am trying to replace every rendered mob's model (not the mob itself) with the model of a custom entity, under certain conditions. This is how it would work:

@SubscribeEvent
public static void replaceMobModel(RenderLivingEvent.Pre<LivingEntity, EntityModel<LivingEntity>> event) {
    if (condition A is reached) {
        // replace every rendered mob's model with the model of a custom entity
    }
    // if condition A is not met, then every rendered mob's model reverts back to normal (e.g. villagers start looking like villagers again)
}

I was looking at this forum (https://forums.minecraftforge.net/topic/75986-114-how-to-change-rendered-entity/) and found out that I need to use EntityRenderManager#renderEntity. But since that is from a previous version, I assume it would be EntityRenderDispatcher#render. Here's what I tried to do so far:

@SubscribeEvent
public static void replaceMobModel(RenderLivingEvent.Pre<LivingEntity, EntityModel<LivingEntity>> event) {
    LocalPlayer localPlayer = Minecraft.getInstance().player;
    if (localPlayer != null && event.getEntity() instanceof Mob mob) {

        // I'll set up the conditions later once I get this working

        event.setCanceled(true);
        CustomEntity customEntity = ModEntities.CUSTOM_ENTITY.get().create(localPlayer.clientLevel);
        if (customEntity != null) {
            customEntity.setXRot(mob.getXRot());
            customEntity.setYRot(mob.getYRot());
            Minecraft.getInstance().getEntityRenderDispatcher().render(
                    customEntity,
                    mob.getX(),
                    mob.getY(),
                    mob.getZ(),
                    mob.getViewYRot(event.getPartialTick()),
                    event.getPartialTick(),
                    event.getPoseStack(),
                    event.getMultiBufferSource(),
                    event.getPackedLight()
            );
        }
    }
}

This results in a stack overflow (EntityRenderDispatcher#render seems to cause RenderLivingEvent to fire, causing an infinite recursion). Is this even the right thing to do? Or do I have to go to my CustomEntity renderer class and override render()? If that's the case, I don't know what I should put in that method.

Edited by LeeCrafts
Link to comment
Share on other sites

  • LeeCrafts changed the title to [Version 1.18.2] Change rendered mob's model under certain conditions

You have to keep track that you are currently calling back into render and then have your event handler bail out early. Otherwise your event handler will fire on your render call, too and result in an infinite recursion. You can just use a private static boolean for this that you reset in a try-finally block.

Note also that you should definitely not be creating new entity instances every time you render, this happens once every frame for every entity.

  • Thanks 1
Link to comment
Share on other sites

Posted (edited)

Although I am not exactly sure how a try-finally block would help (i.e. what code I should run even if a runtime exception is thrown), I have made some progress. The custom mob renders, but it does not rotate--even when I apply rotations to it before calling render().

private static boolean renderingCustomEntity = false;
private static CustomEntity customEntity;

private static void initializeCustomEntityIfNull(LocalPlayer localPlayer) {
    if (customEntity == null) {
        customEntity = ModEntities.CUSTOM_ENTITY.get().create(localPlayer.clientLevel);
    }
}

@SubscribeEvent
public static void replaceMobModel(RenderLivingEvent.Pre<LivingEntity, EntityModel<LivingEntity>> event) {
    LocalPlayer localPlayer = Minecraft.getInstance().player;
    if (localPlayer != null && event.getEntity() instanceof Mob mob) {
        try {
            if (!renderingCustomEntity) {
                renderingCustomEntity = true;
                event.setCanceled(true);
                initializeCustomEntityIfNull(localPlayer);

                // I try to apply rotations to the custom entity before it rendered, but it still does not rotate at all.
                customEntity.setYRot(mob.getYRot());
                customEntity.setYHeadRot(mob.getYHeadRot());

                customEntity.setPose(mob.getPose());

                // For some reason, the custom mob did not render when I called EntityRenderDispatcher#render.
                // So I called render() from EntityRenderDispatcher#getRenderer instead.
                Minecraft.getInstance().getEntityRenderDispatcher().getRenderer(customEntity).render(
                        customEntity,
                        customEntity.getYRot(),
                        event.getPartialTick(),
                        event.getPoseStack(),
                        event.getMultiBufferSource(),
                        event.getPackedLight()
                );
            }
        } finally {
            renderingCustomEntity = false;
        }
    }
}

What would I be missing? Thank you for the help.

Edited by LeeCrafts
Link to comment
Share on other sites

3 hours ago, LeeCrafts said:

(i.e. what code I should run even if a runtime exception is thrown

It is good practice to do this, because if the inner renderer throws an exception your static field will remain set to true and can never go back to false. The try-finally block should also be inside the if statement that checks your static boolean.

3 hours ago, LeeCrafts said:
private static void initializeCustomEntityIfNull(LocalPlayer localPlayer) {
    if (customEntity == null) {
        customEntity = ModEntities.CUSTOM_ENTITY.get().create(localPlayer.clientLevel);
    }
}

This will create a memory leak if the level changes. You have to unload the entity with the level and also change the entity if the level changes.

As for your rotations: I would use the debugger to check what setupRotations in the renderer does and why it doesn't work properly. You can use a conditional breakpoint that only triggers if your static boolean field is true to only catch setupRotation calls that happen during the nested render.

Link to comment
Share on other sites

Posted (edited)

Got the rotation working. I had to look at LivingEntityRenderer#render and setupRotations a little more closely, and I realized that I needed to change the custom entity's yBodyRot and yBodyRotO

private static boolean renderingCustomEntity = false;
private static CustomEntity customEntity;

private static void refreshCustomEntity(LocalPlayer localPlayer, LivingEntity livingEntity) {
    // If the level changes, remove the custom entity by assigning its reference to a new custom entity.
    // The previous custom entity will eventually be garbage collected.
    if (customEntity == null || customEntity.level != livingEntity.level) {
        customEntity = ModEntities.CUSTOM_ENTITY.get().create(localPlayer.clientLevel);
    }
}

@SubscribeEvent
public static void replaceMobModel(RenderLivingEvent.Pre<LivingEntity, EntityModel<LivingEntity>> event) {
    LocalPlayer localPlayer = Minecraft.getInstance().player;
    if (localPlayer != null && event.getEntity() instanceof Mob mob) {
        if (/* put your own condition here */) {
            if (!renderingCustomEntity) {
                try {
                    renderingCustomEntity = true;
                    event.setCanceled(true);
                    refreshCustomEntity(localPlayer, mob);

                    // (truth be told, my custom entity does not have a "head", so these lines aren't needed)
                    // customEntity.setYHeadRot(mob.getYHeadRot());
                    // customEntity.yHeadRotO = mob.yHeadRotO;

                    customEntity.setYBodyRot(mob.yBodyRot);
                    customEntity.yBodyRotO = mob.yBodyRotO;
                    customEntity.setPose(mob.getPose());
                    Minecraft.getInstance().getEntityRenderDispatcher().getRenderer(customEntity).render(
                            customEntity,
                            customEntity.yBodyRot,
                            event.getPartialTick(),
                            event.getPoseStack(),
                            event.getMultiBufferSource(),
                            event.getPackedLight()
                    );
                } finally {
                    renderingCustomEntity = false;
                }
            }
        }
    }
}

 

On 5/25/2022 at 2:13 AM, diesieben07 said:

This will create a memory leak if the level changes. You have to unload the entity with the level and also change the entity if the level changes.

As shown in my code, if the mob whose model is replaced moves to a different dimension, I unload the custom entity with Entity#discard and then reinitialize it on the next tick. I wonder if I also have to unload the custom entity when the mob it is "replacing" dies.

^ Edit: I have changed the code due to @diesieben07's most recent comment.

Edited by LeeCrafts
i changed it a bunch of times, but hopefully this is the last edit lol
Link to comment
Share on other sites

Calling discard on an entity that is never even present in the world does nothing. Your renderer will still hold the reference to the entity and therefor prevent it from being garbage collected.

What you should do is check that the level is still the same (use an == comparison on the level instance, do not check the dimension). If it has changed, remove the old entity.

  • Thanks 1
Link to comment
Share on other sites

  • LeeCrafts changed the title to [Version 1.18.2, SOLVED] Change rendered mob's model under certain conditions

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.