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