    001    /*
    002     * The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
    003     *
    004     * 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
    005     * Software Foundation; either version 2.1 of the License, or any later version.
    006     *
    007     * 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
    008     * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
    009     *
    010     * 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
    011     * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
    012     */
    013    package cpw.mods.fml.client;
    014
    015    import java.util.ArrayList;
    016    import java.util.Arrays;
    017    import java.util.Collections;
    018    import java.util.List;
    019    import java.util.Map;
    020    import java.util.logging.Level;
    021    import java.util.logging.Logger;
    022
    023    import net.minecraft.client.Minecraft;
    024    import net.minecraft.server.MinecraftServer;
    025    import net.minecraft.src.CrashReport;
    026    import net.minecraft.src.Entity;
    027    import net.minecraft.src.EntityLiving;
    028    import net.minecraft.src.EntityPlayer;
    029    import net.minecraft.src.GuiScreen;
    030    import net.minecraft.src.NetClientHandler;
    031    import net.minecraft.src.NetHandler;
    032    import net.minecraft.src.Packet;
    033    import net.minecraft.src.Packet131MapData;
    034    import net.minecraft.src.Render;
    035    import net.minecraft.src.RenderManager;
    036    import net.minecraft.src.World;
    037    import net.minecraft.src.WorldClient;
    038
    039    import com.google.common.base.Throwables;
    040    import com.google.common.collect.ImmutableMap;
    041
    042    import cpw.mods.fml.client.modloader.ModLoaderClientHelper;
    043    import cpw.mods.fml.client.registry.KeyBindingRegistry;
    044    import cpw.mods.fml.client.registry.RenderingRegistry;
    045    import cpw.mods.fml.common.DummyModContainer;
    046    import cpw.mods.fml.common.FMLCommonHandler;
    047    import cpw.mods.fml.common.FMLLog;
    048    import cpw.mods.fml.common.IFMLSidedHandler;
    049    import cpw.mods.fml.common.Loader;
    050    import cpw.mods.fml.common.LoaderException;
    051    import cpw.mods.fml.common.MetadataCollection;
    052    import cpw.mods.fml.common.MissingModsException;
    053    import cpw.mods.fml.common.ModContainer;
    054    import cpw.mods.fml.common.ModMetadata;
    055    import cpw.mods.fml.common.ObfuscationReflectionHelper;
    056    import cpw.mods.fml.common.Side;
    057    import cpw.mods.fml.common.WrongMinecraftVersionException;
    058    import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket;
    059    import cpw.mods.fml.common.network.EntitySpawnPacket;
    060    import cpw.mods.fml.common.network.ModMissingPacket;
    061    import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
    062    import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData;
    063    import cpw.mods.fml.common.registry.IThrowableEntity;
    064    import cpw.mods.fml.common.registry.LanguageRegistry;
    065
    066
    067    /**
    068     * Handles primary communication from hooked code into the system
    069     *
    070     * The FML entry point is {@link #beginMinecraftLoading(Minecraft)} called from
    071     * {@link Minecraft}
    072     *
    073     * Obfuscated code should focus on this class and other members of the "server"
    074     * (or "client") code
    075     *
    076     * The actual mod loading is handled at arms length by {@link Loader}
    077     *
    078     * It is expected that a similar class will exist for each target environment:
    079     * Bukkit and Client side.
    080     *
    081     * It should not be directly modified.
    082     *
    083     * @author cpw
    084     *
    085     */
    086    public class FMLClientHandler implements IFMLSidedHandler
    087    {
    088        /**
    089         * The singleton
    090         */
    091        private static final FMLClientHandler INSTANCE = new FMLClientHandler();
    092
    093        /**
    094         * A reference to the server itself
    095         */
    096        private Minecraft client;
    097
    098        private DummyModContainer optifineContainer;
    099
    100        private boolean guiLoaded;
    101
    102        private boolean serverIsRunning;
    103
    104        private MissingModsException modsMissing;
    105
    106        private boolean loading;
    107
    108        private WrongMinecraftVersionException wrongMC;
    109
    110        private CustomModLoadingErrorDisplayException customError;
    111
    112        /**
    113         * Called to start the whole game off from
    114         * {@link MinecraftServer#startServer}
    115         *
    116         * @param minecraftServer
    117         */
    118        public void beginMinecraftLoading(Minecraft minecraft)
    119        {
    120            if (minecraft.isDemo())
    121            {
    122                FMLLog.severe("DEMO MODE DETECTED, FML will not work. Finishing now.");
    123                haltGame("FML will not run in demo mode", new RuntimeException());
    124                return;
    125            }
    126
    127            loading = true;
    128            client = minecraft;
    129            ObfuscationReflectionHelper.detectObfuscation(World.class);
    130            TextureFXManager.instance().setClient(client);
    131            FMLCommonHandler.instance().beginLoading(this);
    132            new ModLoaderClientHelper(client);
    133            try
    134            {
    135                Class<?> optifineConfig = Class.forName("Config", false, Loader.instance().getModClassLoader());
    136                String optifineVersion = (String) optifineConfig.getField("VERSION").get(null);
    137                Map<String,Object> dummyOptifineMeta = ImmutableMap.<String,Object>builder().put("name", "Optifine").put("version", optifineVersion).build();
    138                ModMetadata optifineMetadata = MetadataCollection.from(getClass().getResourceAsStream("optifinemod.info"),"optifine").getMetadataForId("optifine", dummyOptifineMeta);
    139                optifineContainer = new DummyModContainer(optifineMetadata);
    140                FMLLog.info("Forge Mod Loader has detected optifine %s, enabling compatibility features",optifineContainer.getVersion());
    141            }
    142            catch (Exception e)
    143            {
    144                optifineContainer = null;
    145            }
    146            try
    147            {
    148                Loader.instance().loadMods();
    149            }
    150            catch (WrongMinecraftVersionException wrong)
    151            {
    152                wrongMC = wrong;
    153            }
    154            catch (MissingModsException missing)
    155            {
    156                modsMissing = missing;
    157            }
    158            catch (CustomModLoadingErrorDisplayException custom)
    159            {
    160                FMLLog.log(Level.SEVERE, custom, "A custom exception was thrown by a mod, the game will now halt");
    161                customError = custom;
    162            }
    163            catch (LoaderException le)
    164            {
    165                haltGame("There was a severe problem during mod loading that has caused the game to fail", le);
    166                return;
    167            }
    168        }
    169
    170        @Override
    171        public void haltGame(String message, Throwable t)
    172        {
    173            client.displayCrashReport(new CrashReport(message, t));
    174            throw Throwables.propagate(t);
    175        }
    176        /**
    177         * Called a bit later on during initialization to finish loading mods
    178         * Also initializes key bindings
    179         *
    180         */
    181        @SuppressWarnings("deprecation")
    182        public void finishMinecraftLoading()
    183        {
    184            if (modsMissing != null || wrongMC != null)
    185            {
    186                return;
    187            }
    188            try
    189            {
    190                Loader.instance().initializeMods();
    191            }
    192            catch (CustomModLoadingErrorDisplayException custom)
    193            {
    194                FMLLog.log(Level.SEVERE, custom, "A custom exception was thrown by a mod, the game will now halt");
    195                customError = custom;
    196                return;
    197            }
    198            catch (LoaderException le)
    199            {
    200                haltGame("There was a severe problem during mod loading that has caused the game to fail", le);
    201                return;
    202            }
    203            LanguageRegistry.reloadLanguageTable();
    204            RenderingRegistry.instance().loadEntityRenderers((Map<Class<? extends Entity>, Render>)RenderManager.instance.entityRenderMap);
    205
    206            loading = false;
    207            KeyBindingRegistry.instance().uploadKeyBindingsToGame(client.gameSettings);
    208        }
    209
    210        public void onInitializationComplete()
    211        {
    212            if (wrongMC != null)
    213            {
    214                client.displayGuiScreen(new GuiWrongMinecraft(wrongMC));
    215            }
    216            else if (modsMissing != null)
    217            {
    218                client.displayGuiScreen(new GuiModsMissing(modsMissing));
    219            }
    220            else if (customError != null)
    221            {
    222                client.displayGuiScreen(new GuiCustomModLoadingErrorScreen(customError));
    223            }
    224            else
    225            {
    226                TextureFXManager.instance().loadTextures(client.texturePackList.getSelectedTexturePack());
    227            }
    228        }
    229        /**
    230         * Get the server instance
    231         */
    232        public Minecraft getClient()
    233        {
    234            return client;
    235        }
    236
    237        /**
    238         * Get a handle to the client's logger instance
    239         * The client actually doesn't have one- so we return null
    240         */
    241        public Logger getMinecraftLogger()
    242        {
    243            return null;
    244        }
    245
    246        /**
    247         * @return the instance
    248         */
    249        public static FMLClientHandler instance()
    250        {
    251            return INSTANCE;
    252        }
    253
    254        /**
    255         * @param player
    256         * @param gui
    257         */
    258        public void displayGuiScreen(EntityPlayer player, GuiScreen gui)
    259        {
    260            if (client.thePlayer==player && gui != null) {
    261                client.displayGuiScreen(gui);
    262            }
    263        }
    264
    265        /**
    266         * @param mods
    267         */
    268        public void addSpecialModEntries(ArrayList<ModContainer> mods)
    269        {
    270            if (optifineContainer!=null) {
    271                mods.add(optifineContainer);
    272            }
    273        }
    274
    275        @Override
    276        public List<String> getAdditionalBrandingInformation()
    277        {
    278            if (optifineContainer!=null)
    279            {
    280                return Arrays.asList(String.format("Optifine %s",optifineContainer.getVersion()));
    281            } else {
    282                return Collections.emptyList();
    283            }
    284        }
    285
    286        @Override
    287        public Side getSide()
    288        {
    289            return Side.CLIENT;
    290        }
    291
    292        public boolean hasOptifine()
    293        {
    294            return optifineContainer!=null;
    295        }
    296
    297        @Override
    298        public void showGuiScreen(Object clientGuiElement)
    299        {
    300            GuiScreen gui = (GuiScreen) clientGuiElement;
    301            client.displayGuiScreen(gui);
    302        }
    303
    304        @Override
    305        public Entity spawnEntityIntoClientWorld(EntityRegistration er, EntitySpawnPacket packet)
    306        {
    307            WorldClient wc = client.theWorld;
    308
    309            Class<? extends Entity> cls = er.getEntityClass();
    310
    311            try
    312            {
    313                Entity entity;
    314                if (er.hasCustomSpawning())
    315                {
    316                    entity = er.doCustomSpawning(packet);
    317                }
    318                else
    319                {
    320                    entity = (Entity)(cls.getConstructor(World.class).newInstance(wc));
    321                    entity.entityId = packet.entityId;
    322                    entity.setLocationAndAngles(packet.scaledX, packet.scaledY, packet.scaledZ, packet.scaledYaw, packet.scaledPitch);
    323                    if (entity instanceof EntityLiving)
    324                    {
    325                        ((EntityLiving)entity).rotationYawHead = packet.scaledHeadYaw;
    326                    }
    327
    328                }
    329
    330                entity.serverPosX = packet.rawX;
    331                entity.serverPosY = packet.rawY;
    332                entity.serverPosZ = packet.rawZ;
    333
    334                if (entity instanceof IThrowableEntity)
    335                {
    336                    Entity thrower = client.thePlayer.entityId == packet.throwerId ? client.thePlayer : wc.getEntityByID(packet.throwerId);
    337                    ((IThrowableEntity)entity).setThrower(thrower);
    338                }
    339
    340
    341                Entity parts[] = entity.getParts();
    342                if (parts != null)
    343                {
    344                    int i = packet.entityId - entity.entityId;
    345                    for (int j = 0; j < parts.length; j++)
    346                    {
    347                        parts[j].entityId += i;
    348                    }
    349                }
    350
    351
    352                if (packet.metadata != null)
    353                {
    354                    entity.getDataWatcher().updateWatchedObjectsFromList((List)packet.metadata);
    355                }
    356
    357                if (packet.throwerId > 0)
    358                {
    359                    entity.setVelocity(packet.speedScaledX, packet.speedScaledY, packet.speedScaledZ);
    360                }
    361
    362                if (entity instanceof IEntityAdditionalSpawnData)
    363                {
    364                    ((IEntityAdditionalSpawnData)entity).readSpawnData(packet.dataStream);
    365                }
    366
    367                wc.addEntityToWorld(packet.entityId, entity);
    368                return entity;
    369            }
    370            catch (Exception e)
    371            {
    372                FMLLog.log(Level.SEVERE, e, "A severe problem occurred during the spawning of an entity");
    373                throw Throwables.propagate(e);
    374            }
    375        }
    376
    377        @Override
    378        public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket packet)
    379        {
    380            Entity ent = client.theWorld.getEntityByID(packet.entityId);
    381            if (ent != null)
    382            {
    383                ent.serverPosX = packet.serverX;
    384                ent.serverPosY = packet.serverY;
    385                ent.serverPosZ = packet.serverZ;
    386            }
    387            else
    388            {
    389                FMLLog.fine("Attempted to adjust the position of entity %d which is not present on the client", packet.entityId);
    390            }
    391        }
    392
    393        @Override
    394        public void beginServerLoading(MinecraftServer server)
    395        {
    396            // NOOP
    397        }
    398
    399        @Override
    400        public void finishServerLoading()
    401        {
    402            // NOOP
    403        }
    404
    405        @Override
    406        public MinecraftServer getServer()
    407        {
    408            return client.getIntegratedServer();
    409        }
    410
    411        @Override
    412        public void sendPacket(Packet packet)
    413        {
    414            if(client.thePlayer != null)
    415            {
    416                client.thePlayer.sendQueue.addToSendQueue(packet);
    417            }
    418        }
    419
    420        @Override
    421        public void displayMissingMods(ModMissingPacket modMissingPacket)
    422        {
    423            client.displayGuiScreen(new GuiModsMissingForServer(modMissingPacket));
    424        }
    425
    426        /**
    427         * If the client is in the midst of loading, we disable saving so that custom settings aren't wiped out
    428         */
    429        public boolean isLoading()
    430        {
    431            return loading;
    432        }
    433
    434        @Override
    435        public void handleTinyPacket(NetHandler handler, Packet131MapData mapData)
    436        {
    437            ((NetClientHandler)handler).fmlPacket131Callback(mapData);
    438        }
    439
    440        @Override
    441        public void setClientCompatibilityLevel(byte compatibilityLevel)
    442        {
    443            NetClientHandler.setConnectionCompatibilityLevel(compatibilityLevel);
    444        }
    445
    446        @Override
    447        public byte getClientCompatibilityLevel()
    448        {
    449            return NetClientHandler.getConnectionCompatibilityLevel();
    450        }
    451    }