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