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