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    }