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