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