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