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