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 }