    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}