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