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