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.network.EntitySpawnAdjustmentPacket; 058 import cpw.mods.fml.common.network.EntitySpawnPacket; 059 import cpw.mods.fml.common.network.ModMissingPacket; 060 import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; 061 import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; 062 import cpw.mods.fml.common.registry.IThrowableEntity; 063 import cpw.mods.fml.common.registry.LanguageRegistry; 064 065 066 /** 067 * Handles primary communication from hooked code into the system 068 * 069 * The FML entry point is {@link #beginMinecraftLoading(Minecraft)} called from 070 * {@link Minecraft} 071 * 072 * Obfuscated code should focus on this class and other members of the "server" 073 * (or "client") code 074 * 075 * The actual mod loading is handled at arms length by {@link Loader} 076 * 077 * It is expected that a similar class will exist for each target environment: 078 * Bukkit and Client side. 079 * 080 * It should not be directly modified. 081 * 082 * @author cpw 083 * 084 */ 085 public class FMLClientHandler implements IFMLSidedHandler 086 { 087 /** 088 * The singleton 089 */ 090 private static final FMLClientHandler INSTANCE = new FMLClientHandler(); 091 092 /** 093 * A reference to the server itself 094 */ 095 private Minecraft client; 096 097 private DummyModContainer optifineContainer; 098 099 private boolean guiLoaded; 100 101 private boolean serverIsRunning; 102 103 private MissingModsException modsMissing; 104 105 private boolean loading; 106 107 /** 108 * Called to start the whole game off from 109 * {@link MinecraftServer#startServer} 110 * 111 * @param minecraftServer 112 */ 113 public void beginMinecraftLoading(Minecraft minecraft) 114 { 115 if (minecraft.isDemo()) 116 { 117 FMLLog.severe("DEMO MODE DETECTED, FML will not work. Finishing now."); 118 haltGame("FML will not run in demo mode", new RuntimeException()); 119 return; 120 } 121 122 loading = true; 123 client = minecraft; 124 ObfuscationReflectionHelper.detectObfuscation(World.class); 125 TextureFXManager.instance().setClient(client); 126 FMLCommonHandler.instance().beginLoading(this); 127 new ModLoaderClientHelper(client); 128 try 129 { 130 Class<?> optifineConfig = Class.forName("Config", false, Loader.instance().getModClassLoader()); 131 String optifineVersion = (String) optifineConfig.getField("VERSION").get(null); 132 Map<String,Object> dummyOptifineMeta = ImmutableMap.<String,Object>builder().put("name", "Optifine").put("version", optifineVersion).build(); 133 ModMetadata optifineMetadata = MetadataCollection.from(getClass().getResourceAsStream("optifinemod.info"),"optifine").getMetadataForId("optifine", dummyOptifineMeta); 134 optifineContainer = new DummyModContainer(optifineMetadata); 135 FMLLog.info("Forge Mod Loader has detected optifine %s, enabling compatibility features",optifineContainer.getVersion()); 136 } 137 catch (Exception e) 138 { 139 optifineContainer = null; 140 } 141 try 142 { 143 Loader.instance().loadMods(); 144 } 145 catch (MissingModsException missing) 146 { 147 modsMissing = missing; 148 } 149 catch (LoaderException le) 150 { 151 haltGame("There was a severe problem during mod loading that has caused the game to fail", le); 152 return; 153 } 154 } 155 156 @Override 157 public void haltGame(String message, Throwable t) 158 { 159 client.displayCrashReport(new CrashReport(message, t)); 160 throw Throwables.propagate(t); 161 } 162 /** 163 * Called a bit later on during initialization to finish loading mods 164 * Also initializes key bindings 165 * 166 */ 167 @SuppressWarnings("deprecation") 168 public void finishMinecraftLoading() 169 { 170 if (modsMissing != null) 171 { 172 return; 173 } 174 try 175 { 176 Loader.instance().initializeMods(); 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 LanguageRegistry.reloadLanguageTable(); 184 RenderingRegistry.instance().loadEntityRenderers((Map<Class<? extends Entity>, Render>)RenderManager.instance.entityRenderMap); 185 186 loading = false; 187 KeyBindingRegistry.instance().uploadKeyBindingsToGame(client.gameSettings); 188 } 189 190 public void onInitializationComplete() 191 { 192 if (modsMissing != null) 193 { 194 client.displayGuiScreen(new GuiModsMissing(modsMissing)); 195 } 196 else 197 { 198 TextureFXManager.instance().loadTextures(client.texturePackList.getSelectedTexturePack()); 199 } 200 } 201 /** 202 * Get the server instance 203 */ 204 public Minecraft getClient() 205 { 206 return client; 207 } 208 209 /** 210 * Get a handle to the client's logger instance 211 * The client actually doesn't have one- so we return null 212 */ 213 public Logger getMinecraftLogger() 214 { 215 return null; 216 } 217 218 /** 219 * @return the instance 220 */ 221 public static FMLClientHandler instance() 222 { 223 return INSTANCE; 224 } 225 226 /** 227 * @param player 228 * @param gui 229 */ 230 public void displayGuiScreen(EntityPlayer player, GuiScreen gui) 231 { 232 if (client.thePlayer==player && gui != null) { 233 client.displayGuiScreen(gui); 234 } 235 } 236 237 /** 238 * @param mods 239 */ 240 public void addSpecialModEntries(ArrayList<ModContainer> mods) 241 { 242 if (optifineContainer!=null) { 243 mods.add(optifineContainer); 244 } 245 } 246 247 @Override 248 public List<String> getAdditionalBrandingInformation() 249 { 250 if (optifineContainer!=null) 251 { 252 return Arrays.asList(String.format("Optifine %s",optifineContainer.getVersion())); 253 } else { 254 return Collections.emptyList(); 255 } 256 } 257 258 @Override 259 public Side getSide() 260 { 261 return Side.CLIENT; 262 } 263 264 public boolean hasOptifine() 265 { 266 return optifineContainer!=null; 267 } 268 269 @Override 270 public void showGuiScreen(Object clientGuiElement) 271 { 272 GuiScreen gui = (GuiScreen) clientGuiElement; 273 client.displayGuiScreen(gui); 274 } 275 276 @Override 277 public Entity spawnEntityIntoClientWorld(EntityRegistration er, EntitySpawnPacket packet) 278 { 279 WorldClient wc = client.theWorld; 280 281 Class<? extends Entity> cls = er.getEntityClass(); 282 283 try 284 { 285 Entity entity; 286 if (er.hasCustomSpawning()) 287 { 288 entity = er.doCustomSpawning(packet); 289 } 290 else 291 { 292 entity = (Entity)(cls.getConstructor(World.class).newInstance(wc)); 293 entity.entityId = packet.entityId; 294 entity.setLocationAndAngles(packet.scaledX, packet.scaledY, packet.scaledZ, packet.scaledYaw, packet.scaledPitch); 295 if (entity instanceof EntityLiving) 296 { 297 ((EntityLiving)entity).rotationYawHead = packet.scaledHeadYaw; 298 } 299 300 } 301 302 entity.serverPosX = packet.rawX; 303 entity.serverPosY = packet.rawY; 304 entity.serverPosZ = packet.rawZ; 305 306 if (entity instanceof IThrowableEntity) 307 { 308 Entity thrower = client.thePlayer.entityId == packet.throwerId ? client.thePlayer : wc.getEntityByID(packet.throwerId); 309 ((IThrowableEntity)entity).setThrower(thrower); 310 } 311 312 313 Entity parts[] = entity.getParts(); 314 if (parts != null) 315 { 316 int i = packet.entityId - entity.entityId; 317 for (int j = 0; j < parts.length; j++) 318 { 319 parts[j].entityId += i; 320 } 321 } 322 323 324 if (packet.metadata != null) 325 { 326 entity.getDataWatcher().updateWatchedObjectsFromList((List)packet.metadata); 327 } 328 329 if (packet.throwerId > 0) 330 { 331 entity.setVelocity(packet.speedScaledX, packet.speedScaledY, packet.speedScaledZ); 332 } 333 334 if (entity instanceof IEntityAdditionalSpawnData) 335 { 336 ((IEntityAdditionalSpawnData)entity).readSpawnData(packet.dataStream); 337 } 338 339 wc.addEntityToWorld(packet.entityId, entity); 340 return entity; 341 } 342 catch (Exception e) 343 { 344 FMLLog.log(Level.SEVERE, e, "A severe problem occurred during the spawning of an entity"); 345 throw Throwables.propagate(e); 346 } 347 } 348 349 @Override 350 public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket packet) 351 { 352 Entity ent = client.theWorld.getEntityByID(packet.entityId); 353 if (ent != null) 354 { 355 ent.serverPosX = packet.serverX; 356 ent.serverPosY = packet.serverY; 357 ent.serverPosZ = packet.serverZ; 358 } 359 else 360 { 361 FMLLog.fine("Attempted to adjust the position of entity %d which is not present on the client", packet.entityId); 362 } 363 } 364 365 @Override 366 public void beginServerLoading(MinecraftServer server) 367 { 368 // NOOP 369 } 370 371 @Override 372 public void finishServerLoading() 373 { 374 // NOOP 375 } 376 377 @Override 378 public MinecraftServer getServer() 379 { 380 return client.getIntegratedServer(); 381 } 382 383 @Override 384 public void sendPacket(Packet packet) 385 { 386 client.thePlayer.sendQueue.addToSendQueue(packet); 387 } 388 389 @Override 390 public void displayMissingMods(ModMissingPacket modMissingPacket) 391 { 392 client.displayGuiScreen(new GuiModsMissingForServer(modMissingPacket)); 393 } 394 395 /** 396 * If the client is in the midst of loading, we disable saving so that custom settings aren't wiped out 397 */ 398 public boolean isLoading() 399 { 400 return loading; 401 } 402 403 @Override 404 public void handleTinyPacket(NetHandler handler, Packet131MapData mapData) 405 { 406 ((NetClientHandler)handler).fmlPacket131Callback(mapData); 407 } 408 409 @Override 410 public void setClientCompatibilityLevel(byte compatibilityLevel) 411 { 412 NetClientHandler.setConnectionCompatibilityLevel(compatibilityLevel); 413 } 414 415 @Override 416 public byte getClientCompatibilityLevel() 417 { 418 return NetClientHandler.getConnectionCompatibilityLevel(); 419 } 420 }