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