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