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