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