fix: Re-implement head_model with the new model format

This commit is contained in:
Linnea Gräf
2025-05-01 13:25:13 +02:00
parent 0c3514b091
commit 36b21d0b04
6 changed files with 177 additions and 0 deletions

View File

@@ -26,3 +26,5 @@ accessible method net/minecraft/client/render/RenderPhase$Texture getId ()Ljava/
accessible field net/minecraft/client/render/RenderLayer$MultiPhase phases Lnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;
accessible field net/minecraft/client/render/RenderLayer$MultiPhaseParameters texture Lnet/minecraft/client/render/RenderPhase$TextureBase;
accessible field net/minecraft/client/network/ClientPlayerInteractionManager currentBreakingPos Lnet/minecraft/util/math/BlockPos;
mutable field net/minecraft/client/render/entity/state/LivingEntityRenderState headItemRenderState Lnet/minecraft/client/render/item/ItemRenderState;

View File

@@ -110,6 +110,10 @@ object CustomModelOverrideParser {
Firmament.identifier("predicates/legacy"),
PredicateModel.Unbaked.CODEC
)
ItemModelTypes.ID_MAPPER.put(
Firmament.identifier("head_model"),
HeadModelChooser.Unbaked.CODEC
)
}
}

View File

@@ -0,0 +1,90 @@
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonObject
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.client.item.ItemModelManager
import net.minecraft.client.render.item.ItemRenderState
import net.minecraft.client.render.item.model.BasicItemModel
import net.minecraft.client.render.item.model.ItemModel
import net.minecraft.client.render.item.model.ItemModelTypes
import net.minecraft.client.render.model.ResolvableModel
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.ModelTransformationMode
import net.minecraft.util.Identifier
object HeadModelChooser {
val IS_CHOOSING_HEAD_MODEL = ThreadLocal.withInitial { false }
interface HasExplicitHeadModelMarker {
fun markExplicitHead_Firmament()
fun isExplicitHeadModel_Firmament(): Boolean
companion object{
@JvmStatic
fun cast(state: ItemRenderState) = state as HasExplicitHeadModelMarker
}
}
data class Baked(val head: ItemModel, val regular: ItemModel) : ItemModel {
override fun update(
state: ItemRenderState,
stack: ItemStack?,
resolver: ItemModelManager?,
transformationMode: ModelTransformationMode?,
world: ClientWorld?,
user: LivingEntity?,
seed: Int
) {
val instance =
if (IS_CHOOSING_HEAD_MODEL.get()) {
HasExplicitHeadModelMarker.cast(state).markExplicitHead_Firmament()
head
} else {
regular
}
instance.update(state, stack, resolver, transformationMode, world, user, seed)
}
}
data class Unbaked(
val head: ItemModel.Unbaked,
val regular: ItemModel.Unbaked,
) : ItemModel.Unbaked {
override fun getCodec(): MapCodec<out ItemModel.Unbaked> {
return CODEC
}
override fun bake(context: ItemModel.BakeContext): ItemModel {
return Baked(
head.bake(context),
regular.bake(context)
)
}
override fun resolve(resolver: ResolvableModel.Resolver) {
head.resolve(resolver)
regular.resolve(resolver)
}
companion object {
@JvmStatic
fun fromLegacyJson(jsonObject: JsonObject, unbakedModel: ItemModel.Unbaked): ItemModel.Unbaked {
val model = jsonObject["firmament:head_model"] ?: return unbakedModel
val modelUrl = model.asJsonPrimitive.asString
val headModel = BasicItemModel.Unbaked(Identifier.of(modelUrl), listOf())
return Unbaked(headModel, unbakedModel)
}
val CODEC = RecordCodecBuilder.mapCodec {
it.group(
ItemModelTypes.CODEC.fieldOf("head")
.forGetter(Unbaked::head),
ItemModelTypes.CODEC.fieldOf("regular")
.forGetter(Unbaked::regular),
).apply(it, ::Unbaked)
}
}
}
}

View File

@@ -0,0 +1,28 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.HeadModelChooser;
import net.minecraft.client.render.item.ItemRenderState;
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;
@Mixin(ItemRenderState.class)
public class ItemRenderStateExtraInfo implements HeadModelChooser.HasExplicitHeadModelMarker {
boolean hasExplicitHead_firmament = false;
@Inject(method = "clear", at = @At("HEAD"))
private void clear(CallbackInfo ci) {
hasExplicitHead_firmament = false;
}
@Override
public void markExplicitHead_Firmament() {
hasExplicitHead_firmament = true;
}
@Override
public boolean isExplicitHeadModel_Firmament() {
return hasExplicitHead_firmament;
}
}

View File

@@ -0,0 +1,51 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.HeadModelChooser;
import net.minecraft.client.item.ItemModelManager;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.render.item.ItemRenderState;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ModelTransformationMode;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.CallbackInfo;
@Mixin(LivingEntityRenderer.class)
public class ReplaceHeadModel<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
@Shadow
@Final
protected ItemModelManager itemModelResolver;
@Unique
private ItemRenderState tempRenderState = new ItemRenderState();
@Inject(
method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V",
at = @At("TAIL")
)
private void replaceHeadModel(
T livingEntity, S livingEntityRenderState, float f, CallbackInfo ci
) {
var headItemStack = livingEntity.getEquippedStack(EquipmentSlot.HEAD);
HeadModelChooser.INSTANCE.getIS_CHOOSING_HEAD_MODEL().set(true);
tempRenderState.clear();
this.itemModelResolver.updateForLivingEntity(tempRenderState, headItemStack, ModelTransformationMode.HEAD, false, livingEntity);
HeadModelChooser.INSTANCE.getIS_CHOOSING_HEAD_MODEL().set(false);
if (HeadModelChooser.HasExplicitHeadModelMarker.cast(tempRenderState)
.isExplicitHeadModel_Firmament()) {
livingEntityRenderState.wearingSkullType = null;
var temp = livingEntityRenderState.headItemRenderState;
livingEntityRenderState.headItemRenderState = tempRenderState;
tempRenderState = temp;
}
}
}

View File

@@ -5,6 +5,7 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.Firmament;
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
import moe.nea.firmament.features.texturepack.HeadModelChooser;
import moe.nea.firmament.features.texturepack.PredicateModel;
import moe.nea.firmament.util.ErrorUtil;
import net.minecraft.client.item.ItemAsset;
@@ -61,6 +62,7 @@ public class SupplyFakeModelPatch {
try (var is = resource.getInputStream()) {
var jsonObject = Firmament.INSTANCE.getGson().fromJson(new InputStreamReader(is), JsonObject.class);
unbakedModel = PredicateModel.Unbaked.fromLegacyJson(jsonObject, unbakedModel);
unbakedModel = HeadModelChooser.Unbaked.fromLegacyJson(jsonObject, unbakedModel);
} catch (Exception e) {
ErrorUtil.INSTANCE.softError("Could not create resource for fake model supplication: " + model.getKey(), e);
}