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 }