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 }