Skip to content

Commit

Permalink
fabric-rendering-v1: Custom Armor Model & Texture (FabricMC#963)
Browse files Browse the repository at this point in the history
* Moving testmod id to rendering-v1

Signed-off-by: shedaniel <[email protected]>

Moving testmod to rendering-v1

Signed-off-by: shedaniel <[email protected]>

Reviews

Signed-off-by: shedaniel <[email protected]>

prefix the extensions with armor

Signed-off-by: shedaniel <[email protected]>

change name

Signed-off-by: shedaniel <[email protected]>

drop custom

Signed-off-by: shedaniel <[email protected]>

thing

Signed-off-by: shedaniel <[email protected]>

javadocs

Signed-off-by: shedaniel <[email protected]>

fix imports

Signed-off-by: shedaniel <[email protected]>

forgot to do asItem

Signed-off-by: shedaniel <[email protected]>

add null checks and convert to ItemConvertible

Signed-off-by: shedaniel <[email protected]>

fix license

Signed-off-by: shedaniel <[email protected]>

did thing

Signed-off-by: shedaniel <[email protected]>

it now compiles

Signed-off-by: shedaniel <[email protected]>

change to a registry

Signed-off-by: shedaniel <[email protected]>

add @unique

Signed-off-by: shedaniel <[email protected]>

migrate to fabric-item-api-v1

Signed-off-by: shedaniel <[email protected]>

did some renaming and improvements

Signed-off-by: shedaniel <[email protected]>

don't need that

Signed-off-by: shedaniel <[email protected]>

armor

Signed-off-by: shedaniel <[email protected]>

* add license to CustomArmorTests

Signed-off-by: shedaniel <[email protected]>

* Add @nullable annotations and fix compile

Signed-off-by: shedaniel <[email protected]>

* javadoc

Signed-off-by: shedaniel <[email protected]>

* Fix reviews

Signed-off-by: shedaniel <[email protected]>

* Update fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/MixinArmorFeatureRenderer.java

Co-authored-by: Erlend Åmdal <[email protected]>

* Add registerSimpleTexture
Pass through secondLayer and suffix
Use Identifier's over strings
Fix the test mod

* license fix

Co-authored-by: Erlend Åmdal <[email protected]>
Co-authored-by: modmuss50 <[email protected]>
  • Loading branch information
3 people authored Nov 15, 2020
1 parent ba858fb commit f21864f
Show file tree
Hide file tree
Showing 13 changed files with 568 additions and 1 deletion.
2 changes: 1 addition & 1 deletion fabric-rendering-v1/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
archivesBaseName = "fabric-rendering-v1"
version = getSubprojectVersion(project, "1.3.1")
version = getSubprojectVersion(project, "1.4.0")

dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.api.client.rendering.v1;

import java.util.Arrays;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.Identifier;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;

import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.impl.client.rendering.ArmorRenderingRegistryImpl;

/**
* A class for registering custom armor models and textures for {@link Item}, to be provided by a {@link ModelProvider} or {@link TextureProvider}.
*
* <p>This can be used to replace existing vanilla armor models and textures conditionally, however each {@link Item}
* instance can only allow one {@link ModelProvider} or {@link TextureProvider} respectively, causing potential conflicts
* with other mods if you replace the model or texture for vanilla items. Consider using a separate item instead.</p>
*
* <p>A custom model would probably also require a custom texture to go along it, the model will use the vanilla texture if it is undefined.</p>
*
* <p>Since armor textures identifier in vanilla is hardcoded to be in the {@code minecraft} namespace, this registry can also be
* used to use a custom namespace if desired.</p>
*/
@Environment(EnvType.CLIENT)
public final class ArmorRenderingRegistry {
private ArmorRenderingRegistry() {
}

/**
* Registers a provider for custom armor models for an item.
*
* @param provider the provider for the model
* @param items the items to be registered for
*/
public static void registerModel(@Nullable ModelProvider provider, Item... items) {
registerModel(provider, Arrays.asList(items));
}

/**
* Registers a provider for custom armor models for an item.
*
* @param provider the provider for the model
* @param items the items to be registered for
*/
public static void registerModel(@Nullable ModelProvider provider, Iterable<Item> items) {
ArmorRenderingRegistryImpl.registerModel(provider, items);
}

/**
* Registers a provider for custom texture models for an item.
*
* @param provider the provider for the texture
* @param items the items to be registered for
*/
public static void registerTexture(@Nullable TextureProvider provider, Item... items) {
registerTexture(provider, Arrays.asList(items));
}

/**
* Registers a provider for custom texture models for an item.
*
* @param provider the provider for the texture
* @param items the items to be registered for
*/
public static void registerTexture(@Nullable TextureProvider provider, Iterable<Item> items) {
ArmorRenderingRegistryImpl.registerTexture(provider, items);
}

/**
* Register simple armor items to use the vanilla armor file name under the mods namespace.
*
* @param identifier The namespace + path to use for the armor texture location.
* @param items the items to be registered
*/
public static void registerSimpleTexture(Identifier identifier, Item... items) {
registerTexture((entity, stack, slot, secondLayer, suffix, defaultTexture) -> {
return new Identifier(identifier.getNamespace(), "textures/models/armor/" + identifier.getPath() + "_layer_" + (secondLayer ? 2 : 1) + (suffix == null ? "" : "_" + suffix) + ".png");
}, items);
}

/**
* Gets the model of the armor piece.
*
* @param entity The entity equipping the armor
* @param stack The item stack of the armor
* @param slot The slot which the armor is in
* @param defaultModel The default model that vanilla provides
* @return The model of the armor piece.
*/
@NotNull
public static BipedEntityModel<LivingEntity> getArmorModel(LivingEntity entity, ItemStack stack, EquipmentSlot slot, BipedEntityModel<LivingEntity> defaultModel) {
return ArmorRenderingRegistryImpl.getArmorModel(entity, stack, slot, defaultModel);
}

/**
* Gets the armor texture {@link net.minecraft.util.Identifier}.
*
* @param entity The entity equipping the armor
* @param stack The item stack of the armor
* @param slot The slot which the armor is in
* @param secondLayer True if using the second texture layer
* @param suffix The texture suffix, used in vanilla by {@link net.minecraft.item.DyeableArmorItem}
* @param defaultTexture The default vanilla texture identifier
* @return the custom armor texture identifier, return null to use the vanilla ones. Defaulted to the item's registry id.
*/
@NotNull
public static Identifier getArmorTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, boolean secondLayer, @Nullable String suffix, Identifier defaultTexture) {
return ArmorRenderingRegistryImpl.getArmorTexture(entity, stack, slot, secondLayer, suffix, defaultTexture);
}

@FunctionalInterface
@Environment(EnvType.CLIENT)
public interface ModelProvider {
/**
* Gets the model of the armor piece.
*
* @param entity The entity equipping the armor
* @param stack The item stack of the armor
* @param slot The slot which the armor is in
* @param defaultModel The default vanilla armor model
* @return The model of the armor piece. Should never be null.
*/
@NotNull
BipedEntityModel<LivingEntity> getArmorModel(LivingEntity entity, ItemStack stack, EquipmentSlot slot, BipedEntityModel<LivingEntity> defaultModel);
}

@FunctionalInterface
@Environment(EnvType.CLIENT)
public interface TextureProvider {
/**
* Gets the armor texture {@link net.minecraft.util.Identifier}.
*
* @param entity The entity equipping the armor
* @param stack The item stack of the armor
* @param slot The slot which the armor is in
* @param defaultTexture The default vanilla texture identifier
* @return the custom armor texture identifier. Should never be null.
*/
@NotNull
Identifier getArmorTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, boolean secondLayer, @Nullable String suffix, Identifier defaultTexture);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.impl.client.rendering;

import org.jetbrains.annotations.Nullable;

import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;

public interface ArmorProviderExtensions {
@Nullable
ArmorRenderingRegistry.ModelProvider fabric_getArmorModelProvider();

@Nullable
ArmorRenderingRegistry.TextureProvider fabric_getArmorTextureProvider();

void fabric_setArmorModelProvider(@Nullable ArmorRenderingRegistry.ModelProvider provider);

void fabric_setArmorTextureProvider(@Nullable ArmorRenderingRegistry.TextureProvider provider);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.impl.client.rendering;

import java.util.Objects;

import org.jetbrains.annotations.Nullable;

import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;

import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;

public final class ArmorRenderingRegistryImpl {
private ArmorRenderingRegistryImpl() {
}

public static void registerModel(ArmorRenderingRegistry.ModelProvider provider, Iterable<Item> items) {
Objects.requireNonNull(items);

for (Item item : items) {
Objects.requireNonNull(item);

((ArmorProviderExtensions) item).fabric_setArmorModelProvider(provider);
}
}

public static void registerTexture(ArmorRenderingRegistry.TextureProvider provider, Iterable<Item> items) {
Objects.requireNonNull(items);

for (Item item : items) {
Objects.requireNonNull(item);

((ArmorProviderExtensions) item).fabric_setArmorTextureProvider(provider);
}
}

public static BipedEntityModel<LivingEntity> getArmorModel(LivingEntity entity, ItemStack stack, EquipmentSlot slot, BipedEntityModel<LivingEntity> defaultModel) {
if (!stack.isEmpty()) {
ArmorRenderingRegistry.ModelProvider provider = ((ArmorProviderExtensions) stack.getItem()).fabric_getArmorModelProvider();

if (provider != null) {
return provider.getArmorModel(entity, stack, slot, defaultModel);
}
}

return defaultModel;
}

public static Identifier getArmorTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, boolean secondLayer, @Nullable String suffix, Identifier defaultTexture) {
if (!stack.isEmpty()) {
ArmorRenderingRegistry.TextureProvider provider = ((ArmorProviderExtensions) stack.getItem()).fabric_getArmorTextureProvider();

if (provider != null) {
return provider.getArmorTexture(entity, stack, slot, secondLayer, suffix, defaultTexture);
}
}

return defaultTexture;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.mixin.client.rendering;

import java.util.Map;
import java.util.Objects;

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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;

import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRenderer;
import net.minecraft.client.render.entity.feature.FeatureRendererContext;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;

import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderingRegistry;

@Mixin(ArmorFeatureRenderer.class)
@Environment(EnvType.CLIENT)
public abstract class MixinArmorFeatureRenderer extends FeatureRenderer {
@Shadow
@Final
private static Map<String, Identifier> ARMOR_TEXTURE_CACHE;

public MixinArmorFeatureRenderer(FeatureRendererContext context) {
super(context);
}

@Unique
private LivingEntity storedEntity;
@Unique
private EquipmentSlot storedSlot;

@Inject(method = "render", at = @At("HEAD"))
private void storeEntity(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, LivingEntity livingEntity, float f, float g, float h, float j, float k, float l, CallbackInfo ci) {
// We store the living entity wearing the armor before we render
this.storedEntity = livingEntity;
}

@Inject(method = "renderArmor", at = @At("HEAD"))
private void storeSlot(MatrixStack matrices, VertexConsumerProvider vertexConsumers, LivingEntity livingEntity, EquipmentSlot slot, int i, BipedEntityModel bipedEntityModel, CallbackInfo ci) {
// We store the current armor slot that is rendering before we render each armor piece
this.storedSlot = slot;
}

@Inject(method = "render", at = @At("RETURN"))
private void removeStored(MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, LivingEntity livingEntity, float f, float g, float h, float j, float k, float l, CallbackInfo ci) {
// We remove the stored data after we render
this.storedEntity = null;
this.storedSlot = null;
}

@Inject(method = "getArmor", at = @At("RETURN"), cancellable = true)
private void selectArmorModel(EquipmentSlot slot, CallbackInfoReturnable<BipedEntityModel<LivingEntity>> cir) {
ItemStack stack = storedEntity.getEquippedStack(slot);

BipedEntityModel<LivingEntity> defaultModel = cir.getReturnValue();
BipedEntityModel<LivingEntity> model = ArmorRenderingRegistry.getArmorModel(storedEntity, stack, slot, defaultModel);

if (model != defaultModel) {
cir.setReturnValue(model);
}
}

@Inject(method = "getArmorTexture", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD)
private void getArmorTexture(ArmorItem armorItem, boolean secondLayer, /* @Nullable */ String suffix, CallbackInfoReturnable<Identifier> cir, String vanillaIdentifier) {
String texture = ArmorRenderingRegistry.getArmorTexture(storedEntity, storedEntity.getEquippedStack(storedSlot), storedSlot, secondLayer, suffix, new Identifier(vanillaIdentifier)).toString();

if (!Objects.equals(texture, vanillaIdentifier)) {
cir.setReturnValue(ARMOR_TEXTURE_CACHE.computeIfAbsent(texture, Identifier::new));
}
}
}
Loading

0 comments on commit f21864f

Please sign in to comment.