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