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