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
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("FML v"+Loader.instance().getFMLVersionString());
232 String forgeBranding = (String) callForgeMethod("getBrandingVersion");
233 if (!Strings.isNullOrEmpty(forgeBranding))
234 {
235 brd.add(forgeBranding);
236 }
237 brd.addAll(sidedDelegate.getAdditionalBrandingInformation());
238 try {
239 Properties props=new Properties();
240 props.load(getClass().getClassLoader().getResourceAsStream("fmlbranding.properties"));
241 brd.add(props.getProperty("fmlbranding"));
242 } catch (Exception ex) {
243 // Ignore - no branding file found
244 }
245 int tModCount = Loader.instance().getModList().size();
246 int aModCount = Loader.instance().getActiveModList().size();
247 brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" ));
248 brandings = brd.build();
249 }
250 }
251 public List<String> getBrandings()
252 {
253 if (brandings == null)
254 {
255 computeBranding();
256 }
257 return ImmutableList.copyOf(brandings);
258 }
259
260 public IFMLSidedHandler getSidedDelegate()
261 {
262 return sidedDelegate;
263 }
264
265 public void onPostServerTick()
266 {
267 tickEnd(EnumSet.of(TickType.SERVER), Side.SERVER);
268 }
269
270 /**
271 * Every tick just after world and other ticks occur
272 */
273 public void onPostWorldTick(Object world)
274 {
275 tickEnd(EnumSet.of(TickType.WORLD), Side.SERVER, world);
276 }
277
278 public void onPreServerTick()
279 {
280 tickStart(EnumSet.of(TickType.SERVER), Side.SERVER);
281 }
282
283 /**
284 * Every tick just before world and other ticks occur
285 */
286 public void onPreWorldTick(Object world)
287 {
288 tickStart(EnumSet.of(TickType.WORLD), Side.SERVER, world);
289 }
290
291 public void onWorldLoadTick(World[] worlds)
292 {
293 rescheduleTicks(Side.SERVER);
294 for (World w : worlds)
295 {
296 tickStart(EnumSet.of(TickType.WORLDLOAD), Side.SERVER, w);
297 }
298 }
299
300 public void handleServerStarting(MinecraftServer server)
301 {
302 Loader.instance().serverStarting(server);
303 }
304
305 public void handleServerStarted()
306 {
307 Loader.instance().serverStarted();
308 }
309
310 public void handleServerStopping()
311 {
312 Loader.instance().serverStopping();
313 }
314
315 public MinecraftServer getMinecraftServerInstance()
316 {
317 return sidedDelegate.getServer();
318 }
319
320 public void showGuiScreen(Object clientGuiElement)
321 {
322 sidedDelegate.showGuiScreen(clientGuiElement);
323 }
324
325 public Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket entitySpawnPacket)
326 {
327 return sidedDelegate.spawnEntityIntoClientWorld(registration, entitySpawnPacket);
328 }
329
330 public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket)
331 {
332 sidedDelegate.adjustEntityLocationOnClient(entitySpawnAdjustmentPacket);
333 }
334
335 public void onServerStart(DedicatedServer dedicatedServer)
336 {
337 FMLServerHandler.instance();
338 sidedDelegate.beginServerLoading(dedicatedServer);
339 }
340
341 public void onServerStarted()
342 {
343 sidedDelegate.finishServerLoading();
344 }
345
346
347 public void onPreClientTick()
348 {
349 tickStart(EnumSet.of(TickType.CLIENT), Side.CLIENT);
350
351 }
352
353 public void onPostClientTick()
354 {
355 tickEnd(EnumSet.of(TickType.CLIENT), Side.CLIENT);
356 }
357
358 public void onRenderTickStart(float timer)
359 {
360 tickStart(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
361 }
362
363 public void onRenderTickEnd(float timer)
364 {
365 tickEnd(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
366 }
367
368 public void onPlayerPreTick(EntityPlayer player)
369 {
370 Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
371 tickStart(EnumSet.of(TickType.PLAYER), side, player);
372 }
373
374 public void onPlayerPostTick(EntityPlayer player)
375 {
376 Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
377 tickEnd(EnumSet.of(TickType.PLAYER), side, player);
378 }
379
380 public void registerCrashCallable(ICrashCallable callable)
381 {
382 crashCallables.add(callable);
383 }
384
385 public void enhanceCrashReport(CrashReport crashReport)
386 {
387 for (ICrashCallable call: crashCallables)
388 {
389 crashReport.addCrashSectionCallable(call.getLabel(), call);
390 }
391 }
392
393 public void handleTinyPacket(NetHandler handler, Packet131MapData mapData)
394 {
395 sidedDelegate.handleTinyPacket(handler, mapData);
396 }
397
398 public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
399 {
400 for (ModContainer mc : Loader.instance().getModList())
401 {
402 if (mc instanceof InjectedModContainer)
403 {
404 WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
405 if (wac != null)
406 {
407 NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo);
408 tagCompound.setCompoundTag(mc.getModId(), dataForWriting);
409 }
410 }
411 }
412 }
413
414 public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
415 {
416 if (getEffectiveSide()!=Side.SERVER)
417 {
418 return;
419 }
420 if (handlerSet.contains(handler))
421 {
422 return;
423 }
424 handlerSet.add(handler);
425 Map<String,NBTBase> additionalProperties = Maps.newHashMap();
426 worldInfo.setAdditionalProperties(additionalProperties);
427 for (ModContainer mc : Loader.instance().getModList())
428 {
429 if (mc instanceof InjectedModContainer)
430 {
431 WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
432 if (wac != null)
433 {
434 wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId()));
435 }
436 }
437 }
438 }
439 }