/* * Copyright (C) 2022 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * * NotEnoughUpdates is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * NotEnoughUpdates is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see . */ package io.github.moulberry.notenoughupdates.miscfeatures; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import io.github.moulberry.notenoughupdates.NotEnoughUpdates; import net.minecraft.client.Minecraft; import net.minecraft.client.model.ModelHumanoidHead; import net.minecraft.client.model.ModelSkeletonHead; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockPart; import net.minecraft.client.renderer.block.model.BlockPartFace; import net.minecraft.client.renderer.block.model.FaceBakery; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ModelBlock; import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.renderer.texture.IIconCreator; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.client.renderer.texture.TextureUtil; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.resources.model.IBakedModel; import net.minecraft.client.resources.model.ModelRotation; import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; public class CustomSkulls implements IResourceManagerReloadListener { private static final CustomSkulls INSTANCE = new CustomSkulls(); public static CustomSkulls getInstance() { return INSTANCE; } private final ResourceLocation atlas = new ResourceLocation("notenoughupdates", "custom_skull_textures_atlas"); private final ResourceLocation configuration = new ResourceLocation( "notenoughupdates", "custom_skull_textures/customskull.json"); private final ResourceLocation configuration2 = new ResourceLocation( "notenoughupdates", "custom_skull_textures/customskull2.json"); protected final TextureMap textureMap = new TextureMap("custom_skull_textures"); public static ItemCameraTransforms.TransformType mostRecentTransformType = ItemCameraTransforms.TransformType.NONE; protected final Map sprites = Maps.newHashMap(); private final FaceBakery faceBakery = new FaceBakery(); private final ModelSkeletonHead humanoidHead = new ModelHumanoidHead(); private final HashMap customSkulls = new HashMap<>(); private final HashMap customSkulls2 = new HashMap<>(); private final Gson gson = new GsonBuilder().create(); private static class CustomSkull { private ModelBlock model; private IBakedModel modelBaked; private ResourceLocation texture; } private void processConfig(ResourceLocation config, Map map, Map cache) { map.clear(); try ( BufferedReader reader = new BufferedReader(new InputStreamReader( Minecraft.getMinecraft().getResourceManager().getResource(config).getInputStream(), StandardCharsets.UTF_8 )) ) { JsonObject json = gson.fromJson(reader, JsonObject.class); if (json == null) return; for (Map.Entry entry : json.entrySet()) { if (entry.getValue().isJsonObject()) { JsonObject obj = entry.getValue().getAsJsonObject(); CustomSkull skull; if (obj.has("model")) { ResourceLocation loc = new ResourceLocation( "notenoughupdates", "custom_skull_textures/" + obj.get("model").getAsString() + ".json" ); if ((skull = cache.get(loc.toString())) == null) { skull = new CustomSkull(); skull.model = ModelBlock.deserialize(new InputStreamReader(Minecraft .getMinecraft() .getResourceManager() .getResource(loc) .getInputStream())); cache.put(loc.toString(), skull); } map.put(entry.getKey(), skull); } else if (obj.has("texture")) { String location = obj.get("texture").getAsString(); ResourceLocation loc = new ResourceLocation( "notenoughupdates", "custom_skull_textures/" + location + ".png" ); if ((skull = cache.get(loc.toString())) == null) { skull = new CustomSkull(); skull.texture = loc; Minecraft.getMinecraft().getTextureManager().deleteTexture(skull.texture); cache.put(loc.toString(), skull); } map.put(entry.getKey(), skull); } } } } catch (Exception ignored) {} } @Override public void onResourceManagerReload(IResourceManager resourceManager) { Map skullCache = new HashMap<>(); processConfig(configuration, customSkulls, skullCache); processConfig(configuration2, customSkulls2, skullCache); if (customSkulls.isEmpty() && customSkulls2.isEmpty()) return; try { loadSprites(skullCache.values()); skullCache.values().forEach( (CustomSkull skull) -> { if (skull.model != null) { skull.modelBaked = bakeModel(skull.model, ModelRotation.X0_Y0, false); } } ); Minecraft.getMinecraft().getTextureManager().loadTexture(atlas, textureMap); } catch (Exception ignored) {} } private void loadSprites(Collection models) { final Set set = this.getAllTextureLocations(models); set.remove(TextureMap.LOCATION_MISSING_TEXTURE); IIconCreator iiconcreator = iconRegistry -> { for (ResourceLocation resourcelocation : set) { TextureAtlasSprite textureatlassprite = iconRegistry.registerSprite(resourcelocation); CustomSkulls.this.sprites.put(resourcelocation, textureatlassprite); } }; this.textureMap.loadSprites(Minecraft.getMinecraft().getResourceManager(), iiconcreator); this.sprites.put(new ResourceLocation("missingno"), this.textureMap.getMissingSprite()); } protected Set getAllTextureLocations(Collection models) { Set set = new HashSet<>(); for (CustomSkull skull : models) { if (skull.model != null) { set.addAll(getTextureLocations(skull.model)); } } return set; } protected Set getTextureLocations(ModelBlock modelBlock) { Set set = Sets.newHashSet(); for (BlockPart blockpart : modelBlock.getElements()) { for (BlockPartFace blockpartface : blockpart.mapFaces.values()) { ResourceLocation resourcelocation = new ResourceLocation( "notenoughupdates", modelBlock.resolveTextureName(blockpartface.texture) ); set.add(resourcelocation); } } set.add(new ResourceLocation("notenoughupdates", modelBlock.resolveTextureName("particle"))); return set; } protected IBakedModel bakeModel( ModelBlock modelBlockIn, net.minecraftforge.client.model.ITransformation modelRotationIn, boolean uvLocked ) { TextureAtlasSprite textureatlassprite = this.sprites.get(new ResourceLocation( "notenoughupdates", modelBlockIn.resolveTextureName("particle") )); SimpleBakedModel.Builder simplebakedmodel$builder = (new SimpleBakedModel.Builder(modelBlockIn)).setTexture( textureatlassprite); for (BlockPart blockpart : modelBlockIn.getElements()) { for (EnumFacing enumfacing : blockpart.mapFaces.keySet()) { BlockPartFace blockpartface = blockpart.mapFaces.get(enumfacing); TextureAtlasSprite textureatlassprite1 = this.sprites.get(new ResourceLocation( "notenoughupdates", modelBlockIn.resolveTextureName(blockpartface.texture) )); if (blockpartface.cullFace == null || !net.minecraftforge.client.model.TRSRTransformation.isInteger( modelRotationIn.getMatrix())) { simplebakedmodel$builder.addGeneralQuad(this.makeBakedQuad( blockpart, blockpartface, textureatlassprite1, enumfacing, modelRotationIn, uvLocked )); } else { simplebakedmodel$builder.addFaceQuad( modelRotationIn.rotate(blockpartface.cullFace), this.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, enumfacing, modelRotationIn, uvLocked) ); } } } return simplebakedmodel$builder.makeBakedModel(); } private BakedQuad makeBakedQuad( BlockPart p_177589_1_, BlockPartFace p_177589_2_, TextureAtlasSprite p_177589_3_, EnumFacing p_177589_4_, ModelRotation p_177589_5_, boolean p_177589_6_ ) { return makeBakedQuad( p_177589_1_, p_177589_2_, p_177589_3_, p_177589_4_, (net.minecraftforge.client.model.ITransformation) p_177589_5_, p_177589_6_ ); } protected BakedQuad makeBakedQuad( BlockPart p_177589_1_, BlockPartFace p_177589_2_, TextureAtlasSprite p_177589_3_, EnumFacing p_177589_4_, net.minecraftforge.client.model.ITransformation p_177589_5_, boolean p_177589_6_ ) { return this.faceBakery.makeBakedQuad( p_177589_1_.positionFrom, p_177589_1_.positionTo, p_177589_2_, p_177589_3_, p_177589_4_, p_177589_5_, p_177589_1_.partRotation, p_177589_6_, p_177589_1_.shade ); } private void renderModel(IBakedModel model, int color) { Tessellator tessellator = Tessellator.getInstance(); WorldRenderer worldrenderer = tessellator.getWorldRenderer(); worldrenderer.begin(7, DefaultVertexFormats.ITEM); for (EnumFacing enumfacing : EnumFacing.values()) { this.renderQuads(worldrenderer, model.getFaceQuads(enumfacing), color); } this.renderQuads(worldrenderer, model.getGeneralQuads(), color); tessellator.draw(); } private void renderQuads(WorldRenderer renderer, List quads, int color) { for (BakedQuad quad : quads) { net.minecraftforge.client.model.pipeline.LightUtil.renderQuadColor(renderer, quad, color); } } public boolean renderSkull( float xOffset, float yOffset, float zOffset, EnumFacing placedDirection, float rotationDeg, int skullType, GameProfile skullOwner, int damage ) { if (NotEnoughUpdates.INSTANCE.config.misc.disableSkullRetexturing) { return false; } if (skullType != 3) { return false; } if (skullOwner == null || skullOwner.getId() == null) { return false; } CustomSkull skull = customSkulls.get(skullOwner.getId().toString()); if (skull == null) { try { skull = customSkulls2.get(skullOwner.getProperties().get("textures").iterator().next().getValue()); if (skull == null) return false; } catch (NoSuchElementException e) { return false; } } if (skull.modelBaked != null && skull.model != null) { Minecraft.getMinecraft().getTextureManager().bindTexture(atlas); GlStateManager.pushMatrix(); GlStateManager.disableCull(); GlStateManager.enableLighting(); final float rot; switch (placedDirection) { case NORTH: { GlStateManager.translate(xOffset + 0.5f, yOffset + 0.25f, zOffset + 0.74f); rot = 0f; break; } case SOUTH: { GlStateManager.translate(xOffset + 0.5f, yOffset + 0.25f, zOffset + 0.26f); rot = 180.0f; break; } case WEST: { GlStateManager.translate(xOffset + 0.74f, yOffset + 0.25f, zOffset + 0.5f); rot = 270.0f; break; } case EAST: { GlStateManager.translate(xOffset + 0.26f, yOffset + 0.25f, zOffset + 0.5f); rot = 90.0f; break; } default: { GlStateManager.translate(xOffset + 0.5f, yOffset, zOffset + 0.5f); rot = rotationDeg; } } GlStateManager.enableRescaleNormal(); GlStateManager.enableAlpha(); GlStateManager.rotate(rot, 0, 1, 0); GlStateManager.translate(0, 0.25f, 0); if (placedDirection == EnumFacing.UP && xOffset == -0.5 && yOffset == 0 && zOffset == -0.5 && rotationDeg == 180) { skull.model.getAllTransforms().applyTransform(ItemCameraTransforms.TransformType.HEAD); } else { skull.model.getAllTransforms().applyTransform(mostRecentTransformType); } GlStateManager.translate(-0.5f, 0, -0.5f); renderModel(skull.modelBaked, 0xffffffff); GlStateManager.popMatrix(); } else if (skull.texture != null) { if (Minecraft.getMinecraft().getTextureManager().getTexture(skull.texture) == null) { try { BufferedImage image = ImageIO.read(Minecraft .getMinecraft() .getResourceManager() .getResource(skull.texture) .getInputStream()); int size = Math.max(image.getHeight(), image.getWidth()); Minecraft.getMinecraft().getTextureManager().loadTexture(skull.texture, new AbstractTexture() { @Override public void loadTexture(IResourceManager resourceManager) { TextureUtil.allocateTexture(this.getGlTextureId(), size, size); int[] rgb = new int[size * size]; image.getRGB(0, 0, image.getWidth(), image.getHeight(), rgb, 0, image.getWidth()); TextureUtil.uploadTexture(this.getGlTextureId(), rgb, size, size); } }); } catch (IOException ignored) { } } Minecraft.getMinecraft().getTextureManager().bindTexture(skull.texture); GlStateManager.pushMatrix(); GlStateManager.disableCull(); GlStateManager.translate(xOffset + 0.5F, yOffset, zOffset + 0.5F); GlStateManager.enableRescaleNormal(); GlStateManager.scale(-1.0F, -1.0F, 1.0F); GlStateManager.enableAlpha(); humanoidHead.render(null, 0.0F, 0.0F, 0.0F, rotationDeg, 0.0F, 0.0625F); GlStateManager.popMatrix(); } else { return false; } return true; } }