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