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 }