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