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