    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.Level;
    022    import java.util.logging.Logger;
    023
    024    import net.minecraft.server.MinecraftServer;
    025    import net.minecraft.src.CrashReport;
    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.ThreadServerApplication;
    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.base.Throwables;
    043    import com.google.common.collect.ImmutableList;
    044    import com.google.common.collect.ImmutableList.Builder;
    045    import com.google.common.collect.Lists;
    046    import com.google.common.collect.MapMaker;
    047    import com.google.common.collect.Maps;
    048    import com.google.common.collect.Sets;
    049
    050    import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket;
    051    import cpw.mods.fml.common.network.EntitySpawnPacket;
    052    import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
    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
    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 ThreadServerApplication) || (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("FML v"+Loader.instance().getFMLVersionString());
    234                String forgeBranding = (String) callForgeMethod("getBrandingVersion");
    235                if (!Strings.isNullOrEmpty(forgeBranding))
    236                {
    237                    brd.add(forgeBranding);
    238                }
    239                brd.addAll(sidedDelegate.getAdditionalBrandingInformation());
    240                try {
    241                    Properties props=new Properties();
    242                    props.load(getClass().getClassLoader().getResourceAsStream("fmlbranding.properties"));
    243                    brd.add(props.getProperty("fmlbranding"));
    244                } catch (Exception ex) {
    245                    // Ignore - no branding file found
    246                }
    247                int tModCount = Loader.instance().getModList().size();
    248                int aModCount = Loader.instance().getActiveModList().size();
    249                brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" ));
    250                brandings = brd.build();
    251            }
    252        }
    253        public List<String> getBrandings()
    254        {
    255            if (brandings == null)
    256            {
    257                computeBranding();
    258            }
    259            return ImmutableList.copyOf(brandings);
    260        }
    261
    262        public IFMLSidedHandler getSidedDelegate()
    263        {
    264            return sidedDelegate;
    265        }
    266
    267        public void onPostServerTick()
    268        {
    269            tickEnd(EnumSet.of(TickType.SERVER), Side.SERVER);
    270        }
    271
    272        /**
    273         * Every tick just after world and other ticks occur
    274         */
    275        public void onPostWorldTick(Object world)
    276        {
    277            tickEnd(EnumSet.of(TickType.WORLD), Side.SERVER, world);
    278        }
    279
    280        public void onPreServerTick()
    281        {
    282            tickStart(EnumSet.of(TickType.SERVER), Side.SERVER);
    283        }
    284
    285        /**
    286         * Every tick just before world and other ticks occur
    287         */
    288        public void onPreWorldTick(Object world)
    289        {
    290            tickStart(EnumSet.of(TickType.WORLD), Side.SERVER, world);
    291        }
    292
    293        public void onWorldLoadTick(World[] worlds)
    294        {
    295            rescheduleTicks(Side.SERVER);
    296            for (World w : worlds)
    297            {
    298                tickStart(EnumSet.of(TickType.WORLDLOAD), Side.SERVER, w);
    299            }
    300        }
    301
    302        public void handleServerStarting(MinecraftServer server)
    303        {
    304            Loader.instance().serverStarting(server);
    305        }
    306
    307        public void handleServerStarted()
    308        {
    309            Loader.instance().serverStarted();
    310        }
    311
    312        public void handleServerStopping()
    313        {
    314            Loader.instance().serverStopping();
    315        }
    316
    317        public MinecraftServer getMinecraftServerInstance()
    318        {
    319            return sidedDelegate.getServer();
    320        }
    321
    322        public void showGuiScreen(Object clientGuiElement)
    323        {
    324            sidedDelegate.showGuiScreen(clientGuiElement);
    325        }
    326
    327        public Entity spawnEntityIntoClientWorld(EntityRegistration registration, EntitySpawnPacket entitySpawnPacket)
    328        {
    329            return sidedDelegate.spawnEntityIntoClientWorld(registration, entitySpawnPacket);
    330        }
    331
    332        public void adjustEntityLocationOnClient(EntitySpawnAdjustmentPacket entitySpawnAdjustmentPacket)
    333        {
    334            sidedDelegate.adjustEntityLocationOnClient(entitySpawnAdjustmentPacket);
    335        }
    336
    337        public void onServerStart(DedicatedServer dedicatedServer)
    338        {
    339            FMLServerHandler.instance();
    340            sidedDelegate.beginServerLoading(dedicatedServer);
    341        }
    342
    343        public void onServerStarted()
    344        {
    345            sidedDelegate.finishServerLoading();
    346        }
    347
    348
    349        public void onPreClientTick()
    350        {
    351            tickStart(EnumSet.of(TickType.CLIENT), Side.CLIENT);
    352
    353        }
    354
    355        public void onPostClientTick()
    356        {
    357            tickEnd(EnumSet.of(TickType.CLIENT), Side.CLIENT);
    358        }
    359
    360        public void onRenderTickStart(float timer)
    361        {
    362            tickStart(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
    363        }
    364
    365        public void onRenderTickEnd(float timer)
    366        {
    367            tickEnd(EnumSet.of(TickType.RENDER), Side.CLIENT, timer);
    368        }
    369
    370        public void onPlayerPreTick(EntityPlayer player)
    371        {
    372            Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
    373            tickStart(EnumSet.of(TickType.PLAYER), side, player);
    374        }
    375
    376        public void onPlayerPostTick(EntityPlayer player)
    377        {
    378            Side side = player instanceof EntityPlayerMP ? Side.SERVER : Side.CLIENT;
    379            tickEnd(EnumSet.of(TickType.PLAYER), side, player);
    380        }
    381
    382        public void registerCrashCallable(ICrashCallable callable)
    383        {
    384            crashCallables.add(callable);
    385        }
    386
    387        public void enhanceCrashReport(CrashReport crashReport)
    388        {
    389            for (ICrashCallable call: crashCallables)
    390            {
    391                crashReport.addCrashSectionCallable(call.getLabel(), call);
    392            }
    393        }
    394
    395        public void handleTinyPacket(NetHandler handler, Packet131MapData mapData)
    396        {
    397            sidedDelegate.handleTinyPacket(handler, mapData);
    398        }
    399
    400        public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
    401        {
    402            for (ModContainer mc : Loader.instance().getModList())
    403            {
    404                if (mc instanceof InjectedModContainer)
    405                {
    406                    WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
    407                    if (wac != null)
    408                    {
    409                        NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo);
    410                        tagCompound.setCompoundTag(mc.getModId(), dataForWriting);
    411                    }
    412                }
    413            }
    414        }
    415
    416        public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
    417        {
    418            if (getEffectiveSide()!=Side.SERVER)
    419            {
    420                return;
    421            }
    422            if (handlerSet.contains(handler))
    423            {
    424                return;
    425            }
    426            handlerSet.add(handler);
    427            Map<String,NBTBase> additionalProperties = Maps.newHashMap();
    428            worldInfo.setAdditionalProperties(additionalProperties);
    429            for (ModContainer mc : Loader.instance().getModList())
    430            {
    431                if (mc instanceof InjectedModContainer)
    432                {
    433                    WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
    434                    if (wac != null)
    435                    {
    436                        wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId()));
    437                    }
    438                }
    439            }
    440        }
    441    }