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.GuiConnecting; 030 import net.minecraft.src.GuiScreen; 031 import net.minecraft.src.INetworkManager; 032 import net.minecraft.src.NetClientHandler; 033 import net.minecraft.src.NetHandler; 034 import net.minecraft.src.Packet; 035 import net.minecraft.src.Packet131MapData; 036 import net.minecraft.src.Render; 037 import net.minecraft.src.RenderManager; 038 import net.minecraft.src.World; 039 import net.minecraft.src.WorldClient; 040 041 import com.google.common.base.Throwables; 042 import com.google.common.collect.ImmutableList; 043 import com.google.common.collect.ImmutableMap; 044 import com.google.common.collect.MapDifference; 045 046 import cpw.mods.fml.client.modloader.ModLoaderClientHelper; 047 import cpw.mods.fml.client.registry.KeyBindingRegistry; 048 import cpw.mods.fml.client.registry.RenderingRegistry; 049 import cpw.mods.fml.common.DummyModContainer; 050 import cpw.mods.fml.common.DuplicateModsFoundException; 051 import cpw.mods.fml.common.FMLCommonHandler; 052 import cpw.mods.fml.common.FMLLog; 053 import cpw.mods.fml.common.IFMLSidedHandler; 054 import cpw.mods.fml.common.Loader; 055 import cpw.mods.fml.common.LoaderException; 056 import cpw.mods.fml.common.MetadataCollection; 057 import cpw.mods.fml.common.MissingModsException; 058 import cpw.mods.fml.common.ModContainer; 059 import cpw.mods.fml.common.ModMetadata; 060 import cpw.mods.fml.common.ObfuscationReflectionHelper; 061 import cpw.mods.fml.common.Side; 062 import cpw.mods.fml.common.WrongMinecraftVersionException; 063 import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; 064 import cpw.mods.fml.common.network.EntitySpawnPacket; 065 import cpw.mods.fml.common.network.ModMissingPacket; 066 import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; 067 import cpw.mods.fml.common.registry.GameData; 068 import cpw.mods.fml.common.registry.GameRegistry; 069 import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; 070 import cpw.mods.fml.common.registry.IThrowableEntity; 071 import cpw.mods.fml.common.registry.ItemData; 072 import cpw.mods.fml.common.registry.LanguageRegistry; 073 074 075 /** 076 * Handles primary communication from hooked code into the system 077 * 078 * The FML entry point is {@link #beginMinecraftLoading(Minecraft)} called from 079 * {@link Minecraft} 080 * 081 * Obfuscated code should focus on this class and other members of the "server" 082 * (or "client") code 083 * 084 * The actual mod loading is handled at arms length by {@link Loader} 085 * 086 * It is expected that a similar class will exist for each target environment: 087 * Bukkit and Client side. 088 * 089 * It should not be directly modified. 090 * 091 * @author cpw 092 * 093 */ 094 public class FMLClientHandler implements IFMLSidedHandler 095 { 096 /** 097 * The singleton 098 */ 099 private static final FMLClientHandler INSTANCE = new FMLClientHandler(); 100 101 /** 102 * A reference to the server itself 103 */ 104 private Minecraft client; 105 106 private DummyModContainer optifineContainer; 107 108 private boolean guiLoaded; 109 110 private boolean serverIsRunning; 111 112 private MissingModsException modsMissing; 113 114 private boolean loading; 115 116 private WrongMinecraftVersionException wrongMC; 117 118 private CustomModLoadingErrorDisplayException customError; 119 120 private DuplicateModsFoundException dupesFound; 121 122 private boolean serverShouldBeKilledQuietly; 123 124 /** 125 * Called to start the whole game off 126 * 127 * @param minecraft The minecraft instance being launched 128 */ 129 public void beginMinecraftLoading(Minecraft minecraft) 130 { 131 if (minecraft.isDemo()) 132 { 133 FMLLog.severe("DEMO MODE DETECTED, FML will not work. Finishing now."); 134 haltGame("FML will not run in demo mode", new RuntimeException()); 135 return; 136 } 137 138 loading = true; 139 client = minecraft; 140 ObfuscationReflectionHelper.detectObfuscation(World.class); 141 TextureFXManager.instance().setClient(client); 142 FMLCommonHandler.instance().beginLoading(this); 143 new ModLoaderClientHelper(client); 144 try 145 { 146 Class<?> optifineConfig = Class.forName("Config", false, Loader.instance().getModClassLoader()); 147 String optifineVersion = (String) optifineConfig.getField("VERSION").get(null); 148 Map<String,Object> dummyOptifineMeta = ImmutableMap.<String,Object>builder().put("name", "Optifine").put("version", optifineVersion).build(); 149 ModMetadata optifineMetadata = MetadataCollection.from(getClass().getResourceAsStream("optifinemod.info"),"optifine").getMetadataForId("optifine", dummyOptifineMeta); 150 optifineContainer = new DummyModContainer(optifineMetadata); 151 FMLLog.info("Forge Mod Loader has detected optifine %s, enabling compatibility features",optifineContainer.getVersion()); 152 } 153 catch (Exception e) 154 { 155 optifineContainer = null; 156 } 157 try 158 { 159 Loader.instance().loadMods(); 160 } 161 catch (WrongMinecraftVersionException wrong) 162 { 163 wrongMC = wrong; 164 } 165 catch (DuplicateModsFoundException dupes) 166 { 167 dupesFound = dupes; 168 } 169 catch (MissingModsException missing) 170 { 171 modsMissing = missing; 172 } 173 catch (CustomModLoadingErrorDisplayException custom) 174 { 175 FMLLog.log(Level.SEVERE, custom, "A custom exception was thrown by a mod, the game will now halt"); 176 customError = custom; 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 } 184 185 @Override 186 public void haltGame(String message, Throwable t) 187 { 188 client.displayCrashReport(new CrashReport(message, t)); 189 throw Throwables.propagate(t); 190 } 191 /** 192 * Called a bit later on during initialization to finish loading mods 193 * Also initializes key bindings 194 * 195 */ 196 @SuppressWarnings("deprecation") 197 public void finishMinecraftLoading() 198 { 199 if (modsMissing != null || wrongMC != null || customError!=null || dupesFound!=null) 200 { 201 return; 202 } 203 try 204 { 205 Loader.instance().initializeMods(); 206 } 207 catch (CustomModLoadingErrorDisplayException custom) 208 { 209 FMLLog.log(Level.SEVERE, custom, "A custom exception was thrown by a mod, the game will now halt"); 210 customError = custom; 211 return; 212 } 213 catch (LoaderException le) 214 { 215 haltGame("There was a severe problem during mod loading that has caused the game to fail", le); 216 return; 217 } 218 LanguageRegistry.reloadLanguageTable(); 219 RenderingRegistry.instance().loadEntityRenderers((Map<Class<? extends Entity>, Render>)RenderManager.instance.entityRenderMap); 220 221 loading = false; 222 KeyBindingRegistry.instance().uploadKeyBindingsToGame(client.gameSettings); 223 } 224 225 public void onInitializationComplete() 226 { 227 if (wrongMC != null) 228 { 229 client.displayGuiScreen(new GuiWrongMinecraft(wrongMC)); 230 } 231 else if (modsMissing != null) 232 { 233 client.displayGuiScreen(new GuiModsMissing(modsMissing)); 234 } 235 else if (dupesFound != null) 236 { 237 client.displayGuiScreen(new GuiDupesFound(dupesFound)); 238 } 239 else if (customError != null) 240 { 241 client.displayGuiScreen(new GuiCustomModLoadingErrorScreen(customError)); 242 } 243 else 244 { 245 TextureFXManager.instance().loadTextures(client.texturePackList.getSelectedTexturePack()); 246 } 247 } 248 /** 249 * Get the server instance 250 */ 251 public Minecraft getClient() 252 { 253 return client; 254 } 255 256 /** 257 * Get a handle to the client's logger instance 258 * The client actually doesn't have one- so we return null 259 */ 260 public Logger getMinecraftLogger() 261 { 262 return null; 263 } 264 265 /** 266 * @return the instance 267 */ 268 public static FMLClientHandler instance() 269 { 270 return INSTANCE; 271 } 272 273 /** 274 * @param player 275 * @param gui 276 */ 277 public void displayGuiScreen(EntityPlayer player, GuiScreen gui) 278 { 279 if (client.thePlayer==player && gui != null) { 280 client.displayGuiScreen(gui); 281 } 282 } 283 284 /** 285 * @param mods 286 */ 287 public void addSpecialModEntries(ArrayList<ModContainer> mods) 288 { 289 if (optifineContainer!=null) { 290 mods.add(optifineContainer); 291 } 292 } 293 294 @Override 295 public List<String> getAdditionalBrandingInformation() 296 { 297 if (optifineContainer!=null) 298 { 299 return Arrays.asList(String.format("Optifine %s",optifineContainer.getVersion())); 300 } else { 301 return ImmutableList.<String>of(); 302 } 303 } 304 305 @Override 306 public Side getSide() 307 { 308 return Side.CLIENT; 309 } 310 311 public boolean hasOptifine() 312 { 313 return optifineContainer!=null; 314 } 315 316 @Override 317 public void showGuiScreen(Object clientGuiElement) 318 { 319 GuiScreen gui = (GuiScreen) clientGuiElement; 320 client.displayGuiScreen(gui); 321 } 322 323 @Override 324 public Entity spawnEntityIntoClientWorld(EntityRegistration er, EntitySpawnPacket packet) 325 { 326 WorldClient wc = client.theWorld; 327 328 Class<? extends Entity> cls = er.getEntityClass(); 329 330 try 331 { 332 Entity entity; 333 if (er.hasCustomSpawning()) 334 { 335 entity = er.doCustomSpawning(packet); 336 } 337 else 338 { 339 entity = (Entity)(cls.getConstructor(World.class).newInstance(wc)); 340 entity.entityId = packet.entityId; 341 entity.setLocationAndAngles(packet.scaledX, packet.scaledY, packet.scaledZ, packet.scaledYaw, packet.scaledPitch); 342 if (entity instanceof EntityLiving) 343 { 344 ((EntityLiving)entity).rotationYawHead = packet.scaledHeadYaw; 345 } 346 347 } 348 349 entity.serverPosX = packet.rawX; 350 entity.serverPosY = packet.rawY; 351 entity.serverPosZ = packet.rawZ; 352 353 if (entity instanceof IThrowableEntity) 354 { 355 Entity thrower = client.thePlayer.entityId == packet.throwerId ? client.thePlayer : wc.getEntityByID(packet.throwerId); 356 ((IThrowableEntity)entity).setThrower(thrower); 357 } 358 359 360 Entity parts[] = entity.getParts(); 361 if (parts != null) 362 { 363 int i = packet.entityId - entity.entityId; 364 for (int j = 0; j < parts.length; j++) 365 { 366 parts[j].entityId += i; 367 } 368 } 369 370 371 if (packet.metadata != null) 372 { 373 entity.getDataWatcher().updateWatchedObjectsFromList((List)packet.metadata); 374 } 375 376 if (packet.throwerId > 0) 377 { 378 entity.setVelocity(packet.speedScaledX, packet.speedScaledY, packet.speedScaledZ); 379 } 380 381 if (entity instanceof IEntityAdditionalSpawnData) 382 { 383 ((IEntityAdditionalSpawnData)entity).readSpawnData(packet.dataStream); 384 } 385 386 wc.addEntityToWorld(packet.entityId, entity); 387 return entity; 388 } 389 catch (Exception e) 390 { 391 FMLLog.log(Level.SEVERE, e, "A severe problem occurred during the spawning of an entity"); 392 throw Throwables.propagate(e); 393 } 394 } 395 396 @Override 397 public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket packet) 398 { 399 Entity ent = client.theWorld.getEntityByID(packet.entityId); 400 if (ent != null) 401 { 402 ent.serverPosX = packet.serverX; 403 ent.serverPosY = packet.serverY; 404 ent.serverPosZ = packet.serverZ; 405 } 406 else 407 { 408 FMLLog.fine("Attempted to adjust the position of entity %d which is not present on the client", packet.entityId); 409 } 410 } 411 412 @Override 413 public void beginServerLoading(MinecraftServer server) 414 { 415 serverShouldBeKilledQuietly = false; 416 // NOOP 417 } 418 419 @Override 420 public void finishServerLoading() 421 { 422 // NOOP 423 } 424 425 @Override 426 public MinecraftServer getServer() 427 { 428 return client.getIntegratedServer(); 429 } 430 431 @Override 432 public void sendPacket(Packet packet) 433 { 434 if(client.thePlayer != null) 435 { 436 client.thePlayer.sendQueue.addToSendQueue(packet); 437 } 438 } 439 440 @Override 441 public void displayMissingMods(ModMissingPacket modMissingPacket) 442 { 443 client.displayGuiScreen(new GuiModsMissingForServer(modMissingPacket)); 444 } 445 446 /** 447 * If the client is in the midst of loading, we disable saving so that custom settings aren't wiped out 448 */ 449 public boolean isLoading() 450 { 451 return loading; 452 } 453 454 @Override 455 public void handleTinyPacket(NetHandler handler, Packet131MapData mapData) 456 { 457 ((NetClientHandler)handler).fmlPacket131Callback(mapData); 458 } 459 460 @Override 461 public void setClientCompatibilityLevel(byte compatibilityLevel) 462 { 463 NetClientHandler.setConnectionCompatibilityLevel(compatibilityLevel); 464 } 465 466 @Override 467 public byte getClientCompatibilityLevel() 468 { 469 return NetClientHandler.getConnectionCompatibilityLevel(); 470 } 471 472 public void warnIDMismatch(MapDifference<Integer, ItemData> idDifferences, boolean mayContinue) 473 { 474 GuiIdMismatchScreen mismatch = new GuiIdMismatchScreen(idDifferences, mayContinue); 475 client.displayGuiScreen(mismatch); 476 } 477 478 public void callbackIdDifferenceResponse(boolean response) 479 { 480 if (response) 481 { 482 serverShouldBeKilledQuietly = false; 483 GameData.releaseGate(true); 484 client.continueWorldLoading(); 485 } 486 else 487 { 488 serverShouldBeKilledQuietly = true; 489 GameData.releaseGate(false); 490 // Reset and clear the client state 491 client.loadWorld((WorldClient)null); 492 client.displayGuiScreen(null); 493 } 494 } 495 496 @Override 497 public boolean shouldServerShouldBeKilledQuietly() 498 { 499 return serverShouldBeKilledQuietly; 500 } 501 502 @Override 503 public void disconnectIDMismatch(MapDifference<Integer, ItemData> s, NetHandler toKill, INetworkManager mgr) 504 { 505 // Nuke the connection 506 ((NetClientHandler)toKill).disconnect(); 507 // Stop GuiConnecting 508 GuiConnecting.forceTermination((GuiConnecting)client.currentScreen); 509 // pulse the network manager queue to clear cruft 510 mgr.processReadPackets(); 511 // Nuke the world client 512 client.loadWorld((WorldClient)null); 513 // Show error screen 514 warnIDMismatch(s, false); 515 } 516 }