Proper armor rendering

- Add CustomRenderedArmorItem
- Remove old code in HumanoidArmorLayerMixin
- Move HumanoidArmorLayerMixin from common to client
This commit is contained in:
PepperCode1 2022-11-05 15:04:51 -07:00
parent 8e936a0096
commit d3a33896e2
11 changed files with 128 additions and 105 deletions

View file

@ -259,7 +259,7 @@ public class AllItems {
.tag(AllItemTags.PRESSURIZED_AIR_SOURCES.tag)
.register(),
NETHERITE_BACKTANK = REGISTRATE.item("netherite_backtank", p -> new BacktankItem.MultiLayered(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving"), NETHERITE_BACKTANK_PLACEABLE))
NETHERITE_BACKTANK = REGISTRATE.item("netherite_backtank", p -> new BacktankItem.Layered(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving"), NETHERITE_BACKTANK_PLACEABLE))
.model(AssetLookup.customGenericItemModel("_", "item"))
.tag(AllItemTags.PRESSURIZED_AIR_SOURCES.tag)
.register();
@ -269,7 +269,7 @@ public class AllItems {
COPPER_DIVING_HELMET = REGISTRATE.item("copper_diving_helmet", p -> new DivingHelmetItem(AllArmorMaterials.COPPER, p, Create.asResource("copper_diving")))
.register(),
NETHERITE_DIVING_HELMET = REGISTRATE.item("netherite_diving_helmet", p -> new DivingHelmetItem.MultiLayered(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving")))
NETHERITE_DIVING_HELMET = REGISTRATE.item("netherite_diving_helmet", p -> new DivingHelmetItem.Layered(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving")))
.register();
public static final ItemEntry<? extends DivingBootsItem>
@ -277,7 +277,7 @@ public class AllItems {
COPPER_DIVING_BOOTS = REGISTRATE.item("copper_diving_boots", p -> new DivingBootsItem(AllArmorMaterials.COPPER, p, Create.asResource("copper_diving")))
.register(),
NETHERITE_DIVING_BOOTS = REGISTRATE.item("netherite_diving_boots", p -> new DivingBootsItem.MultiLayered(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving")))
NETHERITE_DIVING_BOOTS = REGISTRATE.item("netherite_diving_boots", p -> new DivingBootsItem.Layered(ArmorMaterials.NETHERITE, p, Create.asResource("netherite_diving")))
.register();
public static final ItemEntry<SandPaperItem> SAND_PAPER = REGISTRATE.item("sand_paper", SandPaperItem::new)

View file

@ -6,7 +6,7 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.content.curiosities.armor.CapacityEnchantment.ICapacityEnchantable;
import com.simibubi.create.foundation.item.MultiLayeredArmorItem;
import com.simibubi.create.foundation.item.LayeredArmorItem;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
@ -119,14 +119,14 @@ public class BacktankItem extends BaseArmorItem implements ICapacityEnchantable
}
}
public static class MultiLayered extends BacktankItem implements MultiLayeredArmorItem {
public MultiLayered(ArmorMaterial material, Properties properties, ResourceLocation textureLoc, Supplier<BacktankBlockItem> placeable) {
public static class Layered extends BacktankItem implements LayeredArmorItem {
public Layered(ArmorMaterial material, Properties properties, ResourceLocation textureLoc, Supplier<BacktankBlockItem> placeable) {
super(material, properties, textureLoc, placeable);
}
@Override
public String getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, String layer) {
return String.format(Locale.ROOT, "%s:textures/models/armor/%s_layer_%s.png", textureLoc.getNamespace(), textureLoc.getPath(), layer);
public String getArmorTextureLocation(LivingEntity entity, EquipmentSlot slot, ItemStack stack, int layer) {
return String.format(Locale.ROOT, "%s:textures/models/armor/%s_layer_%d.png", textureLoc.getNamespace(), textureLoc.getPath(), layer);
}
}
}

View file

@ -2,7 +2,7 @@ package com.simibubi.create.content.curiosities.armor;
import java.util.Locale;
import com.simibubi.create.foundation.item.MultiLayeredArmorItem;
import com.simibubi.create.foundation.item.LayeredArmorItem;
import com.simibubi.create.foundation.utility.NBTHelper;
import net.minecraft.resources.ResourceLocation;
@ -77,14 +77,14 @@ public class DivingBootsItem extends BaseArmorItem {
return true;
}
public static class MultiLayered extends DivingBootsItem implements MultiLayeredArmorItem {
public MultiLayered(ArmorMaterial material, Properties properties, ResourceLocation textureLoc) {
public static class Layered extends DivingBootsItem implements LayeredArmorItem {
public Layered(ArmorMaterial material, Properties properties, ResourceLocation textureLoc) {
super(material, properties, textureLoc);
}
@Override
public String getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, String layer) {
return String.format(Locale.ROOT, "%s:textures/models/armor/%s_layer_%s.png", textureLoc.getNamespace(), textureLoc.getPath(), layer);
public String getArmorTextureLocation(LivingEntity entity, EquipmentSlot slot, ItemStack stack, int layer) {
return String.format(Locale.ROOT, "%s:textures/models/armor/%s_layer_%d.png", textureLoc.getNamespace(), textureLoc.getPath(), layer);
}
}
}

View file

@ -5,7 +5,7 @@ import java.util.Locale;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.foundation.advancement.AllAdvancements;
import com.simibubi.create.foundation.item.MultiLayeredArmorItem;
import com.simibubi.create.foundation.item.LayeredArmorItem;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
@ -97,14 +97,14 @@ public class DivingHelmetItem extends BaseArmorItem {
BacktankUtil.consumeAir(entity, backtank, 1);
}
public static class MultiLayered extends DivingHelmetItem implements MultiLayeredArmorItem {
public MultiLayered(ArmorMaterial material, Properties properties, ResourceLocation textureLoc) {
public static class Layered extends DivingHelmetItem implements LayeredArmorItem {
public Layered(ArmorMaterial material, Properties properties, ResourceLocation textureLoc) {
super(material, properties, textureLoc);
}
@Override
public String getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, String layer) {
return String.format(Locale.ROOT, "%s:textures/models/armor/%s_layer_%s.png", textureLoc.getNamespace(), textureLoc.getPath(), layer);
public String getArmorTextureLocation(LivingEntity entity, EquipmentSlot slot, ItemStack stack, int layer) {
return String.format(Locale.ROOT, "%s:textures/models/armor/%s_layer_%d.png", textureLoc.getNamespace(), textureLoc.getPath(), layer);
}
}
}

View file

@ -0,0 +1,14 @@
package com.simibubi.create.foundation.item;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
public interface CustomRenderedArmorItem {
void renderArmorPiece(HumanoidArmorLayer<?, ?, ?> layer, PoseStack poseStack, MultiBufferSource bufferSource, LivingEntity entity, EquipmentSlot slot, int light, HumanoidModel<?> originalModel, ItemStack stack);
}

View file

@ -0,0 +1,50 @@
package com.simibubi.create.foundation.item;
import java.util.Map;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.mixin.accessor.HumanoidArmorLayerAccessor;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ItemStack;
public interface LayeredArmorItem extends CustomRenderedArmorItem {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
default void renderArmorPiece(HumanoidArmorLayer<?, ?, ?> layer, PoseStack poseStack,
MultiBufferSource bufferSource, LivingEntity entity, EquipmentSlot slot, int light,
HumanoidModel<?> originalModel, ItemStack stack) {
if (!(stack.getItem() instanceof ArmorItem item)) {
return;
}
if (item.getSlot() != slot) {
return;
}
HumanoidArmorLayerAccessor accessor = (HumanoidArmorLayerAccessor) layer;
Map<String, ResourceLocation> locationCache = HumanoidArmorLayerAccessor.create$getArmorLocationCache();
boolean glint = stack.hasFoil();
HumanoidModel<?> innerModel = accessor.create$getInnerModel();
layer.getParentModel().copyPropertiesTo((HumanoidModel) innerModel);
accessor.create$callSetPartVisibility(innerModel, slot);
String locationStr2 = getArmorTextureLocation(entity, slot, stack, 2);
ResourceLocation location2 = locationCache.computeIfAbsent(locationStr2, ResourceLocation::new);
accessor.create$callRenderModel(poseStack, bufferSource, light, glint, innerModel, 1.0F, 1.0F, 1.0F, location2);
HumanoidModel<?> outerModel = accessor.create$getOuterModel();
layer.getParentModel().copyPropertiesTo((HumanoidModel) outerModel);
accessor.create$callSetPartVisibility(outerModel, slot);
String locationStr1 = getArmorTextureLocation(entity, slot, stack, 1);
ResourceLocation location1 = locationCache.computeIfAbsent(locationStr1, ResourceLocation::new);
accessor.create$callRenderModel(poseStack, bufferSource, light, glint, outerModel, 1.0F, 1.0F, 1.0F, location1);
}
String getArmorTextureLocation(LivingEntity entity, EquipmentSlot slot, ItemStack stack, int layer);
}

View file

@ -1,20 +0,0 @@
package com.simibubi.create.foundation.item;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.DyeableLeatherItem;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.extensions.IForgeItem;
/**
* This interface is meant to be implemented on {@link ArmorItem}s, which will allow them to be rendered on both the inner model and outer model.
*
* <p>Classes implementing this interface <b>must not</b> also implement {@link DyeableLeatherItem}.
*
* <p>Classes that implement this interface and override {@link IForgeItem#getArmorTexture(ItemStack, Entity, EquipmentSlot, String) getArmorTexture}
* must note that the {@code String} argument will be used for layer context instead of the type.
* This string will always be {@code "1"} when querying the location for the outer model or {@code "2"} when querying the location for the inner model.
*/
public interface MultiLayeredArmorItem {
}

View file

@ -1,87 +1,28 @@
package com.simibubi.create.foundation.mixin;
import javax.annotation.Nullable;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.item.MultiLayeredArmorItem;
import com.simibubi.create.foundation.item.CustomRenderedArmorItem;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ItemStack;
@Mixin(HumanoidArmorLayer.class)
public class HumanoidArmorLayerMixin {
@Shadow
@Final
private HumanoidModel<?> innerModel;
@Shadow
@Final
private HumanoidModel<?> outerModel;
@Unique
private boolean intercepted;
@Unique
private Boolean useInnerTexture;
@Shadow
private void renderArmorPiece(PoseStack poseStack, MultiBufferSource buffer, LivingEntity livingEntity, EquipmentSlot slot, int packedLight, HumanoidModel<?> model) {
}
@Shadow
private boolean usesInnerModel(EquipmentSlot slot) {
return false;
}
@Inject(method = "renderArmorPiece", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/layers/HumanoidArmorLayer;getParentModel()Lnet/minecraft/client/model/EntityModel;"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
private void onRenderArmorPiece(PoseStack poseStack, MultiBufferSource buffer, LivingEntity livingEntity, EquipmentSlot slot, int packedLight, HumanoidModel<?> model, CallbackInfo ci, ItemStack stack, ArmorItem armorItem) {
if (intercepted) {
return;
}
if (armorItem instanceof MultiLayeredArmorItem) {
intercepted = true;
useInnerTexture = true;
renderArmorPiece(poseStack, buffer, livingEntity, slot, packedLight, innerModel);
useInnerTexture = false;
renderArmorPiece(poseStack, buffer, livingEntity, slot, packedLight, outerModel);
useInnerTexture = null;
intercepted = false;
@Inject(method = "renderArmorPiece(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/entity/EquipmentSlot;ILnet/minecraft/client/model/HumanoidModel;)V", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/world/entity/LivingEntity;getItemBySlot(Lnet/minecraft/world/entity/EquipmentSlot;)Lnet/minecraft/world/item/ItemStack;"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
private void onRenderArmorPiece(PoseStack poseStack, MultiBufferSource bufferSource, LivingEntity entity, EquipmentSlot slot, int light, HumanoidModel<?> model, CallbackInfo ci, ItemStack stack) {
if (stack.getItem() instanceof CustomRenderedArmorItem renderer) {
renderer.renderArmorPiece((HumanoidArmorLayer<?, ?, ?>) (Object) this, poseStack, bufferSource, entity, slot, light, model, stack);
ci.cancel();
}
}
@Inject(method = "usesInnerModel", at = @At("HEAD"), cancellable = true)
private void onUsesInnerModel(EquipmentSlot slot, CallbackInfoReturnable<Boolean> cir) {
if (useInnerTexture != null) {
cir.setReturnValue(useInnerTexture);
}
}
@ModifyVariable(method = "getArmorResource", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/ForgeHooksClient;getArmorTexture(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/item/ItemStack;Ljava/lang/String;Lnet/minecraft/world/entity/EquipmentSlot;Ljava/lang/String;)Ljava/lang/String;", shift = Shift.BEFORE), ordinal = 0, remap = false)
private String modifyType(@Nullable String type, Entity entity, ItemStack stack, EquipmentSlot slot, @Nullable String typeArg) {
if (stack.getItem() instanceof MultiLayeredArmorItem) {
return usesInnerModel(slot) ? "2" : "1";
}
return type;
}
}

View file

@ -10,6 +10,7 @@ import net.minecraft.client.model.geom.ModelPart;
public interface AgeableListModelAccessor {
@Invoker("headParts")
Iterable<ModelPart> create$callHeadParts();
@Invoker("bodyParts")
Iterable<ModelPart> create$callBodyParts();
}

View file

@ -0,0 +1,36 @@
package com.simibubi.create.foundation.mixin.accessor;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.Model;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EquipmentSlot;
@Mixin(HumanoidArmorLayer.class)
public interface HumanoidArmorLayerAccessor {
@Accessor("ARMOR_LOCATION_CACHE")
static Map<String, ResourceLocation> create$getArmorLocationCache() {
throw new RuntimeException();
}
@Accessor("innerModel")
HumanoidModel<?> create$getInnerModel();
@Accessor("outerModel")
HumanoidModel<?> create$getOuterModel();
@Invoker("setPartVisibility")
void create$callSetPartVisibility(HumanoidModel<?> model, EquipmentSlot slot);
@Invoker("renderModel")
void create$callRenderModel(PoseStack poseStack, MultiBufferSource bufferSource, int light, boolean glint, Model model, float red, float green, float blue, ResourceLocation armorResource);
}

View file

@ -5,13 +5,12 @@
"compatibilityLevel": "JAVA_16",
"refmap": "create.refmap.json",
"mixins": [
"ContraptionDriverInteractMixin",
"CustomItemUseEffectsMixin",
"EnchantmentHelperMixin",
"EnchantmentMixin",
"EntityMixin",
"HumanoidArmorLayerMixin",
"MapItemSavedDataMixin",
"ContraptionDriverInteractMixin",
"accessor.AbstractProjectileDispenseBehaviorAccessor",
"accessor.DispenserBlockAccessor",
"accessor.FallingBlockEntityAccessor",
@ -19,18 +18,20 @@
"accessor.ServerLevelAccessor"
],
"client": [
"MapItemSavedDataMixinClient",
"CameraMixin",
"DestroyProgressMixin",
"EntityContraptionInteractionMixin",
"FixNormalScalingMixin",
"GameRendererMixin",
"HeavyBootsOnPlayerMixin",
"HumanoidArmorLayerMixin",
"MapItemSavedDataMixinClient",
"MapRendererMixin",
"ModelDataRefreshMixin",
"WindowResizeMixin",
"accessor.AgeableListModelAccessor",
"accessor.GameRendererAccessor",
"accessor.HumanoidArmorLayerAccessor",
"accessor.ParticleEngineAccessor"
],
"injectors": {