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