001/*
002 * Forge Mod Loader
003 * Copyright (c) 2012-2013 cpw.
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser Public License v2.1
006 * which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
008 * 
009 * Contributors:
010 *     cpw - implementation
011 */
012
013package cpw.mods.fml.common;
014
015import java.util.EnumSet;
016import java.util.List;
017import java.util.Map;
018import java.util.Properties;
019import java.util.Set;
020import java.util.logging.Level;
021import java.util.logging.Logger;
022
023import net.minecraft.crash.CrashReport;
024import net.minecraft.crash.CrashReportCategory;
025import net.minecraft.entity.Entity;
026import net.minecraft.entity.player.EntityPlayer;
027import net.minecraft.entity.player.EntityPlayerMP;
028import net.minecraft.nbt.NBTBase;
029import net.minecraft.nbt.NBTTagCompound;
030import net.minecraft.network.INetworkManager;
031import net.minecraft.network.packet.NetHandler;
032import net.minecraft.network.packet.Packet131MapData;
033import net.minecraft.server.MinecraftServer;
034import net.minecraft.server.ServerListenThread;
035import net.minecraft.server.ThreadMinecraftServer;
036import net.minecraft.server.dedicated.DedicatedServer;
037import net.minecraft.world.World;
038import net.minecraft.world.storage.SaveHandler;
039import net.minecraft.world.storage.WorldInfo;
040
041import com.google.common.base.Objects;
042import com.google.common.base.Strings;
043import com.google.common.collect.ImmutableList;
044import com.google.common.collect.ImmutableList.Builder;
045import com.google.common.collect.Lists;
046import com.google.common.collect.MapDifference;
047import com.google.common.collect.MapMaker;
048import com.google.common.collect.Maps;
049import com.google.common.collect.Sets;
050
051import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket;
052import cpw.mods.fml.common.network.EntitySpawnPacket;
053import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
054import cpw.mods.fml.common.registry.ItemData;
055import cpw.mods.fml.common.registry.TickRegistry;
056import cpw.mods.fml.relauncher.Side;
057import cpw.mods.fml.server.FMLServerHandler;
058
059
060/**
061 * The main class for non-obfuscated hook handling code
062 *
063 * Anything that doesn't require obfuscated or client/server specific code should
064 * go in this handler
065 *
066 * It also contains a reference to the sided handler instance that is valid
067 * allowing for common code to access specific properties from the obfuscated world
068 * without a direct dependency
069 *
070 * @author cpw
071 *
072 */
073public class FMLCommonHandler
074{
075    /**
076     * The singleton
077     */
078    private static final FMLCommonHandler INSTANCE = new FMLCommonHandler();
079    /**
080     * The delegate for side specific data and functions
081     */
082    private IFMLSidedHandler sidedDelegate;
083
084    private List<IScheduledTickHandler> scheduledClientTicks = Lists.newArrayList();
085    private List<IScheduledTickHandler> scheduledServerTicks = Lists.newArrayList();
086    private Class<?> forge;
087    private boolean noForge;
088    private List<String> brandings;
089    private List<ICrashCallable> crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation());
090    private Set<SaveHandler> handlerSet = Sets.newSetFromMap(new MapMaker().weakKeys().<SaveHandler,Boolean>makeMap());
091
092
093
094    public void beginLoading(IFMLSidedHandler handler)
095    {
096        sidedDelegate = handler;
097        FMLLog.log("MinecraftForge", Level.INFO, "Attempting early MinecraftForge initialization");
098        callForgeMethod("initialize");
099        callForgeMethod("registerCrashCallable");
100        FMLLog.log("MinecraftForge", Level.INFO, "Completed early MinecraftForge initialization");
101    }
102
103    public void rescheduleTicks(Side side)
104    {
105        TickRegistry.updateTickQueue(side.isClient() ? scheduledClientTicks : scheduledServerTicks, side);
106    }
107    public void tickStart(EnumSet<TickType> ticks, Side side, Object ... data)
108    {
109        List<IScheduledTickHandler> scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks;
110
111        if (scheduledTicks.size()==0)
112        {
113            return;
114        }
115        for (IScheduledTickHandler ticker : scheduledTicks)
116        {
117            EnumSet<TickType> ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class)));
118            ticksToRun.retainAll(ticks);
119            if (!ticksToRun.isEmpty())
120            {
121                ticker.tickStart(ticksToRun, data);
122            }
123        }
124    }
125
126    public void tickEnd(EnumSet<TickType> ticks, Side side, Object ... data)
127    {
128        List<IScheduledTickHandler> scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks;
129
130        if (scheduledTicks.size()==0)
131        {
132            return;
133        }
134        for (IScheduledTickHandler ticker : scheduledTicks)
135        {
136            EnumSet<TickType> ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class)));
137            ticksToRun.retainAll(ticks);
138            if (!ticksToRun.isEmpty())
139            {
140                ticker.tickEnd(ticksToRun, data);
141            }
142        }
143    }
144
145    /**
146     * @return the instance
147     */
148    public static FMLCommonHandler instance()
149    {
150        return INSTANCE;
151    }
152    /**
153     * Find the container that associates with the supplied mod object
154     * @param mod
155     */
156    public ModContainer findContainerFor(Object mod)
157    {
158        return Loader.instance().getReversedModObjectList().get(mod);
159    }
160    /**
161     * Get the forge mod loader logging instance (goes to the forgemodloader log file)
162     * @return The log instance for the FML log file
163     */
164    public Logger getFMLLogger()
165    {
166        return FMLLog.getLogger();
167    }
168
169    public Side getSide()
170    {
171        return sidedDelegate.getSide();
172    }
173
174    /**
175     * Return the effective side for the context in the game. This is dependent
176     * on thread analysis to try and determine whether the code is running in the
177     * server or not. Use at your own risk
178     */
179    public Side getEffectiveSide()
180    {
181        Thread thr = Thread.currentThread();
182        if ((thr instanceof ThreadMinecraftServer) || (thr instanceof ServerListenThread))
183        {
184            return Side.SERVER;
185        }
186
187        return Side.CLIENT;
188    }
189    /**
190     * Raise an exception
191     */
192    public void raiseException(Throwable exception, String message, boolean stopGame)
193    {
194        FMLLog.log(Level.SEVERE, exception, "Something raised an exception. The message was '%s'. 'stopGame' is %b", message, stopGame);
195        if (stopGame)
196        {
197            getSidedDelegate().haltGame(message,exception);
198        }
199    }
200
201
202    private Class<?> findMinecraftForge()
203    {
204        if (forge==null && !noForge)
205        {
206            try {
207                forge = Class.forName("net.minecraftforge.common.MinecraftForge");
208            } catch (Exception ex) {
209                noForge = true;
210            }
211        }
212        return forge;
213    }
214
215    private Object callForgeMethod(String method)
216    {
217        if (noForge)
218            return null;
219        try
220        {
221            return findMinecraftForge().getMethod(method).invoke(null);
222        }
223        catch (Exception e)
224        {
225            // No Forge installation
226            return null;
227        }
228    }
229
230    public void computeBranding()
231    {
232        if (brandings == null)
233        {
234            Builder brd = ImmutableList.<String>builder();
235            brd.add(Loader.instance().getMCVersionString());
236            brd.add(Loader.instance().getMCPVersionString());
237            brd.add("FML v"+Loader.instance().getFMLVersionString());
238            String forgeBranding = (String) callForgeMethod("getBrandingVersion");
239            if (!Strings.isNullOrEmpty(forgeBranding))
240            {
241                brd.add(forgeBranding);
242            }
243            if (sidedDelegate!=null)
244            {
245                brd.addAll(sidedDelegate.getAdditionalBrandingInformation());
246            }
247            try {
248                Properties props=new Properties();
249                props.load(getClass().getClassLoader().getResourceAsStream("fmlbranding.properties"));
250                brd.add(props.getProperty("fmlbranding"));
251            } catch (Exception ex) {
252                // Ignore - no branding file found
253            }
254            int tModCount = Loader.instance().getModList().size();
255            int aModCount = Loader.instance().getActiveModList().size();
256            brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" ));
257            brandings = brd.build();
258        }
259    }
260    public List<String> getBrandings()
261    {
262        if (brandings == null)
263        {
264            computeBranding();
265        }
266        return ImmutableList.copyOf(brandings);
267    }
268
269    public IFMLSidedHandler getSidedDelegate()
270    {
271        return sidedDelegate;
272    }
273
274    public void onPostServerTick()
275    {
276        tickEnd(EnumSet.of(TickType.SERVER), Side.SERVER);
277    }
278
279    /**
280     * Every tick just after world and other ticks occur
281     */
282    public void onPostWorldTick(Object world)
283    {
284        tickEnd(EnumSet.of(TickType.WORLD), Side.SERVER, world);
285    }
286
287    public void onPreServerTick()
288    {
289        tickStart(EnumSet.of(TickType.SERVER), Side.SERVER);
290    }
291
292    /**
293     * Every tick just before world and other ticks occur
294     */
295    public void onPreWorldTick(Object world)
296    {
297        tickStart(EnumSet.of(TickType.WORLD), Side.SERVER, world);
298    }
299
300    public void onWorldLoadTick(World[] worlds)
301    {
302        rescheduleTicks(Side.SERVER);
303        for (World w : worlds)
304        {
305            tickStart(EnumSet.of(TickType.WORLDLOAD), Side.SERVER, w);
306        }
307    }
308
309    public boolean handleServerAboutToStart(MinecraftServer server)
310    {
311        return Loader.instance().serverAboutToStart(server);
312    }
313
314    public boolean handleServerStarting(MinecraftServer server)
315    {
316        return Loader.instance().serverStarting(server);
317    }
318
319    public void handleServerStarted()
320    {
321        Loader.instance().serverStarted();
322    }
323
324    public void handleServerStopping()
325    {
326        Loader.instance().serverStopping();
327    }
328
329    public MinecraftServer getMinecraftServerInstance()
330    {
331        return sidedDelegate.getServer();
332    }
333
334    public void showGuiScreen(Object clientGuiElement)
335    {
336        sidedDelegate.showGuiScreen(clientGuiElement);
337    }
338
339    public Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket entitySpawnPacket)
340    {
341        return sidedDelegate.spawnEntityIntoClientWorld(registration, entitySpawnPacket);
342    }
343
344    public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket)
345    {
346        sidedDelegate.adjustEntityLocationOnClient(entitySpawnAdjustmentPacket);
347    }
348
349    public void onServerStart(DedicatedServer dedicatedServer)
350    {
351        FMLServerHandler.instance();
352        sidedDelegate.beginServerLoading(dedicatedServer);
353    }
354
355    public void onServerStarted()
356    {
357        sidedDelegate.finishServerLoading();
358    }
359
360
361    public void onPreClientTick()
362    {
363        tickStart(EnumSet.of(TickType.CLIENT), Side.CLIENT);
364
365    }
366
367    public void onPostClientTick()
368    {
369        tickEnd(EnumSet.of(TickType.CLIENT), Side.CLIENT);
370    }
371
372    public void onRenderTickStart(float timer)
373    {
374        tickStart(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
375    }
376
377    public void onRenderTickEnd(float timer)
378    {
379        tickEnd(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
380    }
381
382    public void onPlayerPreTick(EntityPlayer player)
383    {
384        Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
385        tickStart(EnumSet.of(TickType.PLAYER), side, player);
386    }
387
388    public void onPlayerPostTick(EntityPlayer player)
389    {
390        Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
391        tickEnd(EnumSet.of(TickType.PLAYER), side, player);
392    }
393
394    public void registerCrashCallable(ICrashCallable callable)
395    {
396        crashCallables.add(callable);
397    }
398
399    public void enhanceCrashReport(CrashReport crashReport, CrashReportCategory category)
400    {
401        for (ICrashCallable call: crashCallables)
402        {
403            category.addCrashSectionCallable(call.getLabel(), call);
404        }
405    }
406
407    public void handleTinyPacket(NetHandler handler, Packet131MapData mapData)
408    {
409        sidedDelegate.handleTinyPacket(handler, mapData);
410    }
411
412    public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
413    {
414        for (ModContainer mc : Loader.instance().getModList())
415        {
416            if (mc instanceof InjectedModContainer)
417            {
418                WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
419                if (wac != null)
420                {
421                    NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo);
422                    tagCompound.setCompoundTag(mc.getModId(), dataForWriting);
423                }
424            }
425        }
426    }
427
428    public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
429    {
430        if (getEffectiveSide()!=Side.SERVER)
431        {
432            return;
433        }
434        if (handlerSet.contains(handler))
435        {
436            return;
437        }
438        handlerSet.add(handler);
439        Map<String,NBTBase> additionalProperties = Maps.newHashMap();
440        worldInfo.setAdditionalProperties(additionalProperties);
441        for (ModContainer mc : Loader.instance().getModList())
442        {
443            if (mc instanceof InjectedModContainer)
444            {
445                WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
446                if (wac != null)
447                {
448                    wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId()));
449                }
450            }
451        }
452    }
453
454    public boolean shouldServerBeKilledQuietly()
455    {
456        if (sidedDelegate == null)
457        {
458            return false;
459        }
460        return sidedDelegate.shouldServerShouldBeKilledQuietly();
461    }
462
463    public void disconnectIDMismatch(MapDifference<Integer, ItemData> serverDifference, NetHandler toKill, INetworkManager network)
464    {
465        sidedDelegate.disconnectIDMismatch(serverDifference, toKill, network);
466    }
467
468    public void handleServerStopped()
469    {
470        Loader.instance().serverStopped();
471    }
472}