001 package cpw.mods.fml.client; 002 003 import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; 004 import static org.lwjgl.opengl.GL11.GL_TEXTURE_BINDING_2D; 005 006 import java.awt.Dimension; 007 import java.awt.image.BufferedImage; 008 import java.io.IOException; 009 import java.io.InputStream; 010 import java.nio.ByteBuffer; 011 import java.util.ArrayList; 012 import java.util.HashMap; 013 import java.util.HashSet; 014 import java.util.IdentityHashMap; 015 import java.util.List; 016 import java.util.ListIterator; 017 import java.util.Map; 018 019 import javax.imageio.ImageIO; 020 021 import net.minecraft.client.Minecraft; 022 import net.minecraft.client.renderer.RenderEngine; 023 import net.minecraft.client.renderer.texturefx.TextureFX; 024 import net.minecraft.client.texturepacks.ITexturePack; 025 import net.minecraft.src.ModTextureStatic; 026 027 import org.lwjgl.opengl.GL11; 028 029 import com.google.common.collect.ArrayListMultimap; 030 import com.google.common.collect.Maps; 031 import com.google.common.collect.Multimap; 032 033 import cpw.mods.fml.common.FMLCommonHandler; 034 import cpw.mods.fml.common.FMLLog; 035 import cpw.mods.fml.common.ModContainer; 036 037 public class TextureFXManager 038 { 039 private static final TextureFXManager INSTANCE = new TextureFXManager(); 040 041 private class TextureProperties 042 { 043 private int textureId; 044 private Dimension dim; 045 } 046 047 private Map<Integer,TextureProperties> textureProperties = Maps.newHashMap(); 048 private Multimap<String, OverrideInfo> overrideInfo = ArrayListMultimap.create(); 049 private HashSet<OverrideInfo> animationSet = new HashSet<OverrideInfo>(); 050 051 private List<TextureFX> addedTextureFX = new ArrayList<TextureFX>(); 052 053 private Minecraft client; 054 055 void setClient(Minecraft client) 056 { 057 this.client = client; 058 } 059 060 public boolean onUpdateTextureEffect(TextureFX effect) 061 { 062 ITextureFX ifx = (effect instanceof ITextureFX ? ((ITextureFX)effect) : null); 063 064 if (ifx != null && ifx.getErrored()) 065 { 066 return false; 067 } 068 069 String name = effect.getClass().getSimpleName(); 070 client.mcProfiler.startSection(name); 071 try 072 { 073 if (!FMLClientHandler.instance().hasOptifine()) 074 { 075 effect.onTick(); 076 } 077 } 078 catch (Exception e) 079 { 080 FMLLog.warning("Texture FX %s has failed to animate. Likely caused by a texture pack change that they did not respond correctly to", name); 081 if (ifx != null) 082 { 083 ifx.setErrored(true); 084 } 085 client.mcProfiler.endSection(); 086 return false; 087 } 088 client.mcProfiler.endSection(); 089 090 if (ifx != null) 091 { 092 Dimension dim = getTextureDimensions(effect); 093 int target = ((dim.width >> 4) * (dim.height >> 4)) << 2; 094 if (effect.imageData.length != target) 095 { 096 FMLLog.warning("Detected a texture FX sizing discrepancy in %s (%d, %d)", name, effect.imageData.length, target); 097 ifx.setErrored(true); 098 return false; 099 } 100 } 101 return true; 102 } 103 104 //Quick and dirty image scaling, no smoothing or fanciness, meant for speed as it will be called every tick. 105 public void scaleTextureFXData(byte[] data, ByteBuffer buf, int target, int length) 106 { 107 int sWidth = (int)Math.sqrt(data.length / 4); 108 int factor = target / sWidth; 109 byte[] tmp = new byte[4]; 110 111 buf.clear(); 112 113 if (factor > 1) 114 { 115 for (int y = 0; y < sWidth; y++) 116 { 117 int sRowOff = sWidth * y; 118 int tRowOff = target * y * factor; 119 for (int x = 0; x < sWidth; x++) 120 { 121 int sPos = (x + sRowOff) * 4; 122 tmp[0] = data[sPos + 0]; 123 tmp[1] = data[sPos + 1]; 124 tmp[2] = data[sPos + 2]; 125 tmp[3] = data[sPos + 3]; 126 127 int tPosTop = (x * factor) + tRowOff; 128 for (int y2 = 0; y2 < factor; y2++) 129 { 130 buf.position((tPosTop + (y2 * target)) * 4); 131 for (int x2 = 0; x2 < factor; x2++) 132 { 133 buf.put(tmp); 134 } 135 } 136 } 137 } 138 } 139 140 buf.position(0).limit(length); 141 } 142 143 public void onPreRegisterEffect(TextureFX effect) 144 { 145 Dimension dim = getTextureDimensions(effect); 146 if (effect instanceof ITextureFX) 147 { 148 ((ITextureFX)effect).onTextureDimensionsUpdate(dim.width, dim.height); 149 } 150 } 151 152 153 public int getEffectTexture(TextureFX effect) 154 { 155 Integer id = effectTextures.get(effect); 156 if (id != null) 157 { 158 return id; 159 } 160 161 int old = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); 162 effect.bindImage(client.renderEngine); 163 id = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); 164 GL11.glBindTexture(GL_TEXTURE_2D, old); 165 effectTextures.put(effect, id); 166 effect.textureId = id; 167 return id; 168 } 169 170 public void onTexturePackChange(RenderEngine engine, ITexturePack texturepack, List<TextureFX> effects) 171 { 172 pruneOldTextureFX(texturepack, effects); 173 174 for (TextureFX tex : effects) 175 { 176 if (tex instanceof ITextureFX) 177 { 178 ((ITextureFX)tex).onTexturePackChanged(engine, texturepack, getTextureDimensions(tex)); 179 } 180 } 181 182 loadTextures(texturepack); 183 } 184 185 private HashMap<Integer, Dimension> textureDims = new HashMap<Integer, Dimension>(); 186 private IdentityHashMap<TextureFX, Integer> effectTextures = new IdentityHashMap<TextureFX, Integer>(); 187 private ITexturePack earlyTexturePack; 188 public void setTextureDimensions(int id, int width, int height, List<TextureFX> effects) 189 { 190 Dimension dim = new Dimension(width, height); 191 textureDims.put(id, dim); 192 193 for (TextureFX tex : effects) 194 { 195 if (getEffectTexture(tex) == id && tex instanceof ITextureFX) 196 { 197 ((ITextureFX)tex).onTextureDimensionsUpdate(width, height); 198 } 199 } 200 } 201 202 public Dimension getTextureDimensions(TextureFX effect) 203 { 204 return getTextureDimensions(getEffectTexture(effect)); 205 } 206 207 public Dimension getTextureDimensions(int id) 208 { 209 return textureDims.get(id); 210 } 211 212 public void addAnimation(TextureFX anim) 213 { 214 OverrideInfo info=new OverrideInfo(); 215 info.index=anim.iconIndex; 216 info.imageIndex=anim.tileImage; 217 info.textureFX=anim; 218 if (animationSet.contains(info)) { 219 animationSet.remove(info); 220 } 221 animationSet.add(info); 222 } 223 224 225 public void loadTextures(ITexturePack texturePack) 226 { 227 registerTextureOverrides(client.renderEngine); 228 } 229 230 231 public void registerTextureOverrides(RenderEngine renderer) { 232 for (OverrideInfo animationOverride : animationSet) { 233 renderer.registerTextureFX(animationOverride.textureFX); 234 addedTextureFX.add(animationOverride.textureFX); 235 FMLCommonHandler.instance().getFMLLogger().finer(String.format("Registered texture override %d (%d) on %s (%d)", animationOverride.index, animationOverride.textureFX.iconIndex, animationOverride.textureFX.getClass().getSimpleName(), animationOverride.textureFX.tileImage)); 236 } 237 238 for (String fileToOverride : overrideInfo.keySet()) { 239 for (OverrideInfo override : overrideInfo.get(fileToOverride)) { 240 try 241 { 242 BufferedImage image=loadImageFromTexturePack(renderer, override.override); 243 ModTextureStatic mts=new ModTextureStatic(override.index, 1, override.texture, image); 244 renderer.registerTextureFX(mts); 245 addedTextureFX.add(mts); 246 FMLCommonHandler.instance().getFMLLogger().finer(String.format("Registered texture override %d (%d) on %s (%d)", override.index, mts.iconIndex, override.texture, mts.tileImage)); 247 } 248 catch (IOException e) 249 { 250 FMLCommonHandler.instance().getFMLLogger().throwing("FMLClientHandler", "registerTextureOverrides", e); 251 } 252 } 253 } 254 } 255 256 protected void registerAnimatedTexturesFor(ModContainer mod) 257 { 258 } 259 260 public void onEarlyTexturePackLoad(ITexturePack fallback) 261 { 262 if (client==null) { 263 // We're far too early- let's wait 264 this.earlyTexturePack = fallback; 265 } else { 266 loadTextures(fallback); 267 } 268 } 269 270 271 public void pruneOldTextureFX(ITexturePack var1, List<TextureFX> effects) 272 { 273 ListIterator<TextureFX> li = addedTextureFX.listIterator(); 274 while (li.hasNext()) 275 { 276 TextureFX tex = li.next(); 277 if (tex instanceof FMLTextureFX) 278 { 279 if (((FMLTextureFX)tex).unregister(client.renderEngine, effects)) 280 { 281 li.remove(); 282 } 283 } 284 else 285 { 286 effects.remove(tex); 287 li.remove(); 288 } 289 } 290 } 291 public void addNewTextureOverride(String textureToOverride, String overridingTexturePath, int location) { 292 OverrideInfo info = new OverrideInfo(); 293 info.index = location; 294 info.override = overridingTexturePath; 295 info.texture = textureToOverride; 296 overrideInfo.put(textureToOverride, info); 297 FMLLog.fine("Overriding %s @ %d with %s. %d slots remaining",textureToOverride, location, overridingTexturePath, SpriteHelper.freeSlotCount(textureToOverride)); 298 } 299 300 public BufferedImage loadImageFromTexturePack(RenderEngine renderEngine, String path) throws IOException 301 { 302 InputStream image=client.texturePackList.getSelectedTexturePack().getResourceAsStream(path); 303 if (image==null) { 304 throw new RuntimeException(String.format("The requested image path %s is not found",path)); 305 } 306 BufferedImage result=ImageIO.read(image); 307 if (result==null) 308 { 309 throw new RuntimeException(String.format("The requested image path %s appears to be corrupted",path)); 310 } 311 return result; 312 } 313 314 public static TextureFXManager instance() 315 { 316 return INSTANCE; 317 } 318 319 }