001/* 002 * Forge Mod Loader 003 * Copyright (c) 2012-2013 cpw. 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser Public License v2.1 006 * which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 008 * 009 * Contributors: 010 * cpw - implementation 011 */ 012 013package cpw.mods.fml.common; 014 015import java.util.EnumSet; 016import java.util.List; 017import java.util.Map; 018import java.util.Properties; 019import java.util.Set; 020import java.util.logging.Level; 021import java.util.logging.Logger; 022 023import net.minecraft.crash.CrashReport; 024import net.minecraft.crash.CrashReportCategory; 025import net.minecraft.entity.Entity; 026import net.minecraft.entity.player.EntityPlayer; 027import net.minecraft.entity.player.EntityPlayerMP; 028import net.minecraft.nbt.NBTBase; 029import net.minecraft.nbt.NBTTagCompound; 030import net.minecraft.network.INetworkManager; 031import net.minecraft.network.packet.NetHandler; 032import net.minecraft.network.packet.Packet131MapData; 033import net.minecraft.server.MinecraftServer; 034import net.minecraft.server.ServerListenThread; 035import net.minecraft.server.ThreadMinecraftServer; 036import net.minecraft.server.dedicated.DedicatedServer; 037import net.minecraft.world.World; 038import net.minecraft.world.storage.SaveHandler; 039import net.minecraft.world.storage.WorldInfo; 040 041import com.google.common.base.Joiner; 042import com.google.common.base.Objects; 043import com.google.common.base.Strings; 044import com.google.common.collect.ImmutableList; 045import com.google.common.collect.ImmutableList.Builder; 046import com.google.common.collect.Lists; 047import com.google.common.collect.MapDifference; 048import com.google.common.collect.MapMaker; 049import com.google.common.collect.Maps; 050import com.google.common.collect.Sets; 051 052import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; 053import cpw.mods.fml.common.network.EntitySpawnPacket; 054import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; 055import cpw.mods.fml.common.registry.ItemData; 056import cpw.mods.fml.common.registry.TickRegistry; 057import cpw.mods.fml.relauncher.Side; 058import cpw.mods.fml.server.FMLServerHandler; 059 060 061/** 062 * The main class for non-obfuscated hook handling code 063 * 064 * Anything that doesn't require obfuscated or client/server specific code should 065 * go in this handler 066 * 067 * It also contains a reference to the sided handler instance that is valid 068 * allowing for common code to access specific properties from the obfuscated world 069 * without a direct dependency 070 * 071 * @author cpw 072 * 073 */ 074public class FMLCommonHandler 075{ 076 /** 077 * The singleton 078 */ 079 private static final FMLCommonHandler INSTANCE = new FMLCommonHandler(); 080 /** 081 * The delegate for side specific data and functions 082 */ 083 private IFMLSidedHandler sidedDelegate; 084 085 private List<IScheduledTickHandler> scheduledClientTicks = Lists.newArrayList(); 086 private List<IScheduledTickHandler> scheduledServerTicks = Lists.newArrayList(); 087 private Class<?> forge; 088 private boolean noForge; 089 private List<String> brandings; 090 private List<ICrashCallable> crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation()); 091 private Set<SaveHandler> handlerSet = Sets.newSetFromMap(new MapMaker().weakKeys().<SaveHandler,Boolean>makeMap()); 092 093 094 095 public void beginLoading(IFMLSidedHandler handler) 096 { 097 sidedDelegate = handler; 098 FMLLog.log("MinecraftForge", Level.INFO, "Attempting early MinecraftForge initialization"); 099 callForgeMethod("initialize"); 100 callForgeMethod("registerCrashCallable"); 101 FMLLog.log("MinecraftForge", Level.INFO, "Completed early MinecraftForge initialization"); 102 } 103 104 public void rescheduleTicks(Side side) 105 { 106 TickRegistry.updateTickQueue(side.isClient() ? scheduledClientTicks : scheduledServerTicks, side); 107 } 108 public void tickStart(EnumSet<TickType> ticks, Side side, Object ... data) 109 { 110 List<IScheduledTickHandler> scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks; 111 112 if (scheduledTicks.size()==0) 113 { 114 return; 115 } 116 for (IScheduledTickHandler ticker : scheduledTicks) 117 { 118 EnumSet<TickType> ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class))); 119 ticksToRun.retainAll(ticks); 120 if (!ticksToRun.isEmpty()) 121 { 122 ticker.tickStart(ticksToRun, data); 123 } 124 } 125 } 126 127 public void tickEnd(EnumSet<TickType> ticks, Side side, Object ... data) 128 { 129 List<IScheduledTickHandler> scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks; 130 131 if (scheduledTicks.size()==0) 132 { 133 return; 134 } 135 for (IScheduledTickHandler ticker : scheduledTicks) 136 { 137 EnumSet<TickType> ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class))); 138 ticksToRun.retainAll(ticks); 139 if (!ticksToRun.isEmpty()) 140 { 141 ticker.tickEnd(ticksToRun, data); 142 } 143 } 144 } 145 146 /** 147 * @return the instance 148 */ 149 public static FMLCommonHandler instance() 150 { 151 return INSTANCE; 152 } 153 /** 154 * Find the container that associates with the supplied mod object 155 * @param mod 156 */ 157 public ModContainer findContainerFor(Object mod) 158 { 159 return Loader.instance().getReversedModObjectList().get(mod); 160 } 161 /** 162 * Get the forge mod loader logging instance (goes to the forgemodloader log file) 163 * @return The log instance for the FML log file 164 */ 165 public Logger getFMLLogger() 166 { 167 return FMLLog.getLogger(); 168 } 169 170 public Side getSide() 171 { 172 return sidedDelegate.getSide(); 173 } 174 175 /** 176 * Return the effective side for the context in the game. This is dependent 177 * on thread analysis to try and determine whether the code is running in the 178 * server or not. Use at your own risk 179 */ 180 public Side getEffectiveSide() 181 { 182 Thread thr = Thread.currentThread(); 183 if ((thr instanceof ThreadMinecraftServer) || (thr instanceof ServerListenThread)) 184 { 185 return Side.SERVER; 186 } 187 188 return Side.CLIENT; 189 } 190 /** 191 * Raise an exception 192 */ 193 public void raiseException(Throwable exception, String message, boolean stopGame) 194 { 195 FMLLog.log(Level.SEVERE, exception, "Something raised an exception. The message was '%s'. 'stopGame' is %b", message, stopGame); 196 if (stopGame) 197 { 198 getSidedDelegate().haltGame(message,exception); 199 } 200 } 201 202 203 private Class<?> findMinecraftForge() 204 { 205 if (forge==null && !noForge) 206 { 207 try { 208 forge = Class.forName("net.minecraftforge.common.MinecraftForge"); 209 } catch (Exception ex) { 210 noForge = true; 211 } 212 } 213 return forge; 214 } 215 216 private Object callForgeMethod(String method) 217 { 218 if (noForge) 219 return null; 220 try 221 { 222 return findMinecraftForge().getMethod(method).invoke(null); 223 } 224 catch (Exception e) 225 { 226 // No Forge installation 227 return null; 228 } 229 } 230 231 public void computeBranding() 232 { 233 if (brandings == null) 234 { 235 Builder brd = ImmutableList.<String>builder(); 236 brd.add(Loader.instance().getMCVersionString()); 237 brd.add(Loader.instance().getMCPVersionString()); 238 brd.add("FML v"+Loader.instance().getFMLVersionString()); 239 String forgeBranding = (String) callForgeMethod("getBrandingVersion"); 240 if (!Strings.isNullOrEmpty(forgeBranding)) 241 { 242 brd.add(forgeBranding); 243 } 244 if (sidedDelegate!=null) 245 { 246 brd.addAll(sidedDelegate.getAdditionalBrandingInformation()); 247 } 248 if (Loader.instance().getFMLBrandingProperties().containsKey("fmlbranding")) 249 { 250 brd.add(Loader.instance().getFMLBrandingProperties().get("fmlbranding")); 251 } 252 int tModCount = Loader.instance().getModList().size(); 253 int aModCount = Loader.instance().getActiveModList().size(); 254 brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" )); 255 brandings = brd.build(); 256 } 257 } 258 public List<String> getBrandings() 259 { 260 if (brandings == null) 261 { 262 computeBranding(); 263 } 264 return ImmutableList.copyOf(brandings); 265 } 266 267 public IFMLSidedHandler getSidedDelegate() 268 { 269 return sidedDelegate; 270 } 271 272 public void onPostServerTick() 273 { 274 tickEnd(EnumSet.of(TickType.SERVER), Side.SERVER); 275 } 276 277 /** 278 * Every tick just after world and other ticks occur 279 */ 280 public void onPostWorldTick(Object world) 281 { 282 tickEnd(EnumSet.of(TickType.WORLD), Side.SERVER, world); 283 } 284 285 public void onPreServerTick() 286 { 287 tickStart(EnumSet.of(TickType.SERVER), Side.SERVER); 288 } 289 290 /** 291 * Every tick just before world and other ticks occur 292 */ 293 public void onPreWorldTick(Object world) 294 { 295 tickStart(EnumSet.of(TickType.WORLD), Side.SERVER, world); 296 } 297 298 public void onWorldLoadTick(World[] worlds) 299 { 300 rescheduleTicks(Side.SERVER); 301 for (World w : worlds) 302 { 303 tickStart(EnumSet.of(TickType.WORLDLOAD), Side.SERVER, w); 304 } 305 } 306 307 public boolean handleServerAboutToStart(MinecraftServer server) 308 { 309 return Loader.instance().serverAboutToStart(server); 310 } 311 312 public boolean handleServerStarting(MinecraftServer server) 313 { 314 return Loader.instance().serverStarting(server); 315 } 316 317 public void handleServerStarted() 318 { 319 Loader.instance().serverStarted(); 320 } 321 322 public void handleServerStopping() 323 { 324 Loader.instance().serverStopping(); 325 } 326 327 public MinecraftServer getMinecraftServerInstance() 328 { 329 return sidedDelegate.getServer(); 330 } 331 332 public void showGuiScreen(Object clientGuiElement) 333 { 334 sidedDelegate.showGuiScreen(clientGuiElement); 335 } 336 337 public Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket entitySpawnPacket) 338 { 339 return sidedDelegate.spawnEntityIntoClientWorld(registration, entitySpawnPacket); 340 } 341 342 public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket) 343 { 344 sidedDelegate.adjustEntityLocationOnClient(entitySpawnAdjustmentPacket); 345 } 346 347 public void onServerStart(DedicatedServer dedicatedServer) 348 { 349 FMLServerHandler.instance(); 350 sidedDelegate.beginServerLoading(dedicatedServer); 351 } 352 353 public void onServerStarted() 354 { 355 sidedDelegate.finishServerLoading(); 356 } 357 358 359 public void onPreClientTick() 360 { 361 tickStart(EnumSet.of(TickType.CLIENT), Side.CLIENT); 362 363 } 364 365 public void onPostClientTick() 366 { 367 tickEnd(EnumSet.of(TickType.CLIENT), Side.CLIENT); 368 } 369 370 public void onRenderTickStart(float timer) 371 { 372 tickStart(EnumSet.of(TickType.RENDER), Side.CLIENT, timer); 373 } 374 375 public void onRenderTickEnd(float timer) 376 { 377 tickEnd(EnumSet.of(TickType.RENDER), Side.CLIENT, timer); 378 } 379 380 public void onPlayerPreTick(EntityPlayer player) 381 { 382 Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT; 383 tickStart(EnumSet.of(TickType.PLAYER), side, player); 384 } 385 386 public void onPlayerPostTick(EntityPlayer player) 387 { 388 Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT; 389 tickEnd(EnumSet.of(TickType.PLAYER), side, player); 390 } 391 392 public void registerCrashCallable(ICrashCallable callable) 393 { 394 crashCallables.add(callable); 395 } 396 397 public void enhanceCrashReport(CrashReport crashReport, CrashReportCategory category) 398 { 399 for (ICrashCallable call: crashCallables) 400 { 401 category.addCrashSectionCallable(call.getLabel(), call); 402 } 403 } 404 405 public void handleTinyPacket(NetHandler handler, Packet131MapData mapData) 406 { 407 sidedDelegate.handleTinyPacket(handler, mapData); 408 } 409 410 public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound) 411 { 412 for (ModContainer mc : Loader.instance().getModList()) 413 { 414 if (mc instanceof InjectedModContainer) 415 { 416 WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); 417 if (wac != null) 418 { 419 NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo); 420 tagCompound.setCompoundTag(mc.getModId(), dataForWriting); 421 } 422 } 423 } 424 } 425 426 public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound) 427 { 428 if (getEffectiveSide()!=Side.SERVER) 429 { 430 return; 431 } 432 if (handlerSet.contains(handler)) 433 { 434 return; 435 } 436 handlerSet.add(handler); 437 Map<String,NBTBase> additionalProperties = Maps.newHashMap(); 438 worldInfo.setAdditionalProperties(additionalProperties); 439 for (ModContainer mc : Loader.instance().getModList()) 440 { 441 if (mc instanceof InjectedModContainer) 442 { 443 WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer(); 444 if (wac != null) 445 { 446 wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId())); 447 } 448 } 449 } 450 } 451 452 public boolean shouldServerBeKilledQuietly() 453 { 454 if (sidedDelegate == null) 455 { 456 return false; 457 } 458 return sidedDelegate.shouldServerShouldBeKilledQuietly(); 459 } 460 461 public void disconnectIDMismatch(MapDifference<Integer, ItemData> serverDifference, NetHandler toKill, INetworkManager network) 462 { 463 sidedDelegate.disconnectIDMismatch(serverDifference, toKill, network); 464 } 465 466 public void handleServerStopped() 467 { 468 Loader.instance().serverStopped(); 469 } 470 471 public String getModName() 472 { 473 List<String> modNames = Lists.newArrayListWithExpectedSize(3); 474 modNames.add("fml"); 475 if (!noForge) 476 { 477 modNames.add("forge"); 478 } 479 480 if (Loader.instance().getFMLBrandingProperties().containsKey("snooperbranding")) 481 { 482 modNames.add(Loader.instance().getFMLBrandingProperties().get("snooperbranding")); 483 } 484 return Joiner.on(',').join(modNames); 485 } 486}