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    }