Add custom head models

This commit is contained in:
Linnea Gräf
2024-07-11 01:58:23 +02:00
parent 67cc7c22ac
commit feca9c937b
11 changed files with 328 additions and 0 deletions

View File

@@ -24,6 +24,22 @@ replacement texture at `firmskyblock:textures/placedskulls/<thathash>.png`. Keep
the texture with another skin texture, meaning that skin texture has it's own hash. Do not mix those up, you need to use
the hash of the old skin.
## Armor Skull Models
You can replace the models of skull items (or other items) by specifying the `firmament:head_model` property on your
model. Note that this is resolved *after* all [overrides](#predicates) and further predicates are not resolved on the
head model.
```json5
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "firmskyblock:item/regular_texture"
},
"firmament:head_model": "minecraft:block/diamond_block" // when wearing on the head render a diamond block instead (can be any item model, including custom ones)
}
```
## Predicates
Firmament adds the ability for more complex [item model predicates](https://minecraft.wiki/w/Tutorials/Models#Item_predicates).

View File

@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(ItemRenderer.class)
public class ApplyHeadModelInItemRenderer {
@WrapOperation(method = "renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/world/World;III)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;getModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel applyHeadModel(ItemRenderer instance, ItemStack stack, World world, LivingEntity entity, int seed, Operation<BakedModel> original,
@Local(argsOnly = true) ModelTransformationMode modelTransformationMode) {
var model = original.call(instance, stack, world, entity, seed);
if (modelTransformationMode == ModelTransformationMode.HEAD
&& model instanceof BakedModelExtra extra) {
var headModel = extra.getHeadModel_firmament();
if (headModel != null) {
model = headModel;
}
}
return model;
}
}

View File

@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BasicBakedModel;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(BasicBakedModel.class)
public class BakedModelDataHolderBasic implements BakedModelExtra {
@Unique
private BakedModel headModel;
@Nullable
@Override
public BakedModel getHeadModel_firmament() {
return headModel;
}
@Override
public void setHeadModel_firmament(@Nullable BakedModel headModel) {
this.headModel = headModel;
}
}

View File

@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BuiltinBakedModel;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(BuiltinBakedModel.class)
public class BakedModelDataHolderBuiltin implements BakedModelExtra {
@Unique
private BakedModel headModel;
@Nullable
@Override
public BakedModel getHeadModel_firmament() {
return headModel;
}
@Override
public void setHeadModel_firmament(@Nullable BakedModel headModel) {
this.headModel = headModel;
}
}

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import net.minecraft.client.render.model.json.ItemModelGenerator;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(ItemModelGenerator.class)
public class ItemModelGeneratorJsonUnbakedModelCopy {
@ModifyReturnValue(method = "create", at = @At("RETURN"))
private JsonUnbakedModel copyHeadModel(JsonUnbakedModel original, @Local(argsOnly = true) JsonUnbakedModel oldModel) {
((JsonUnbakedModelFirmExtra) original).setHeadModel_firmament(((JsonUnbakedModelFirmExtra) oldModel).getHeadModel_firmament());
return original;
}
}

View File

@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import com.google.gson.annotations.SerializedName;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelRotation;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
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 java.util.Collection;
import java.util.Objects;
@Mixin(JsonUnbakedModel.class)
public class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
@Shadow
@Nullable
protected JsonUnbakedModel parent;
@Unique
@Nullable
public Identifier headModel;
@Override
public void setHeadModel_firmament(@Nullable Identifier identifier) {
this.headModel = identifier;
}
@Override
public @Nullable Identifier getHeadModel_firmament() {
if (this.headModel != null) return this.headModel;
if (this.parent == null) return null;
return ((JsonUnbakedModelFirmExtra) this.parent).getHeadModel_firmament();
}
@ModifyReturnValue(method = "getModelDependencies", at = @At("RETURN"))
private Collection<Identifier> addDependencies(Collection<Identifier> original) {
var headModel = getHeadModel_firmament();
if (headModel != null) {
original.add(headModel);
}
return original;
}
@ModifyReturnValue(
method = "bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
at = @At(value = "RETURN"))
private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) {
var headModel = getHeadModel_firmament();
if (headModel != null && original instanceof BakedModelExtra extra) {
UnbakedModel unbakedModel = baker.getOrLoadModel(headModel);
extra.setHeadModel_firmament(
Objects.equals(unbakedModel, parent)
? null
: baker.bake(headModel, ModelRotation.X0_Y0));
}
return original;
}
}

View File

@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.block.AbstractSkullBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(HeadFeatureRenderer.class)
public class PatchHeadFeatureRenderer<T extends LivingEntity, M extends EntityModel<T>> {
@Shadow
@Final
private HeldItemRenderer heldItemRenderer;
@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/entity/LivingEntity;FFFFFF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local ItemStack itemStack) {
var oldBlock = original.call(instance);
if (oldBlock instanceof AbstractSkullBlock) {
var bakedModel = this.heldItemRenderer.itemRenderer
.getModel(itemStack, null, null, 0);
if (bakedModel instanceof BakedModelExtra extra && extra.getHeadModel_firmament() != null)
return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
}
return oldBlock;
}
}

View File

@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins.custommodels;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(JsonUnbakedModel.Deserializer.class)
public class PatchJsonUnbakedModelDeserializer {
@ModifyReturnValue(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;",
at = @At("RETURN"))
private JsonUnbakedModel addHeadModel(JsonUnbakedModel original, @Local JsonObject jsonObject) {
var headModel = jsonObject.get("firmament:head_model");
if (headModel instanceof JsonPrimitive prim && prim.isString()) {
((JsonUnbakedModelFirmExtra) original).setHeadModel_firmament(Identifier.of(prim.getAsString()));
}
return original;
}
}

View File

@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.texturepack
import net.minecraft.client.render.model.BakedModel
interface BakedModelExtra {
fun getHeadModel_firmament(): BakedModel?
fun setHeadModel_firmament(headModel: BakedModel?)
}

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.texturepack
import net.minecraft.util.Identifier
interface JsonUnbakedModelFirmExtra {
fun setHeadModel_firmament(identifier: Identifier?)
fun getHeadModel_firmament(): Identifier?
}

View File

@@ -4,6 +4,8 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
accessible class net/minecraft/client/font/TextRenderer$Drawer
accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator;
accessible field net/minecraft/client/render/item/HeldItemRenderer itemRenderer Lnet/minecraft/client/render/item/ItemRenderer;
accessible class net/minecraft/client/render/model/json/ModelOverride$Deserializer
accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride
accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData;