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