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 }