package choonster.testmod3.client.model;
import choonster.testmod3.block.*;
import choonster.testmod3.init.ModBlocks;
import choonster.testmod3.init.ModFluids;
import choonster.testmod3.init.ModItems;
import choonster.testmod3.item.ItemVariants;
import choonster.testmod3.util.Constants;
import choonster.testmod3.util.EnumFaceRotation;
import choonster.testmod3.util.IVariant;
import net.minecraft.block.Block;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.BlockPlanks;
import net.minecraft.block.BlockSlab;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.ItemMeshDefinition;
import net.minecraft.client.renderer.block.model.ModelBakery;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.client.renderer.block.statemap.StateMap;
import net.minecraft.client.renderer.block.statemap.StateMapperBase;
import net.minecraft.init.Items;
import net.minecraft.item.EnumDyeColor;
import net.minecraft.item.Item;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.IStringSerializable;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.IFluidBlock;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import java.util.HashSet;
import java.util.Set;
import java.util.function.ToIntFunction;
@Mod.EventBusSubscriber(Side.CLIENT)
public class ModModelManager {
public static final ModModelManager INSTANCE = new ModModelManager();
private static final String FLUID_MODEL_PATH = Constants.RESOURCE_PREFIX + "fluid";
private ModModelManager() {
}
/**
* Register this mod's {@link Fluid}, {@link Block} and {@link Item} models.
*
* @param event The event
*/
@SubscribeEvent
public static void registerAllModels(ModelRegistryEvent event) {
INSTANCE.registerFluidModels();
INSTANCE.registerBlockModels();
INSTANCE.registerItemModels();
}
/**
* Register this mod's {@link Fluid} models.
*/
private void registerFluidModels() {
ModFluids.MOD_FLUID_BLOCKS.forEach(this::registerFluidModel);
}
/**
* Register the block and item model for a {@link Fluid}.
*
* @param fluidBlock The Fluid's Block
*/
private void registerFluidModel(IFluidBlock fluidBlock) {
final Item item = Item.getItemFromBlock((Block) fluidBlock);
assert item != Items.AIR;
ModelBakery.registerItemVariants(item);
final ModelResourceLocation modelResourceLocation = new ModelResourceLocation(FLUID_MODEL_PATH, fluidBlock.getFluid().getName());
ModelLoader.setCustomMeshDefinition(item, MeshDefinitionFix.create(stack -> modelResourceLocation));
ModelLoader.setCustomStateMapper((Block) fluidBlock, new StateMapperBase() {
@Override
protected ModelResourceLocation getModelResourceLocation(IBlockState p_178132_1_) {
return modelResourceLocation;
}
});
}
/**
* A {@link StateMapperBase} used to create property strings.
*/
private final StateMapperBase propertyStringMapper = new StateMapperBase() {
@Override
protected ModelResourceLocation getModelResourceLocation(IBlockState state) {
return new ModelResourceLocation("minecraft:air");
}
};
/**
* Register this mod's {@link Block} models.
*/
private void registerBlockModels() {
ModelLoader.setCustomStateMapper(ModBlocks.WATER_GRASS, new StateMap.Builder().ignore(BlockLiquid.LEVEL).build());
registerBlockItemModel(
ModBlocks.RIGHT_CLICK_TEST.getDefaultState()
.withProperty(BlockRightClickTest.HAS_ENDER_EYE, false)
);
registerVariantBlockItemModels(
ModBlocks.COLORED_ROTATABLE.getDefaultState()
.withProperty(BlockColoredRotatable.FACING, EnumFacing.NORTH),
BlockColoredRotatable.COLOR, EnumDyeColor::getMetadata
);
registerVariantBlockItemModels(
ModBlocks.COLORED_MULTI_ROTATABLE.getDefaultState()
.withProperty(BlockColoredMultiRotatable.FACING, EnumFacing.NORTH)
.withProperty(BlockColoredMultiRotatable.FACE_ROTATION, EnumFaceRotation.UP),
BlockColoredMultiRotatable.COLOR, EnumDyeColor::getMetadata
);
registerSlabGroupItemModels(ModBlocks.Slabs.STAINED_CLAY_SLABS.high);
registerSlabGroupItemModels(ModBlocks.Slabs.STAINED_CLAY_SLABS.low);
registerVariantBlockItemModels(
ModBlocks.VARIANTS.getDefaultState(),
BlockVariants.VARIANT
);
registerBlockItemModel(
ModBlocks.MIRROR_PLANE.getDefaultState()
.withProperty(BlockPlane.HORIZONTAL_ROTATION, EnumFacing.NORTH)
.withProperty(BlockPlane.VERTICAL_ROTATION, BlockPlane.EnumVerticalRotation.UP)
);
registerBlockItemModel(
ModBlocks.CHEST.getDefaultState()
.withProperty(BlockModChest.FACING, EnumFacing.NORTH)
);
registerVariantBlockItemModels(
ModBlocks.SAPLING.getDefaultState()
.withProperty(BlockSaplingTestMod3.STAGE, 0)
.withProperty(BlockSaplingTestMod3.ITEM, true),
BlockSaplingTestMod3.TYPE, BlockPlanks.EnumType::getMetadata
);
ModBlocks.RegistrationHandler.ITEM_BLOCKS.stream().filter(item -> !itemsRegistered.contains(item)).forEach(this::registerItemModel);
}
/**
* Register a single model for the {@link Block}'s {@link Item}.
* <p>
* Uses the registry name as the domain/path and the {@link IBlockState} as the variant.
*
* @param state The state to use as the variant
*/
private void registerBlockItemModel(IBlockState state) {
final Block block = state.getBlock();
final Item item = Item.getItemFromBlock(block);
if (item != Items.AIR) {
registerItemModel(item, new ModelResourceLocation(block.getRegistryName(), propertyStringMapper.getPropertyString(state.getProperties())));
}
}
/**
* Register a model for a metadata value of the {@link Block}'s {@link Item}.
* <p>
* Uses the registry name as the domain/path and the {@link IBlockState} as the variant.
*
* @param state The state to use as the variant
* @param metadata The item metadata to register the model for
*/
private void registerBlockItemModelForMeta(IBlockState state, int metadata) {
final Item item = Item.getItemFromBlock(state.getBlock());
if (item != Items.AIR) {
registerItemModelForMeta(item, metadata, propertyStringMapper.getPropertyString(state.getProperties()));
}
}
/**
* Register a model for each metadata value of the {@link Block}'s {@link Item} corresponding to the values of an {@link IProperty}.
* <p>
* For each value:
* <li>The domain/path is the registry name</li>
* <li>The variant is {@code baseState} with the {@link IProperty} set to the value</li>
* <p>
* The {@code getMeta} function is used to get the metadata of each value.
*
* @param baseState The base state to use for the variant
* @param property The property whose values should be used
* @param getMeta A function to get the metadata of each value
* @param <T> The value type
*/
private <T extends Comparable<T>> void registerVariantBlockItemModels(IBlockState baseState, IProperty<T> property, ToIntFunction<T> getMeta) {
property.getAllowedValues().forEach(value -> registerBlockItemModelForMeta(baseState.withProperty(property, value), getMeta.applyAsInt(value)));
}
/**
* Register a model for each metadata value of the {@link Block}'s {@link Item} corresponding to the values of an {@link IProperty}.
* <p>
* For each value:
* <li>The domain/path is the registry name</li>
* <li>The variant is {@code baseState} with the {@link IProperty} set to the value</li>
* <p>
* {@link IVariant#getMeta()} is used to get the metadata of each value.
*
* @param baseState The base state to use for the variant
* @param property The property whose values should be used
* @param <T> The value type
*/
private <T extends IVariant & Comparable<T>> void registerVariantBlockItemModels(IBlockState baseState, IProperty<T> property) {
registerVariantBlockItemModels(baseState, property, IVariant::getMeta);
}
/**
* Register a model for each metadata value of a {@link BlockSlabTestMod3.SlabGroup}'s {@link Item} corresponding to
* the values of the slab's variant property ({@link BlockSlab#getVariantProperty()}).
* <p>
* For each value:
* <li>The domain/path is the registry name</li>
* <li>The variant is the default state with {@link BlockSlab#HALF} set to {@link BlockSlab.EnumBlockHalf#BOTTOM}
* and the variant property set to the value as the variant.</li>
* <p>
* {@link BlockSlabTestMod3#getMetadata} is used to get the metadata of each value.
*
* @param slabGroup The SlabGroup
* @param <VARIANT> The variant type
* @param <VARIANTS> The variant collection type
* @param <SLAB> The slab type
*/
private <
VARIANT extends Enum<VARIANT> & IStringSerializable,
VARIANTS extends Iterable<VARIANT> & IStringSerializable,
SLAB extends BlockSlabTestMod3<VARIANT, VARIANTS, SLAB>
>
void registerSlabGroupItemModels(BlockSlabTestMod3.SlabGroup<VARIANT, VARIANTS, SLAB> slabGroup) {
final SLAB singleSlab = slabGroup.singleSlab;
registerVariantBlockItemModels(
singleSlab.getDefaultState()
.withProperty(BlockSlab.HALF, BlockSlab.EnumBlockHalf.BOTTOM),
singleSlab.getVariantProperty(), singleSlab::getMetadata
);
}
/**
* The {@link Item}s that have had models registered so far.
*/
private final Set<Item> itemsRegistered = new HashSet<>();
/**
* Register this mod's {@link Item} models.
*/
private void registerItemModels() {
// Register items with custom model names first
registerItemModel(ModItems.SNOWBALL_LAUNCHER, "minecraft:fishing_rod");
registerItemModel(ModItems.UNICODE_TOOLTIPS, "minecraft:rabbit");
registerItemModel(ModItems.SWAP_TEST_A, "minecraft:brick");
registerItemModel(ModItems.SWAP_TEST_B, "minecraft:netherbrick");
registerItemModel(ModItems.BLOCK_DEBUGGER, "minecraft:nether_star");
registerItemModel(ModItems.WOODEN_HARVEST_SWORD, "minecraft:wooden_sword");
registerItemModel(ModItems.DIAMOND_HARVEST_SWORD, "minecraft:diamond_sword");
registerItemModel(ModItems.CLEARER, "minecraft:nether_star");
registerItemModel(ModItems.HEIGHT_TESTER, "minecraft:compass");
registerItemModel(ModItems.HEAVY, "minecraft:brick");
registerItemModel(ModItems.ENTITY_TEST, "minecraft:porkchop");
registerItemModel(ModItems.BLOCK_DESTROYER, "minecraft:tnt_minecart");
registerItemModel(ModItems.REPLACEMENT_HELMET, "minecraft:chainmail_helmet");
registerItemModel(ModItems.REPLACEMENT_CHESTPLATE, "minecraft:chainmail_chestplate");
registerItemModel(ModItems.REPLACEMENT_LEGGINGS, "minecraft:chainmail_leggings");
registerItemModel(ModItems.REPLACEMENT_BOOTS, "minecraft:chainmail_boots");
registerItemModel(ModItems.PIG_SPAWNER_FINITE, "minecraft:porkchop");
registerItemModel(ModItems.PIG_SPAWNER_INFINITE, "minecraft:porkchop");
registerItemModel(ModItems.RESPAWNER, "minecraft:clock");
registerItemModel(ModItems.LOOT_TABLE_TEST, "minecraft:gold_ingot");
registerItemModel(ModItems.SADDLE, "minecraft:saddle");
registerItemModel(ModItems.WOODEN_SLOW_SWORD, "minecraft:wooden_sword");
registerItemModel(ModItems.DIAMOND_SLOW_SWORD, "minecraft:diamond_sword");
registerItemModel(ModItems.NO_MOD_NAME, "minecraft:bread");
registerItemModel(ModItems.SATURATION_HELMET, "minecraft:chainmail_helmet");
registerVariantItemModels(ModItems.VARIANTS_ITEM, "variant", ItemVariants.EnumType.values());
// Then register items with default model names
ModItems.RegistrationHandler.ITEMS.stream().filter(item -> !itemsRegistered.contains(item)).forEach(this::registerItemModel);
}
/**
* Register a single model for an {@link Item}.
* <p>
* Uses the registry name as the domain/path and {@code "inventory"} as the variant.
*
* @param item The Item
*/
private void registerItemModel(Item item) {
registerItemModel(item, item.getRegistryName().toString());
}
/**
* Register a single model for an {@link Item}.
* <p>
* Uses {@code modelLocation} as the domain/path and {@link "inventory"} as the variant.
*
* @param item The Item
* @param modelLocation The model location
*/
private void registerItemModel(Item item, String modelLocation) {
final ModelResourceLocation fullModelLocation = new ModelResourceLocation(modelLocation, "inventory");
registerItemModel(item, fullModelLocation);
}
/**
* Register a single model for an {@link Item}.
* <p>
* Uses {@code fullModelLocation} as the domain, path and variant.
*
* @param item The Item
* @param fullModelLocation The full model location
*/
private void registerItemModel(Item item, ModelResourceLocation fullModelLocation) {
ModelBakery.registerItemVariants(item, fullModelLocation); // Ensure the custom model is loaded and prevent the default model from being loaded
registerItemModel(item, MeshDefinitionFix.create(stack -> fullModelLocation));
}
/**
* Register an {@link ItemMeshDefinition} for an {@link Item}.
*
* @param item The Item
* @param meshDefinition The ItemMeshDefinition
*/
private void registerItemModel(Item item, ItemMeshDefinition meshDefinition) {
itemsRegistered.add(item);
ModelLoader.setCustomMeshDefinition(item, meshDefinition);
}
/**
* Register a model for each metadata value of an {@link Item} corresponding to the values in {@code values}.
* <p>
* Uses the registry name as the domain/path and {@code "[variantName]=[valueName]"} as the variant.
* <p>
* Uses {@link IVariant#getMeta()} to determine the metadata of each value.
*
* @param item The Item
* @param variantName The variant name
* @param values The values
* @param <T> The value type
*/
private <T extends IVariant> void registerVariantItemModels(Item item, String variantName, T[] values) {
for (T value : values) {
registerItemModelForMeta(item, value.getMeta(), variantName + "=" + value.getName());
}
}
/**
* Register a model for a metadata value an {@link Item}.
* <p>
* Uses the registry name as the domain/path and {@code variant} as the variant.
*
* @param item The Item
* @param metadata The metadata
* @param variant The variant
*/
private void registerItemModelForMeta(Item item, int metadata, String variant) {
registerItemModelForMeta(item, metadata, new ModelResourceLocation(item.getRegistryName(), variant));
}
/**
* Register a model for a metadata value of an {@link Item}.
* <p>
* Uses {@code modelResourceLocation} as the domain, path and variant.
*
* @param item The Item
* @param metadata The metadata
* @param modelResourceLocation The full model location
*/
private void registerItemModelForMeta(Item item, int metadata, ModelResourceLocation modelResourceLocation) {
itemsRegistered.add(item);
ModelLoader.setCustomModelResourceLocation(item, metadata, modelResourceLocation);
}
}