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