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