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