    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}