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    }