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 }