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    }