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