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 }