From efbf4aefa635c757818abc852a4d4dc13a05a851 Mon Sep 17 00:00:00 2001 From: norbiros Date: Sun, 16 Nov 2025 15:11:10 +0100 Subject: [PATCH] feat: support overriding skins of mannequins --- .../ui/windows/SelectedEntityPopup.java | 2 + .../mixin/visuals/MixinClientMannequin.java | 51 +++++++++++++++++++ src/main/resources/flashback.mixins.json | 1 + 3 files changed, 54 insertions(+) create mode 100644 src/main/java/com/moulberry/flashback/mixin/visuals/MixinClientMannequin.java diff --git a/src/main/java/com/moulberry/flashback/editor/ui/windows/SelectedEntityPopup.java b/src/main/java/com/moulberry/flashback/editor/ui/windows/SelectedEntityPopup.java index 5f2dbb5f..e1850f2e 100644 --- a/src/main/java/com/moulberry/flashback/editor/ui/windows/SelectedEntityPopup.java +++ b/src/main/java/com/moulberry/flashback/editor/ui/windows/SelectedEntityPopup.java @@ -174,7 +174,9 @@ public static void render(Entity entity, EditorState editorState) { } showGlowingDropdown(entity, editorState); + } + if (entity instanceof AbstractClientPlayer || entity instanceof ClientMannequin) { ImGuiHelper.separatorWithText(I18n.get("flashback.change_skin_and_cape")); ImGui.setNextItemWidth(320); ImGui.inputTextWithHint("##SetSkinInput", "e.g. d0e05de7-6067-454d-beae-c6d19d886191", changeSkinInput); diff --git a/src/main/java/com/moulberry/flashback/mixin/visuals/MixinClientMannequin.java b/src/main/java/com/moulberry/flashback/mixin/visuals/MixinClientMannequin.java new file mode 100644 index 00000000..a66ffc1f --- /dev/null +++ b/src/main/java/com/moulberry/flashback/mixin/visuals/MixinClientMannequin.java @@ -0,0 +1,51 @@ +package com.moulberry.flashback.mixin.visuals; + +import com.mojang.authlib.GameProfile; +import com.moulberry.flashback.FilePlayerSkin; +import com.moulberry.flashback.state.EditorState; +import com.moulberry.flashback.state.EditorStateManager; +import net.minecraft.client.entity.ClientMannequin; +import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.decoration.Mannequin; +import net.minecraft.world.entity.player.PlayerSkin; +import net.minecraft.world.level.Level; +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.callback.CallbackInfoReturnable; + +@Mixin(ClientMannequin.class) +public abstract class MixinClientMannequin extends LivingEntity { + + @Unique + private PlayerInfo skinOverridePlayerInfo = null; + + public MixinClientMannequin(EntityType entityType, Level level) { + super(entityType, level); + } + + @Inject(method = "getSkin", at = @At("HEAD"), cancellable = true, require = 0) + public void getSkin(CallbackInfoReturnable cir) { + EditorState editorState = EditorStateManager.getCurrent(); + if (editorState != null) { + FilePlayerSkin filePlayerSkin = editorState.skinOverrideFromFile.get(this.getUUID()); + if (filePlayerSkin != null) { + cir.setReturnValue(filePlayerSkin.getSkin()); + return; + } + + GameProfile skinOverride = editorState.skinOverride.get(this.getUUID()); + if (skinOverride != null) { + if (skinOverridePlayerInfo == null || skinOverridePlayerInfo.getProfile() != skinOverride) { + skinOverridePlayerInfo = new PlayerInfo(skinOverride, false); + } + cir.setReturnValue(skinOverridePlayerInfo.getSkin()); + } + } + } +} diff --git a/src/main/resources/flashback.mixins.json b/src/main/resources/flashback.mixins.json index a314f33f..9f93f1d6 100644 --- a/src/main/resources/flashback.mixins.json +++ b/src/main/resources/flashback.mixins.json @@ -41,6 +41,7 @@ "visuals.MixinBossHealthOverlay", "visuals.MixinClientLevel", "visuals.MixinClientLevelData", + "visuals.MixinClientMannequin", "visuals.MixinEntityRenderDispatcher", "visuals.MixinEntityRenderer", "visuals.MixinExperienceBarRenderer",