    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}