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.src.ModTextureStatic; 023 import net.minecraft.src.RenderEngine; 024 import net.minecraft.src.TextureFX; 025 import net.minecraft.src.TexturePackBase; 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 163 effect.bindImage(client.renderEngine); 164 165 id = GL11.glGetInteger(GL_TEXTURE_BINDING_2D); 166 167 GL11.glBindTexture(GL_TEXTURE_2D, old); 168 169 effectTextures.put(effect, id); 170 171 return id; 172 } 173 174 public void onTexturePackChange(RenderEngine engine, TexturePackBase texturepack, List<TextureFX> effects) 175 { 176 pruneOldTextureFX(texturepack, effects); 177 178 for (TextureFX tex : effects) 179 { 180 if (tex instanceof ITextureFX) 181 { 182 ((ITextureFX)tex).onTexturePackChanged(engine, texturepack, getTextureDimensions(tex)); 183 } 184 } 185 186 loadTextures(texturepack); 187 } 188 189 private HashMap<Integer, Dimension> textureDims = new HashMap<Integer, Dimension>(); 190 private IdentityHashMap<TextureFX, Integer> effectTextures = new IdentityHashMap<TextureFX, Integer>(); 191 private TexturePackBase earlyTexturePack; 192 public void setTextureDimensions(int id, int width, int height, List<TextureFX> effects) 193 { 194 Dimension dim = new Dimension(width, height); 195 textureDims.put(id, dim); 196 197 for (TextureFX tex : effects) 198 { 199 if (getEffectTexture(tex) == id && tex instanceof ITextureFX) 200 { 201 ((ITextureFX)tex).onTextureDimensionsUpdate(width, height); 202 } 203 } 204 } 205 206 public Dimension getTextureDimensions(TextureFX effect) 207 { 208 return getTextureDimensions(getEffectTexture(effect)); 209 } 210 211 public Dimension getTextureDimensions(int id) 212 { 213 return textureDims.get(id); 214 } 215 216 /** 217 * @param anim 218 */ 219 public void addAnimation(TextureFX anim) 220 { 221 OverrideInfo info=new OverrideInfo(); 222 info.index=anim.iconIndex; 223 info.imageIndex=anim.tileImage; 224 info.textureFX=anim; 225 if (animationSet.contains(info)) { 226 animationSet.remove(info); 227 } 228 animationSet.add(info); 229 } 230 231 232 /** 233 * @param p_6531_1_ 234 */ 235 public void loadTextures(TexturePackBase texturePack) 236 { 237 registerTextureOverrides(client.renderEngine); 238 } 239 240 241 public void registerTextureOverrides(RenderEngine renderer) { 242 for (OverrideInfo animationOverride : animationSet) { 243 renderer.registerTextureFX(animationOverride.textureFX); 244 addedTextureFX.add(animationOverride.textureFX); 245 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)); 246 } 247 248 for (String fileToOverride : overrideInfo.keySet()) { 249 for (OverrideInfo override : overrideInfo.get(fileToOverride)) { 250 try 251 { 252 BufferedImage image=loadImageFromTexturePack(renderer, override.override); 253 ModTextureStatic mts=new ModTextureStatic(override.index, 1, override.texture, image); 254 renderer.registerTextureFX(mts); 255 addedTextureFX.add(mts); 256 FMLCommonHandler.instance().getFMLLogger().finer(String.format("Registered texture override %d (%d) on %s (%d)", override.index, mts.iconIndex, override.texture, mts.tileImage)); 257 } 258 catch (IOException e) 259 { 260 FMLCommonHandler.instance().getFMLLogger().throwing("FMLClientHandler", "registerTextureOverrides", e); 261 } 262 } 263 } 264 } 265 266 /** 267 * @param mod 268 */ 269 protected void registerAnimatedTexturesFor(ModContainer mod) 270 { 271 } 272 273 /** 274 * @param field_6539_c 275 */ 276 public void onEarlyTexturePackLoad(TexturePackBase fallback) 277 { 278 if (client==null) { 279 // We're far too early- let's wait 280 this.earlyTexturePack = fallback; 281 } else { 282 loadTextures(fallback); 283 } 284 } 285 286 287 public void pruneOldTextureFX(TexturePackBase var1, List<TextureFX> effects) 288 { 289 ListIterator<TextureFX> li = addedTextureFX.listIterator(); 290 while (li.hasNext()) 291 { 292 TextureFX tex = li.next(); 293 if (tex instanceof FMLTextureFX) 294 { 295 if (((FMLTextureFX)tex).unregister(client.renderEngine, effects)) 296 { 297 li.remove(); 298 } 299 } 300 else 301 { 302 effects.remove(tex); 303 li.remove(); 304 } 305 } 306 } 307 public void addNewTextureOverride(String textureToOverride, String overridingTexturePath, int location) { 308 OverrideInfo info = new OverrideInfo(); 309 info.index = location; 310 info.override = overridingTexturePath; 311 info.texture = textureToOverride; 312 overrideInfo.put(textureToOverride, info); 313 FMLLog.fine("Overriding %s @ %d with %s. %d slots remaining",textureToOverride, location, overridingTexturePath, SpriteHelper.freeSlotCount(textureToOverride)); 314 } 315 316 /** 317 * @param renderEngine 318 * @param path 319 * @return 320 */ 321 public BufferedImage loadImageFromTexturePack(RenderEngine renderEngine, String path) throws IOException 322 { 323 InputStream image=client.texturePackList.getSelectedTexturePack().getResourceAsStream(path); 324 if (image==null) { 325 throw new RuntimeException(String.format("The requested image path %s is not found",path)); 326 } 327 BufferedImage result=ImageIO.read(image); 328 if (result==null) 329 { 330 throw new RuntimeException(String.format("The requested image path %s appears to be corrupted",path)); 331 } 332 return result; 333 } 334 335 public static TextureFXManager instance() 336 { 337 return INSTANCE; 338 } 339 340 }