package slimeknights.tconstruct.library.client; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.ModelBakery; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.item.Item; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.client.model.IModel; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.fml.common.Loader; import net.minecraftforge.fml.common.LoaderState; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.registry.RegistryDelegate; import org.apache.logging.log4j.Logger; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; import slimeknights.tconstruct.library.TinkerRegistry; import slimeknights.tconstruct.library.Util; import slimeknights.tconstruct.library.client.material.MaterialRenderInfoLoader; import slimeknights.tconstruct.library.client.model.IPatternOffset; import slimeknights.tconstruct.library.client.model.MaterialModelLoader; import slimeknights.tconstruct.library.client.texture.AbstractColoredTexture; import slimeknights.tconstruct.library.client.texture.CastTexture; import slimeknights.tconstruct.library.client.texture.GuiOutlineTexture; import slimeknights.tconstruct.library.client.texture.PatternTexture; import slimeknights.tconstruct.library.client.texture.TextureColoredTexture; import slimeknights.tconstruct.library.materials.Material; import slimeknights.tconstruct.library.materials.MaterialGUI; import slimeknights.tconstruct.library.tools.IToolPart; import slimeknights.tconstruct.library.tools.Pattern; /** * Textures registered with this creator will get a texture created/loaded for each material. */ public class CustomTextureCreator implements IResourceManagerReloadListener { public static final CustomTextureCreator INSTANCE = new CustomTextureCreator(); private static Logger log = Util.getLogger("TextureGen"); /** * Holds all sprites built from the base-texture used as the key. */ public static Map<String, Map<String, TextureAtlasSprite>> sprites = Maps.newHashMap(); private static Set<ResourceLocation> baseTextures = Sets.newHashSet(); private static Map<ResourceLocation, Set<IToolPart>> texturePartMapping = Maps.newHashMap(); public static void registerTextures(Collection<ResourceLocation> textures) { baseTextures.addAll(textures); } public static void registerTexture(ResourceLocation texture) { baseTextures.add(texture); } public static void registerTextureForPart(ResourceLocation texture, IToolPart toolPart) { if(!texturePartMapping.containsKey(texture)) { texturePartMapping.put(texture, Sets.<IToolPart>newHashSet()); } texturePartMapping.get(texture).add(toolPart); registerTexture(texture); } // set these to the pattern/cast model to generate part-textures for them public static ResourceLocation patternModelLocation; public static ResourceLocation castModelLocation; public static String patternLocString; public static String castLocString; public static final Material guiMaterial; private int createdTextures; // low since other event-handlers might want to register textures beforehand @SubscribeEvent(priority = EventPriority.LOW) public void createCustomTextures(TextureStitchEvent.Pre event) { // only do the processing once: at the end of the loading when the resource manager gets reloaded // this is equivalent to a resourcepack change midgame if(!Loader.instance().hasReachedState(LoaderState.POSTINITIALIZATION)) { return; } // get the material info at this point, to override hardcoded material rendering with resources MaterialRenderInfoLoader.INSTANCE.loadRenderInfo(); createdTextures = 0; // create textures for each material where needed createMaterialTextures(event.getMap()); // add stencil and cast textures for all used toolparts createPatterntextures(event.getMap()); log.debug("Generated " + createdTextures + " Textures for Materials"); } private void createMaterialTextures(TextureMap map) { // Create textures for toolparts and tools - Textures that need 1 per material for(ResourceLocation baseTexture : baseTextures) { // exclude missingno :I if(baseTexture.toString().equals("minecraft:missingno")) { continue; } TextureAtlasSprite base = map.getTextureExtry(baseTexture.toString()); if(base == null) { log.error("Missing base texture: " + baseTexture.toString()); continue; } Set<IToolPart> parts = texturePartMapping.get(baseTexture); Map<String, TextureAtlasSprite> builtSprites = Maps.newHashMap(); for(Material material : TinkerRegistry.getAllMaterials()) { boolean usable; if(parts == null || material instanceof MaterialGUI) { usable = true; } else { usable = false; for(IToolPart toolPart : parts) { usable |= toolPart.canUseMaterial(material); } } if(usable) { TextureAtlasSprite sprite = createTexture(material, baseTexture, base, map); if(sprite != null) { builtSprites.put(material.identifier, sprite); } } } if(belongsToToolPart(baseTexture)) { TextureAtlasSprite sprite = createTexture(guiMaterial, baseTexture, base, map); if(sprite != null) { builtSprites.put(guiMaterial.identifier, sprite); } } sprites.put(baseTexture.toString(), builtSprites); } } private TextureAtlasSprite createTexture(Material material, ResourceLocation baseTexture, TextureAtlasSprite base, TextureMap map) { String location = baseTexture.toString() + "_" + material.identifier; TextureAtlasSprite sprite; if(exists(location)) { sprite = map.registerSprite(new ResourceLocation(location)); } else { // material does not need a special generated texture if(material.renderInfo == null) { return null; } TextureAtlasSprite matBase = base; // different base texture? if(material.renderInfo.getTextureSuffix() != null) { String loc2 = baseTexture.toString() + "_" + material.renderInfo.getTextureSuffix(); TextureAtlasSprite base2 = map.getTextureExtry(loc2); // can we manually load it? if(base2 == null && exists(loc2)) { base2 = new AbstractColoredTexture(loc2, loc2) { @Override protected int colorPixel(int pixel, int mipmap, int pxCoord) { return pixel; } }; // save in the map so it's getting reused by the others and is available map.setTextureEntry(loc2, base2); } if(base2 != null) { matBase = base2; } } sprite = material.renderInfo.getTexture(matBase, location); createdTextures++; } // stitch new textures if(sprite != null && material.renderInfo.isStitched()) { map.setTextureEntry(location, sprite); } return sprite; } private void createPatterntextures(TextureMap map) { // create Pattern textures if(patternModelLocation != null) { patternLocString = createPatternTexturesFor(map, patternModelLocation, TinkerRegistry.getPatternItems(), PatternTexture.class); } // create cast textures if(castModelLocation != null) { castLocString = createPatternTexturesFor(map, castModelLocation, TinkerRegistry.getCastItems(), CastTexture.class); } } public String createPatternTexturesFor(TextureMap map, ResourceLocation baseTextureLoc, Iterable<Item> items, Class<? extends TextureColoredTexture> clazz) { Constructor<? extends TextureColoredTexture> constructor; String baseTextureString; TextureAtlasSprite baseTexture; try { constructor = clazz.getConstructor(String.class, TextureAtlasSprite.class, String.class); IModel patternModel = ModelLoaderRegistry.getModel(baseTextureLoc); ResourceLocation patternLocation = patternModel.getTextures().iterator().next(); baseTexture = map.getTextureExtry(patternLocation.toString()); baseTextureString = patternLocation.toString(); if(baseTexture == null) { log.error("No basetexture found for pattern texture generation: " + patternLocation); return null; } } catch(Exception e) { log.error(e); return null; } for(Item item : items) { try { // get id String identifier = Pattern.getTextureIdentifier(item); String partPatternLocation = baseTextureString + identifier; TextureAtlasSprite partPatternTexture; if(exists(partPatternLocation)) { partPatternTexture = map.registerSprite(new ResourceLocation(partPatternLocation)); map.setTextureEntry(partPatternLocation, partPatternTexture); } else { /* ResourceLocation modelLocation = getModelLocationForItem(item); IModel partModel = ModelLoaderRegistry.getModel(modelLocation); */ ResourceLocation modelLocation = Util.getItemLocation(item); IModel partModel = ModelLoaderRegistry.getModel(new ResourceLocation(modelLocation.getResourceDomain(), "item/parts/" + modelLocation .getResourcePath() + MaterialModelLoader.EXTENSION)); ResourceLocation partTexture = partModel.getTextures().iterator().next(); if(partModel != ModelLoaderRegistry.getMissingModel()) { partPatternTexture = constructor.newInstance(partTexture.toString(), baseTexture, partPatternLocation); if(partModel instanceof IPatternOffset) { IPatternOffset offset = (IPatternOffset) partModel; ((TextureColoredTexture) partPatternTexture).setOffset(offset.getXOffset(), offset.getYOffset()); } map.setTextureEntry(partPatternLocation, partPatternTexture); } } } catch(Exception e) { log.error(e); } } return baseTextureString; } private ResourceLocation getModelLocationForItem(Item item) { String loc = null; try { Field field = ModelBakery.class.getDeclaredField("customVariantNames"); field.setAccessible(true); Map<net.minecraftforge.fml.common.registry.RegistryDelegate<Item>, Set<String>> map = (Map<RegistryDelegate<Item>, Set<String>>) field.get(null); Set<String> variants = map.get(item.delegate); if(variants != null) { loc = variants.iterator().next(); } } catch(NoSuchFieldException e) { e.printStackTrace(); } catch(IllegalAccessException e) { e.printStackTrace(); } if(loc == null) { loc = Util.getItemLocation(item).toString(); } ResourceLocation rl = new ResourceLocation(loc.replaceAll("#.*", "")); rl = new ResourceLocation(rl.getResourceDomain(), "item/" + rl.getResourcePath()); return rl; } // the same as materialtextures but only creates the ones for toolparts for the gui private void createGUITextures(TextureMap map) { for(IToolPart toolpart : TinkerRegistry.getToolParts()) { if(!(toolpart instanceof Item)) { continue; // WHY?! } try { // name and model location ResourceLocation modelLocation = Util.getItemLocation((Item) toolpart); IModel partModel = ModelLoaderRegistry.getModel(new ResourceLocation(modelLocation.getResourceDomain(), "item/parts/" + modelLocation .getResourcePath() + MaterialModelLoader.EXTENSION)); // the actual texture of the part ResourceLocation baseTexture = partModel.getTextures().iterator().next(); TextureAtlasSprite base = map.getTextureExtry(baseTexture.toString()); if(base == null) { log.error("Missing base texture: " + baseTexture.toString()); continue; } // does it have textures? Map<String, TextureAtlasSprite> partTextures = sprites.get(baseTexture.toString()); if(partTextures == null) { continue; } String location = baseTexture.toString() + "_internal_gui"; // the texture created TextureAtlasSprite outlineTexture = new GuiOutlineTexture(base, location); // add it to the loading list map.setTextureEntry(location, outlineTexture); partTextures.put("_internal_gui", outlineTexture); } catch(Exception e) { log.error(e); } } } public static String getItemLoc(String res) { ResourceLocation loc = new ResourceLocation(res); return String.format("%s:items/%s", loc.getResourceDomain(), loc.getResourcePath()); } public static boolean exists(String res) { try { ResourceLocation loc = new ResourceLocation(res); loc = new ResourceLocation(loc.getResourceDomain(), "textures/" + loc.getResourcePath() + ".png"); Minecraft.getMinecraft().getResourceManager().getAllResources(loc); return true; } catch(IOException e) { return false; } } @Override public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) { // clear cache baseTextures.clear(); for(Map map : sprites.values()) { // safety in case there are some references lying around map.clear(); } sprites.clear(); } public static ResourceLocation getTextureLocationFromToolPart(IToolPart toolpart) throws Exception { if(!(toolpart instanceof Item)) { return null; // WHY?! } ResourceLocation modelLocation = Util.getItemLocation((Item) toolpart); IModel partModel = ModelLoaderRegistry.getModel(new ResourceLocation(modelLocation.getResourceDomain(), "item/parts/" + modelLocation .getResourcePath() + MaterialModelLoader.EXTENSION)); ResourceLocation partTexture = partModel.getTextures().iterator().next(); return partTexture; } public static boolean belongsToToolPart(ResourceLocation location) { for(IToolPart toolpart : TinkerRegistry.getToolParts()) { if(!(toolpart instanceof Item)) { continue; // WHY?! } try { Optional<ResourceLocation> storedResourceLocation = MaterialModelLoader.getToolPartModelLocation(toolpart); if(storedResourceLocation.isPresent()) { ResourceLocation stored = storedResourceLocation.get(); ResourceLocation modelLocation = new ResourceLocation(stored.getResourceDomain(), "item/" + stored.getResourcePath()); IModel partModel = ModelLoaderRegistry.getModel(modelLocation); // the actual texture of the part ResourceLocation baseTexture = partModel.getTextures().iterator().next(); if(baseTexture.toString().equals(location.toString())) { return true; } } } catch(Exception e) { return false; } } return false; } static { guiMaterial = new MaterialGUI("_internal_gui"); guiMaterial.setRenderInfo(new MaterialRenderInfo.AbstractMaterialRenderInfo() { @Override public TextureAtlasSprite getTexture(TextureAtlasSprite baseTexture, String location) { return new GuiOutlineTexture(baseTexture, location); } }); } }