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