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 }