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