    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     */
    014    package cpw.mods.fml.common;
    015
    016    import java.util.EnumSet;
    017    import java.util.List;
    018    import java.util.Map;
    019    import java.util.Properties;
    020    import java.util.Set;
    021    import java.util.logging.Logger;
    022
    023    import net.minecraft.crash.CrashReport;
    024    import net.minecraft.crash.CrashReportCategory;
    025    import net.minecraft.entity.Entity;
    026    import net.minecraft.entity.player.EntityPlayer;
    027    import net.minecraft.entity.player.EntityPlayerMP;
    028    import net.minecraft.nbt.NBTBase;
    029    import net.minecraft.nbt.NBTTagCompound;
    030    import net.minecraft.network.INetworkManager;
    031    import net.minecraft.network.packet.NetHandler;
    032    import net.minecraft.network.packet.Packet131MapData;
    033    import net.minecraft.server.*;
    034    import net.minecraft.server.dedicated.DedicatedServer;
    035    import net.minecraft.world.World;
    036    import net.minecraft.world.storage.SaveHandler;
    037    import net.minecraft.world.storage.WorldInfo;
    038
    039    import com.google.common.base.Objects;
    040    import com.google.common.base.Strings;
    041    import com.google.common.collect.ImmutableList;
    042    import com.google.common.collect.ImmutableList.Builder;
    043    import com.google.common.collect.Lists;
    044    import com.google.common.collect.MapDifference;
    045    import com.google.common.collect.MapMaker;
    046    import com.google.common.collect.Maps;
    047    import com.google.common.collect.Sets;
    048
    049    import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket;
    050    import cpw.mods.fml.common.network.EntitySpawnPacket;
    051    import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
    052    import cpw.mods.fml.common.registry.ItemData;
    053    import cpw.mods.fml.common.registry.TickRegistry;
    054    import cpw.mods.fml.relauncher.Side;
    055    import 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     */
    071    public 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    }