Add custom head models
This commit is contained in:
@@ -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).
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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?)
|
||||||
|
}
|
||||||
@@ -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?
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user