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 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. 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 ## Predicates
Firmament adds the ability for more complex [item model predicates](https://minecraft.wiki/w/Tutorials/Models#Item_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 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/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/ModelOverride$Deserializer
accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride
accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData; accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData;