    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.server.FMLServerHandler;
    055
    056
    057    /**
    058     * The main class for non-obfuscated hook handling code
    059     *
    060     * Anything that doesn't require obfuscated or client/server specific code should
    061     * go in this handler
    062     *
    063     * It also contains a reference to the sided handler instance that is valid
    064     * allowing for common code to access specific properties from the obfuscated world
    065     * without a direct dependency
    066     *
    067     * @author cpw
    068     *
    069     */
    070    public class FMLCommonHandler
    071    {
    072        /**
    073         * The singleton
    074         */
    075        private static final FMLCommonHandler INSTANCE = new FMLCommonHandler();
    076        /**
    077         * The delegate for side specific data and functions
    078         */
    079        private IFMLSidedHandler sidedDelegate;
    080
    081        private List<IScheduledTickHandler> scheduledClientTicks = Lists.newArrayList();
    082        private List<IScheduledTickHandler> scheduledServerTicks = Lists.newArrayList();
    083        private Class<?> forge;
    084        private boolean noForge;
    085        private List<String> brandings;
    086        private List<ICrashCallable> crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation());
    087        private Set<SaveHandler> handlerSet = Sets.newSetFromMap(new MapMaker().weakKeys().<SaveHandler,Boolean>makeMap());
    088
    089
    090
    091        public void beginLoading(IFMLSidedHandler handler)
    092        {
    093            sidedDelegate = handler;
    094            FMLLog.info("Attempting early MinecraftForge initialization");
    095            callForgeMethod("initialize");
    096            callForgeMethod("registerCrashCallable");
    097            FMLLog.info("Completed early MinecraftForge initialization");
    098        }
    099
    100        public void rescheduleTicks(Side side)
    101        {
    102            TickRegistry.updateTickQueue(side.isClient() ? scheduledClientTicks : scheduledServerTicks, side);
    103        }
    104        public void tickStart(EnumSet<TickType> ticks, Side side, Object ... data)
    105        {
    106            List<IScheduledTickHandler> scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks;
    107
    108            if (scheduledTicks.size()==0)
    109            {
    110                return;
    111            }
    112            for (IScheduledTickHandler ticker : scheduledTicks)
    113            {
    114                EnumSet<TickType> ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class)));
    115                ticksToRun.removeAll(EnumSet.complementOf(ticks));
    116                if (!ticksToRun.isEmpty())
    117                {
    118                    ticker.tickStart(ticksToRun, data);
    119                }
    120            }
    121        }
    122
    123        public void tickEnd(EnumSet<TickType> ticks, Side side, Object ... data)
    124        {
    125            List<IScheduledTickHandler> scheduledTicks = side.isClient() ? scheduledClientTicks : scheduledServerTicks;
    126
    127            if (scheduledTicks.size()==0)
    128            {
    129                return;
    130            }
    131            for (IScheduledTickHandler ticker : scheduledTicks)
    132            {
    133                EnumSet<TickType> ticksToRun = EnumSet.copyOf(Objects.firstNonNull(ticker.ticks(), EnumSet.noneOf(TickType.class)));
    134                ticksToRun.removeAll(EnumSet.complementOf(ticks));
    135                if (!ticksToRun.isEmpty())
    136                {
    137                    ticker.tickEnd(ticksToRun, data);
    138                }
    139            }
    140        }
    141
    142        /**
    143         * @return the instance
    144         */
    145        public static FMLCommonHandler instance()
    146        {
    147            return INSTANCE;
    148        }
    149        /**
    150         * Find the container that associates with the supplied mod object
    151         * @param mod
    152         */
    153        public ModContainer findContainerFor(Object mod)
    154        {
    155            return Loader.instance().getReversedModObjectList().get(mod);
    156        }
    157        /**
    158         * Get the forge mod loader logging instance (goes to the forgemodloader log file)
    159         * @return The log instance for the FML log file
    160         */
    161        public Logger getFMLLogger()
    162        {
    163            return FMLLog.getLogger();
    164        }
    165
    166        public Side getSide()
    167        {
    168            return sidedDelegate.getSide();
    169        }
    170
    171        /**
    172         * Return the effective side for the context in the game. This is dependent
    173         * on thread analysis to try and determine whether the code is running in the
    174         * server or not. Use at your own risk
    175         */
    176        public Side getEffectiveSide()
    177        {
    178            Thread thr = Thread.currentThread();
    179            if ((thr instanceof ThreadMinecraftServer) || (thr instanceof ServerListenThread))
    180            {
    181                return Side.SERVER;
    182            }
    183
    184            return Side.CLIENT;
    185        }
    186        /**
    187         * Raise an exception
    188         */
    189        public void raiseException(Throwable exception, String message, boolean stopGame)
    190        {
    191            FMLCommonHandler.instance().getFMLLogger().throwing("FMLHandler", "raiseException", exception);
    192            if (stopGame)
    193            {
    194                getSidedDelegate().haltGame(message,exception);
    195            }
    196        }
    197
    198
    199        private Class<?> findMinecraftForge()
    200        {
    201            if (forge==null && !noForge)
    202            {
    203                try {
    204                    forge = Class.forName("net.minecraftforge.common.MinecraftForge");
    205                } catch (Exception ex) {
    206                    noForge = true;
    207                }
    208            }
    209            return forge;
    210        }
    211
    212        private Object callForgeMethod(String method)
    213        {
    214            if (noForge)
    215                return null;
    216            try
    217            {
    218                return findMinecraftForge().getMethod(method).invoke(null);
    219            }
    220            catch (Exception e)
    221            {
    222                // No Forge installation
    223                return null;
    224            }
    225        }
    226
    227        public void computeBranding()
    228        {
    229            if (brandings == null)
    230            {
    231                Builder brd = ImmutableList.<String>builder();
    232                brd.add(Loader.instance().getMCVersionString());
    233                brd.add(Loader.instance().getMCPVersionString());
    234                brd.add("FML v"+Loader.instance().getFMLVersionString());
    235                String forgeBranding = (String) callForgeMethod("getBrandingVersion");
    236                if (!Strings.isNullOrEmpty(forgeBranding))
    237                {
    238                    brd.add(forgeBranding);
    239                }
    240                if (sidedDelegate!=null)
    241                {
    242                    brd.addAll(sidedDelegate.getAdditionalBrandingInformation());
    243                }
    244                try {
    245                    Properties props=new Properties();
    246                    props.load(getClass().getClassLoader().getResourceAsStream("fmlbranding.properties"));
    247                    brd.add(props.getProperty("fmlbranding"));
    248                } catch (Exception ex) {
    249                    // Ignore - no branding file found
    250                }
    251                int tModCount = Loader.instance().getModList().size();
    252                int aModCount = Loader.instance().getActiveModList().size();
    253                brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" ));
    254                brandings = brd.build();
    255            }
    256        }
    257        public List<String> getBrandings()
    258        {
    259            if (brandings == null)
    260            {
    261                computeBranding();
    262            }
    263            return ImmutableList.copyOf(brandings);
    264        }
    265
    266        public IFMLSidedHandler getSidedDelegate()
    267        {
    268            return sidedDelegate;
    269        }
    270
    271        public void onPostServerTick()
    272        {
    273            tickEnd(EnumSet.of(TickType.SERVER), Side.SERVER);
    274        }
    275
    276        /**
    277         * Every tick just after world and other ticks occur
    278         */
    279        public void onPostWorldTick(Object world)
    280        {
    281            tickEnd(EnumSet.of(TickType.WORLD), Side.SERVER, world);
    282        }
    283
    284        public void onPreServerTick()
    285        {
    286            tickStart(EnumSet.of(TickType.SERVER), Side.SERVER);
    287        }
    288
    289        /**
    290         * Every tick just before world and other ticks occur
    291         */
    292        public void onPreWorldTick(Object world)
    293        {
    294            tickStart(EnumSet.of(TickType.WORLD), Side.SERVER, world);
    295        }
    296
    297        public void onWorldLoadTick(World[] worlds)
    298        {
    299            rescheduleTicks(Side.SERVER);
    300            for (World w : worlds)
    301            {
    302                tickStart(EnumSet.of(TickType.WORLDLOAD), Side.SERVER, w);
    303            }
    304        }
    305
    306        public void handleServerStarting(MinecraftServer server)
    307        {
    308            Loader.instance().serverStarting(server);
    309        }
    310
    311        public void handleServerStarted()
    312        {
    313            Loader.instance().serverStarted();
    314        }
    315
    316        public void handleServerStopping()
    317        {
    318            Loader.instance().serverStopping();
    319        }
    320
    321        public MinecraftServer getMinecraftServerInstance()
    322        {
    323            return sidedDelegate.getServer();
    324        }
    325
    326        public void showGuiScreen(Object clientGuiElement)
    327        {
    328            sidedDelegate.showGuiScreen(clientGuiElement);
    329        }
    330
    331        public Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket entitySpawnPacket)
    332        {
    333            return sidedDelegate.spawnEntityIntoClientWorld(registration, entitySpawnPacket);
    334        }
    335
    336        public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket)
    337        {
    338            sidedDelegate.adjustEntityLocationOnClient(entitySpawnAdjustmentPacket);
    339        }
    340
    341        public void onServerStart(DedicatedServer dedicatedServer)
    342        {
    343            FMLServerHandler.instance();
    344            sidedDelegate.beginServerLoading(dedicatedServer);
    345        }
    346
    347        public void onServerStarted()
    348        {
    349            sidedDelegate.finishServerLoading();
    350        }
    351
    352
    353        public void onPreClientTick()
    354        {
    355            tickStart(EnumSet.of(TickType.CLIENT), Side.CLIENT);
    356
    357        }
    358
    359        public void onPostClientTick()
    360        {
    361            tickEnd(EnumSet.of(TickType.CLIENT), Side.CLIENT);
    362        }
    363
    364        public void onRenderTickStart(float timer)
    365        {
    366            tickStart(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
    367        }
    368
    369        public void onRenderTickEnd(float timer)
    370        {
    371            tickEnd(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
    372        }
    373
    374        public void onPlayerPreTick(EntityPlayer player)
    375        {
    376            Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
    377            tickStart(EnumSet.of(TickType.PLAYER), side, player);
    378        }
    379
    380        public void onPlayerPostTick(EntityPlayer player)
    381        {
    382            Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
    383            tickEnd(EnumSet.of(TickType.PLAYER), side, player);
    384        }
    385
    386        public void registerCrashCallable(ICrashCallable callable)
    387        {
    388            crashCallables.add(callable);
    389        }
    390
    391        public void enhanceCrashReport(CrashReport crashReport, CrashReportCategory category)
    392        {
    393            for (ICrashCallable call: crashCallables)
    394            {
    395                category.addCrashSectionCallable(call.getLabel(), call);
    396            }
    397        }
    398
    399        public void handleTinyPacket(NetHandler handler, Packet131MapData mapData)
    400        {
    401            sidedDelegate.handleTinyPacket(handler, mapData);
    402        }
    403
    404        public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
    405        {
    406            for (ModContainer mc : Loader.instance().getModList())
    407            {
    408                if (mc instanceof InjectedModContainer)
    409                {
    410                    WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
    411                    if (wac != null)
    412                    {
    413                        NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo);
    414                        tagCompound.setCompoundTag(mc.getModId(), dataForWriting);
    415                    }
    416                }
    417            }
    418        }
    419
    420        public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
    421        {
    422            if (getEffectiveSide()!=Side.SERVER)
    423            {
    424                return;
    425            }
    426            if (handlerSet.contains(handler))
    427            {
    428                return;
    429            }
    430            handlerSet.add(handler);
    431            Map<String,NBTBase> additionalProperties = Maps.newHashMap();
    432            worldInfo.setAdditionalProperties(additionalProperties);
    433            for (ModContainer mc : Loader.instance().getModList())
    434            {
    435                if (mc instanceof InjectedModContainer)
    436                {
    437                    WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
    438                    if (wac != null)
    439                    {
    440                        wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId()));
    441                    }
    442                }
    443            }
    444        }
    445
    446        public boolean shouldServerBeKilledQuietly()
    447        {
    448            return sidedDelegate.shouldServerShouldBeKilledQuietly();
    449        }
    450
    451        public void disconnectIDMismatch(MapDifference<Integer, ItemData> serverDifference, NetHandler toKill, INetworkManager network)
    452        {
    453            sidedDelegate.disconnectIDMismatch(serverDifference, toKill, network);
    454        }
    455    }