cpw.mods.fml.client.FMLClientHandler.java Source code

Java tutorial

Introduction

Here is the source code for cpw.mods.fml.client.FMLClientHandler.java

Source

/*
 * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
 *
 * This library 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 2.1 of the License, or any later version.
 *
 * This library 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 this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
package cpw.mods.fml.client;

import static org.lwjgl.opengl.GL11.*;

import java.awt.image.BufferedImage;
import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import org.lwjgl.opengl.GL11;

import net.minecraft.client.Minecraft;
import net.minecraft.src.BaseMod;
import net.minecraft.src.BiomeGenBase;
import net.minecraft.src.Block;
import net.minecraft.src.ClientRegistry;
import net.minecraft.src.EntityItem;
import net.minecraft.src.EntityPlayer;
import net.minecraft.src.GameSettings;
import net.minecraft.src.GuiScreen;
import net.minecraft.src.IBlockAccess;
import net.minecraft.src.IChunkProvider;
import net.minecraft.src.IInventory;
import net.minecraft.src.Item;
import net.minecraft.src.ItemStack;
import net.minecraft.src.KeyBinding;
import net.minecraft.src.MLProp;
import net.minecraft.src.ModTextureStatic;
import net.minecraft.src.NetClientHandler;
import net.minecraft.src.NetworkManager;
import net.minecraft.src.Packet;
import net.minecraft.src.Packet1Login;
import net.minecraft.src.Packet250CustomPayload;
import net.minecraft.src.Packet3Chat;
import net.minecraft.src.Profiler;
import net.minecraft.src.Render;
import net.minecraft.src.RenderBlocks;
import net.minecraft.src.RenderEngine;
import net.minecraft.src.RenderManager;
import net.minecraft.src.RenderPlayer;
import net.minecraft.src.SidedProxy;
import net.minecraft.src.StringTranslate;
import net.minecraft.src.TextureFX;
import net.minecraft.src.TexturePackBase;
import net.minecraft.src.World;
import net.minecraft.src.WorldType;
import argo.jdom.JdomParser;
import argo.jdom.JsonNode;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.FMLModLoaderContainer;
import cpw.mods.fml.common.IFMLSidedHandler;
import cpw.mods.fml.common.IKeyHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.ModMetadata;
import cpw.mods.fml.common.ProxyInjector;
import cpw.mods.fml.common.ReflectionHelper;
import cpw.mods.fml.common.Side;
import cpw.mods.fml.common.TickType;
import cpw.mods.fml.common.modloader.ModLoaderHelper;
import cpw.mods.fml.common.modloader.ModLoaderModContainer;
import cpw.mods.fml.common.modloader.ModProperty;
import cpw.mods.fml.common.registry.FMLRegistry;

/**
 * Handles primary communication from hooked code into the system
 * 
 * The FML entry point is {@link #onPreLoad(MinecraftServer)} called from
 * {@link MinecraftServer}
 * 
 * Obfuscated code should focus on this class and other members of the "server"
 * (or "client") code
 * 
 * The actual mod loading is handled at arms length by {@link Loader}
 * 
 * It is expected that a similar class will exist for each target environment:
 * Bukkit and Client side.
 * 
 * It should not be directly modified.
 * 
 * @author cpw
 * 
 */
public class FMLClientHandler implements IFMLSidedHandler {
    /**
     * The singleton
     */
    private static final FMLClientHandler INSTANCE = new FMLClientHandler();

    /**
     * A reference to the server itself
     */
    private Minecraft client;

    /**
     * A handy list of the default overworld biomes
     */
    private BiomeGenBase[] defaultOverworldBiomes;

    private int nextRenderId = 30;

    private TexturePackBase fallbackTexturePack;

    private NetClientHandler networkClient;

    private ModContainer animationCallbackMod;

    // Cached lookups
    private HashMap<String, ArrayList<OverrideInfo>> overrideInfo = new HashMap<String, ArrayList<OverrideInfo>>();
    private HashMap<Integer, BlockRenderInfo> blockModelIds = new HashMap<Integer, BlockRenderInfo>();
    private HashMap<KeyBinding, ModContainer> keyBindings = new HashMap<KeyBinding, ModContainer>();
    private List<IKeyHandler> keyHandlers = new ArrayList<IKeyHandler>();
    private HashSet<OverrideInfo> animationSet = new HashSet<OverrideInfo>();

    private List<TextureFX> addedTextureFX = new ArrayList<TextureFX>();

    private boolean firstTick;
    /**
     * Called to start the whole game off from
     * {@link MinecraftServer#startServer}
     * 
     * @param minecraftServer
     */

    private OptifineModContainer optifineContainer;

    public void onPreLoad(Minecraft minecraft) {
        client = minecraft;
        ReflectionHelper.detectObfuscation(World.class);
        FMLCommonHandler.instance().beginLoading(this);
        FMLRegistry.registerRegistry(new ClientRegistry());
        try {
            Class<?> optifineConfig = Class.forName("Config", false, Loader.instance().getModClassLoader());
            optifineContainer = new OptifineModContainer(optifineConfig);
        } catch (Exception e) {
            // OPTIFINE not found
            optifineContainer = null;
        }
        if (optifineContainer != null) {
            ModMetadata optifineMetadata;
            try {
                optifineMetadata = readMetadataFrom(
                        Loader.instance().getModClassLoader().getResourceAsStream("optifinemod.info"),
                        optifineContainer);
                optifineContainer.setMetadata(optifineMetadata);
            } catch (Exception e) {
                //not available
            }
            FMLCommonHandler.instance().getFMLLogger()
                    .info(String.format(
                            "Forge Mod Loader has detected optifine %s, enabling compatibility features",
                            optifineContainer.getVersion()));
        }
        Loader.instance().loadMods();
    }

    /**
     * Called a bit later on during initialization to finish loading mods
     * Also initializes key bindings
     * 
     */
    public void onLoadComplete() {
        Loader.instance().initializeMods();
        for (ModContainer mod : Loader.getModList()) {
            mod.gatherRenderers(RenderManager.instance.getRendererList());
            for (Render r : RenderManager.instance.getRendererList().values()) {
                r.setRenderManager(RenderManager.instance);
            }
        }
        // Load the key bindings into the settings table

        GameSettings gs = client.gameSettings;
        KeyBinding[] modKeyBindings = harvestKeyBindings();
        KeyBinding[] allKeys = new KeyBinding[gs.keyBindings.length + modKeyBindings.length];
        System.arraycopy(gs.keyBindings, 0, allKeys, 0, gs.keyBindings.length);
        System.arraycopy(modKeyBindings, 0, allKeys, gs.keyBindings.length, modKeyBindings.length);
        gs.keyBindings = allKeys;
        gs.loadOptions();

        // Mark this as a "first tick"

        firstTick = true;
    }

    public KeyBinding[] harvestKeyBindings() {
        List<IKeyHandler> allKeys = FMLCommonHandler.instance().gatherKeyBindings();
        KeyBinding[] keys = new KeyBinding[allKeys.size()];
        int i = 0;
        for (IKeyHandler key : allKeys) {
            keys[i++] = (KeyBinding) key.getKeyBinding();
            keyBindings.put((KeyBinding) key.getKeyBinding(), key.getOwningContainer());
        }
        keyHandlers = allKeys;
        return keys;
    }

    /**
     * Every tick just before world and other ticks occur
     */
    public void onPreWorldTick() {
        if (client.theWorld != null) {
            // For the client world ticks and game ticks are the same
            FMLCommonHandler.instance().tickStart(EnumSet.of(TickType.WORLD, TickType.GAME, TickType.WORLDGUI),
                    0.0f, client.currentScreen, client.theWorld);
        }
    }

    /**
     * Every tick just after world and other ticks occur
     */
    public void onPostWorldTick() {
        if (client.theWorld != null) {
            // For the client world ticks and game ticks are the same
            FMLCommonHandler.instance().tickEnd(EnumSet.of(TickType.WORLD, TickType.GAME, TickType.WORLDGUI), 0.0f,
                    client.currentScreen, client.theWorld);
        }
        for (IKeyHandler entry : keyHandlers) {
            entry.onEndTick();
        }
    }

    public void onWorldLoadTick() {
        if (client.theWorld != null) {
            if (firstTick) {
                loadTextures(fallbackTexturePack);
                firstTick = false;
            }
            FMLCommonHandler.instance().tickStart(EnumSet.of(TickType.WORLDLOAD, TickType.GUILOAD));
        }
    }

    public void onRenderTickStart(float partialTickTime) {
        FMLCommonHandler.instance().tickStart(EnumSet.of(TickType.RENDER, TickType.GUI), partialTickTime,
                client.currentScreen);
    }

    public void onRenderTickEnd(float partialTickTime) {
        FMLCommonHandler.instance().tickEnd(EnumSet.of(TickType.RENDER, TickType.GUI), partialTickTime,
                client.currentScreen);
    }

    /**
     * Get the server instance
     * 
     * @return
     */
    public Minecraft getClient() {
        return client;
    }

    /**
     * Get a handle to the client's logger instance
     * The client actually doesn't have one- so we return null
     */
    public Logger getMinecraftLogger() {
        return null;
    }

    /**
     * Called from ChunkProvider when a chunk needs to be populated
     * 
     * To avoid polluting the worldgen seed, we generate a new random from the
     * world seed and generate a seed from that
     * 
     * @param chunkProvider
     * @param chunkX
     * @param chunkZ
     * @param world
     * @param generator
     */
    public void onChunkPopulate(IChunkProvider chunkProvider, int chunkX, int chunkZ, World world,
            IChunkProvider generator) {
        FMLCommonHandler.instance().handleWorldGeneration(chunkX, chunkZ, world.getSeed(), world, generator,
                chunkProvider);
    }

    /**
     * Is the offered class and instance of BaseMod and therefore a ModLoader
     * mod?
     */
    public boolean isModLoaderMod(Class<?> clazz) {
        return BaseMod.class.isAssignableFrom(clazz);
    }

    /**
     * Load the supplied mod class into a mod container
     */
    public ModContainer loadBaseModMod(Class<?> clazz, File canonicalFile) {
        @SuppressWarnings("unchecked")
        Class<? extends BaseMod> bmClazz = (Class<? extends BaseMod>) clazz;
        return new ModLoaderModContainer(bmClazz, canonicalFile);
    }

    /**
     * Called to notify that an item was picked up from the world
     * 
     * @param entityItem
     * @param entityPlayer
     */
    public void notifyItemPickup(EntityItem entityItem, EntityPlayer entityPlayer) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsPickupNotification()) {
                mod.getPickupNotifier().notifyPickup(entityItem, entityPlayer);
            }
        }
    }

    /**
     * Attempt to dispense the item as an entity other than just as a the item
     * itself
     * 
     * @param world
     * @param x
     * @param y
     * @param z
     * @param xVelocity
     * @param zVelocity
     * @param item
     * @return
     */
    public boolean tryDispensingEntity(World world, double x, double y, double z, byte xVelocity, byte zVelocity,
            ItemStack item) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsToDispense()
                    && mod.getDispenseHandler().dispense(x, y, z, xVelocity, zVelocity, world, item)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return the instance
     */
    public static FMLClientHandler instance() {
        return INSTANCE;
    }

    /**
     * Build a list of default overworld biomes
     * 
     * @return
     */
    public BiomeGenBase[] getDefaultOverworldBiomes() {
        if (defaultOverworldBiomes == null) {
            ArrayList<BiomeGenBase> biomes = new ArrayList<BiomeGenBase>(20);

            for (int i = 0; i < 23; i++) {
                if ("Sky".equals(BiomeGenBase.biomeList[i].biomeName)
                        || "Hell".equals(BiomeGenBase.biomeList[i].biomeName)) {
                    continue;
                }

                biomes.add(BiomeGenBase.biomeList[i]);
            }

            defaultOverworldBiomes = new BiomeGenBase[biomes.size()];
            biomes.toArray(defaultOverworldBiomes);
        }

        return defaultOverworldBiomes;
    }

    /**
     * Called when an item is crafted
     * 
     * @param player
     * @param craftedItem
     * @param craftingGrid
     */
    public void onItemCrafted(EntityPlayer player, ItemStack craftedItem, IInventory craftingGrid) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsCraftingNotification()) {
                mod.getCraftingHandler().onCrafting(player, craftedItem, craftingGrid);
            }
        }
    }

    /**
     * Called when an item is smelted
     * 
     * @param player
     * @param smeltedItem
     */
    public void onItemSmelted(EntityPlayer player, ItemStack smeltedItem) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsCraftingNotification()) {
                mod.getCraftingHandler().onSmelting(player, smeltedItem);
            }
        }
    }

    /**
     * Called when a chat packet is received
     * 
     * @param chat
     * @param player
     * @return true if you want the packet to stop processing and not echo to
     *         the rest of the world
     */
    public boolean handleChatPacket(Packet3Chat chat) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsNetworkPackets() && mod.getNetworkHandler().onChat(chat)) {
                return true;
            }
        }

        return false;
    }

    public void handleServerLogin(Packet1Login loginPacket, NetClientHandler handler,
            NetworkManager networkManager) {
        this.networkClient = handler;
        Packet250CustomPayload packet = new Packet250CustomPayload();
        packet.channel = "REGISTER";
        packet.data = FMLCommonHandler.instance().getPacketRegistry();
        packet.length = packet.data.length;
        if (packet.length > 0) {
            networkManager.addToSendQueue(packet);
        }
        for (ModContainer mod : Loader.getModList()) {
            mod.getNetworkHandler().onServerLogin(handler);
        }
    }

    /**
     * Called when a packet 250 packet is received from the player
     * 
     * @param packet
     * @param player
     */
    public void handlePacket250(Packet250CustomPayload packet) {
        if ("REGISTER".equals(packet.channel) || "UNREGISTER".equals(packet.channel)) {
            handleServerRegistration(packet);
            return;
        }

        ModContainer mod = FMLCommonHandler.instance().getModForChannel(packet.channel);

        if (mod != null) {
            mod.getNetworkHandler().onPacket250Packet(packet);
        }
    }

    /**
     * Handle register requests for packet 250 channels
     * 
     * @param packet
     */
    private void handleServerRegistration(Packet250CustomPayload packet) {
        if (packet.data == null) {
            return;
        }
        try {
            for (String channel : new String(packet.data, "UTF8").split("\0")) {
                // Skip it if we don't know it
                if (FMLCommonHandler.instance().getModForChannel(channel) == null) {
                    continue;
                }

                if ("REGISTER".equals(packet.channel)) {
                    FMLCommonHandler.instance().activateChannel(client.thePlayer, channel);
                } else {
                    FMLCommonHandler.instance().deactivateChannel(client.thePlayer, channel);
                }
            }
        } catch (UnsupportedEncodingException e) {
            getMinecraftLogger().warning("Received invalid registration packet");
        }
    }

    @Override
    public File getMinecraftRootDirectory() {
        return client.mcDataDir;
    }

    /**
     * @param player
     */
    public void announceLogout(EntityPlayer player) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsPlayerTracking()) {
                mod.getPlayerTracker().onPlayerLogout(player);
            }
        }
    }

    /**
     * @param p_28168_1_
     */
    public void announceDimensionChange(EntityPlayer player) {
        for (ModContainer mod : Loader.getModList()) {
            if (mod.wantsPlayerTracking()) {
                mod.getPlayerTracker().onPlayerChangedDimension(player);
            }
        }
    }

    /**
     * @param biome
     */
    public void addBiomeToDefaultWorldGenerator(BiomeGenBase biome) {
        WorldType.DEFAULT.addNewBiome(biome);
    }

    /**
     * Return the minecraft instance
     */
    @Override
    public Object getMinecraftInstance() {
        return client;
    }

    /* (non-Javadoc)
     * @see cpw.mods.fml.common.IFMLSidedHandler#getCurrentLanguage()
     */
    @Override
    public String getCurrentLanguage() {
        return StringTranslate.getInstance().getCurrentLanguage();
    }

    public Properties getCurrentLanguageTable() {
        return StringTranslate.getInstance().getTranslationTable();
    }

    /**
     * @param armor
     * @return
     */
    public int addNewArmourRendererPrefix(String armor) {
        return RenderPlayer.addNewArmourPrefix(armor);
    }

    public void addNewTextureOverride(String textureToOverride, String overridingTexturePath, int location) {
        if (!overrideInfo.containsKey(textureToOverride)) {
            overrideInfo.put(textureToOverride, new ArrayList<OverrideInfo>());
        }
        ArrayList<OverrideInfo> list = overrideInfo.get(textureToOverride);
        OverrideInfo info = new OverrideInfo();
        info.index = location;
        info.override = overridingTexturePath;
        info.texture = textureToOverride;
        list.add(info);
        FMLCommonHandler.instance().getFMLLogger().log(Level.FINE,
                String.format("Overriding %s @ %d with %s. %d slots remaining", textureToOverride, location,
                        overridingTexturePath, SpriteHelper.freeSlotCount(textureToOverride)));
    }

    /**
     * @param mod
     * @param inventoryRenderer
     * @return
     */
    public int obtainBlockModelIdFor(BaseMod mod, boolean inventoryRenderer) {
        ModLoaderModContainer mlmc = ModLoaderHelper.registerRenderHelper(mod);
        int renderId = nextRenderId++;
        BlockRenderInfo bri = new BlockRenderInfo(renderId, inventoryRenderer, mlmc);
        blockModelIds.put(renderId, bri);
        return renderId;
    }

    /**
     * @param renderEngine
     * @param path
     * @return
     */
    public BufferedImage loadImageFromTexturePack(RenderEngine renderEngine, String path) throws IOException {
        InputStream image = client.texturePackList.selectedTexturePack.getResourceAsStream(path);
        if (image == null) {
            throw new RuntimeException(String.format("The requested image path %s is not found", path));
        }
        BufferedImage result = ImageIO.read(image);
        if (result == null) {
            throw new RuntimeException(String.format("The requested image path %s appears to be corrupted", path));
        }
        return result;
    }

    /**
     * @param player
     * @param gui
     */
    public void displayGuiScreen(EntityPlayer player, GuiScreen gui) {
        if (client.renderViewEntity == player && gui != null) {
            client.displayGuiScreen(gui);
        }
    }

    /**
     * @param mod
     * @param keyHandler
     * @param allowRepeat
     */
    public void registerKeyHandler(BaseMod mod, KeyBinding keyHandler, boolean allowRepeat) {
        ModLoaderModContainer mlmc = ModLoaderHelper.registerKeyHelper(mod);
        mlmc.addKeyHandler(new KeyBindingHandler(keyHandler, allowRepeat, mlmc));
    }

    /**
     * @param renderer
     * @param world
     * @param x
     * @param y
     * @param z
     * @param block
     * @param modelId
     * @return
     */
    public boolean renderWorldBlock(RenderBlocks renderer, IBlockAccess world, int x, int y, int z, Block block,
            int modelId) {
        if (!blockModelIds.containsKey(modelId)) {
            return false;
        }
        BlockRenderInfo bri = blockModelIds.get(modelId);
        return bri.renderWorldBlock(world, x, y, z, block, modelId, renderer);
    }

    /**
     * @param renderer
     * @param block
     * @param metadata
     * @param modelID
     */
    public void renderInventoryBlock(RenderBlocks renderer, Block block, int metadata, int modelID) {
        if (!blockModelIds.containsKey(modelID)) {
            return;
        }
        BlockRenderInfo bri = blockModelIds.get(modelID);
        bri.renderInventoryBlock(block, metadata, modelID, renderer);
    }

    /**
     * @param par0
     * @return
     */
    public boolean renderItemAsFull3DBlock(int modelId) {
        BlockRenderInfo bri = blockModelIds.get(modelId);
        if (bri != null) {
            return bri.shouldRender3DInInventory();
        }
        return false;
    }

    public void registerTextureOverrides(RenderEngine renderer) {
        for (ModContainer mod : Loader.getModList()) {
            registerAnimatedTexturesFor(mod);
        }

        for (OverrideInfo animationOverride : animationSet) {
            renderer.registerTextureFX(animationOverride.textureFX);
            addedTextureFX.add(animationOverride.textureFX);
            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));
        }

        for (String fileToOverride : overrideInfo.keySet()) {
            for (OverrideInfo override : overrideInfo.get(fileToOverride)) {
                try {
                    BufferedImage image = loadImageFromTexturePack(renderer, override.override);
                    ModTextureStatic mts = new ModTextureStatic(override.index, 1, override.texture, image);
                    renderer.registerTextureFX(mts);
                    addedTextureFX.add(mts);
                    FMLCommonHandler.instance().getFMLLogger()
                            .finer(String.format("Registered texture override %d (%d) on %s (%d)", override.index,
                                    mts.iconIndex, override.texture, mts.tileImage));
                } catch (IOException e) {
                    FMLCommonHandler.instance().getFMLLogger().throwing("FMLClientHandler",
                            "registerTextureOverrides", e);
                }
            }
        }
    }

    /**
     * @param mod
     */
    private void registerAnimatedTexturesFor(ModContainer mod) {
        this.animationCallbackMod = mod;
        mod.requestAnimations();
        this.animationCallbackMod = null;
    }

    public String getObjectName(Object instance) {
        String objectName;
        if (instance instanceof Item) {
            objectName = ((Item) instance).getItemName();
        } else if (instance instanceof Block) {
            objectName = ((Block) instance).getBlockName();
        } else if (instance instanceof ItemStack) {
            objectName = Item.itemsList[((ItemStack) instance).itemID].getItemNameIS((ItemStack) instance);
        } else {
            throw new IllegalArgumentException(String.format("Illegal object for naming %s", instance));
        }
        objectName += ".name";
        return objectName;
    }

    /* (non-Javadoc)
     * @see cpw.mods.fml.common.IFMLSidedHandler#readMetadataFrom(java.io.InputStream, cpw.mods.fml.common.ModContainer)
     */
    @Override
    public ModMetadata readMetadataFrom(InputStream input, ModContainer mod) throws Exception {
        JsonNode root = new JdomParser().parse(new InputStreamReader(input));
        List<JsonNode> lst = root.getArrayNode();
        JsonNode modinfo = null;
        for (JsonNode tmodinfo : lst) {
            if (mod.getName().equals(tmodinfo.getStringValue("modid"))) {
                modinfo = tmodinfo;
                break;
            }
        }
        if (modinfo == null) {
            FMLCommonHandler.instance().getFMLLogger()
                    .fine(String.format("Unable to process JSON modinfo file for %s", mod.getName()));
            return null;
        }
        ModMetadata meta = new ModMetadata(mod);
        try {
            meta.name = modinfo.getStringValue("name");
            meta.description = modinfo.getStringValue("description").replace("\r", "");
            meta.version = modinfo.getStringValue("version");
            meta.credits = modinfo.getStringValue("credits");
            List authors = modinfo.getArrayNode("authors");
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < authors.size(); i++) {
                meta.authorList.add(((JsonNode) authors.get(i)).getText());
            }
            meta.logoFile = modinfo.getStringValue("logoFile");
            meta.url = modinfo.getStringValue("url");
            meta.updateUrl = modinfo.getStringValue("updateUrl");
            meta.parent = modinfo.getStringValue("parent");
            List screenshots = modinfo.getArrayNode("screenshots");
            meta.screenshots = new String[screenshots.size()];
            for (int i = 0; i < screenshots.size(); i++) {
                meta.screenshots[i] = ((JsonNode) screenshots.get(i)).getText();
            }
        } catch (Exception e) {
            FMLCommonHandler.instance().getFMLLogger().log(Level.FINE,
                    String.format("An error occured reading the info file for %s", mod.getName()), e);
            if (Item.class.getPackage() != null) //Print the error if we are in MCP so mod devs see it
            {
                System.out.println(String.format("An error occured reading the info file for %s", mod.getName()));
                e.printStackTrace();
            }
        }
        return meta;
    }

    public void pruneOldTextureFX(TexturePackBase var1, List<TextureFX> effects) {
        ListIterator<TextureFX> li = addedTextureFX.listIterator();
        while (li.hasNext()) {
            TextureFX tex = li.next();
            if (tex instanceof FMLTextureFX) {
                if (((FMLTextureFX) tex).unregister(client.renderEngine, effects)) {
                    li.remove();
                }
            } else {
                effects.remove(tex);
                li.remove();
            }
        }
    }

    /**
     * @param par1TexturePackBase
     */
    public void loadTextures(TexturePackBase texturePack) {
        registerTextureOverrides(client.renderEngine);
    }

    /**
     * @param defaultTexturePack
     */
    public void onEarlyTexturePackLoad(TexturePackBase fallback) {
        if (client == null) {
            // We're far too early- let's wait
            this.fallbackTexturePack = fallback;
        } else {
            loadTextures(fallback);
        }
    }

    /**
     * @param packet
     */
    public void sendPacket(Packet packet) {
        if (this.networkClient != null) {
            this.networkClient.addToSendQueue(packet);
        }
    }

    /**
     * @param anim
     */
    public void addAnimation(TextureFX anim) {
        if (animationCallbackMod == null) {
            return;
        }
        OverrideInfo info = new OverrideInfo();
        info.index = anim.iconIndex;
        info.imageIndex = anim.tileImage;
        info.textureFX = anim;
        if (animationSet.contains(info)) {
            animationSet.remove(info);
        }
        animationSet.add(info);
    }

    @Override
    public void profileStart(String profileLabel) {
        Profiler.startSection(profileLabel);
    }

    @Override
    public void profileEnd() {
        Profiler.endSection();
    }

    /**
     * 
     */
    public void preGameLoad(String user, String sessionToken) {
        // Currently this does nothing, but it's possible I could relaunch Minecraft in a new classloader if I wished
        Minecraft.fmlReentry(user, sessionToken);
    }

    public void onTexturePackChange(RenderEngine engine, TexturePackBase texturepack, List<TextureFX> effects) {
        FMLClientHandler.instance().pruneOldTextureFX(texturepack, effects);

        for (TextureFX tex : effects) {
            if (tex instanceof ITextureFX) {
                ((ITextureFX) tex).onTexturePackChanged(engine, texturepack, getTextureDimensions(tex));
            }
        }

        FMLClientHandler.instance().loadTextures(texturepack);
    }

    private HashMap<Integer, Dimension> textureDims = new HashMap<Integer, Dimension>();
    private IdentityHashMap<TextureFX, Integer> effectTextures = new IdentityHashMap<TextureFX, Integer>();

    public void setTextureDimensions(int id, int width, int height, List<TextureFX> effects) {
        Dimension dim = new Dimension(width, height);
        textureDims.put(id, dim);

        for (TextureFX tex : effects) {
            if (getEffectTexture(tex) == id && tex instanceof ITextureFX) {
                ((ITextureFX) tex).onTextureDimensionsUpdate(width, height);
            }
        }
    }

    public Dimension getTextureDimensions(TextureFX effect) {
        return getTextureDimensions(getEffectTexture(effect));
    }

    public Dimension getTextureDimensions(int id) {
        return textureDims.get(id);
    }

    public int getEffectTexture(TextureFX effect) {
        Integer id = effectTextures.get(effect);
        if (id != null) {
            return id;
        }

        int old = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);

        effect.bindImage(client.renderEngine);

        id = GL11.glGetInteger(GL_TEXTURE_BINDING_2D);

        GL11.glBindTexture(GL_TEXTURE_2D, old);

        effectTextures.put(effect, id);

        return id;
    }

    public boolean onUpdateTextureEffect(TextureFX effect) {
        Logger log = FMLCommonHandler.instance().getFMLLogger();
        ITextureFX ifx = (effect instanceof ITextureFX ? ((ITextureFX) effect) : null);

        if (ifx != null && ifx.getErrored()) {
            return false;
        }

        String name = effect.getClass().getSimpleName();
        Profiler.startSection(name);

        try {
            if (optifineContainer == null) {
                effect.onTick();
            }
        } catch (Exception e) {
            log.warning(String.format(
                    "Texture FX %s has failed to animate. Likely caused by a texture pack change that they did not respond correctly to",
                    name));
            if (ifx != null) {
                ifx.setErrored(true);
            }
            Profiler.endSection();
            return false;
        }
        Profiler.endSection();

        if (ifx != null) {
            Dimension dim = getTextureDimensions(effect);
            int target = ((dim.width >> 4) * (dim.height >> 4)) << 2;
            if (effect.imageData.length != target) {
                log.warning(String.format("Detected a texture FX sizing discrepancy in %s (%d, %d)", name,
                        effect.imageData.length, target));
                ifx.setErrored(true);
                return false;
            }
        }
        return true;
    }

    //Quick and dirty image scaling, no smoothing or fanciness, meant for speed as it will be called every tick.    
    public void scaleTextureFXData(byte[] data, ByteBuffer buf, int target, int length) {
        int sWidth = (int) Math.sqrt(data.length / 4);
        int factor = target / sWidth;
        byte[] tmp = new byte[4];

        buf.clear();

        if (factor > 1) {
            for (int y = 0; y < sWidth; y++) {
                int sRowOff = sWidth * y;
                int tRowOff = target * y * factor;
                for (int x = 0; x < sWidth; x++) {
                    int sPos = (x + sRowOff) * 4;
                    tmp[0] = data[sPos + 0];
                    tmp[1] = data[sPos + 1];
                    tmp[2] = data[sPos + 2];
                    tmp[3] = data[sPos + 3];

                    int tPosTop = (x * factor) + tRowOff;
                    for (int y2 = 0; y2 < factor; y2++) {
                        buf.position((tPosTop + (y2 * target)) * 4);
                        for (int x2 = 0; x2 < factor; x2++) {
                            buf.put(tmp);
                        }
                    }
                }
            }
        }

        buf.position(0).limit(length);
    }

    public void onPreRegisterEffect(TextureFX effect) {
        Dimension dim = getTextureDimensions(effect);
        if (effect instanceof ITextureFX) {
            ((ITextureFX) effect).onTextureDimensionsUpdate(dim.width, dim.height);
        }
    }

    /* (non-Javadoc)
     * @see cpw.mods.fml.common.IFMLSidedHandler#getModLoaderPropertyFor(java.lang.reflect.Field)
     */
    @Override
    public ModProperty getModLoaderPropertyFor(Field f) {
        if (f.isAnnotationPresent(MLProp.class)) {
            MLProp prop = f.getAnnotation(MLProp.class);
            return new ModProperty(prop.info(), prop.min(), prop.max(), prop.name());
        }
        return null;
    }

    /**
     * @param mods
     */
    public void addSpecialModEntries(ArrayList<ModContainer> mods) {
        mods.add(new FMLModLoaderContainer());
        if (optifineContainer != null) {
            mods.add(optifineContainer);
        }
    }

    @Override
    public List<String> getAdditionalBrandingInformation() {
        if (optifineContainer != null) {
            return Arrays.asList(String.format("Optifine %s", optifineContainer.getVersion()));
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public Side getSide() {
        return Side.CLIENT;
    }

    @Override
    public ProxyInjector findSidedProxyOn(cpw.mods.fml.common.modloader.BaseMod mod) {
        for (Field f : mod.getClass().getDeclaredFields()) {
            if (f.isAnnotationPresent(SidedProxy.class)) {
                SidedProxy sp = f.getAnnotation(SidedProxy.class);
                return new ProxyInjector(sp.clientSide(), sp.serverSide(), sp.bukkitSide(), f);
            }
        }
        return null;
    }

    /**
     * @param biome
     */
    public void removeBiomeFromDefaultWorldGenerator(BiomeGenBase biome) {
        WorldType.DEFAULT.removeBiome(biome);
    }
}