001    package net.minecraftforge.client;
002    
003    import java.io.File;
004    import java.io.IOException;
005    import java.lang.reflect.Field;
006    import java.util.logging.Level;
007    
008    import cpw.mods.fml.client.FMLClientHandler;
009    import cpw.mods.fml.common.FMLLog;
010    
011    import paulscode.sound.SoundSystemConfig;
012    import paulscode.sound.codecs.CodecIBXM;
013    
014    import net.minecraft.client.Minecraft;
015    import net.minecraft.src.*;
016    
017    public class ModCompatibilityClient
018    {
019        /**
020         * Tries to get the class for the specified name, will also try the
021         * net.minecraft.src package in case we are in MCP
022         * Returns null if not found.
023         *
024         * @param name The class name
025         * @return The Class, or null if not found
026         */
027        private static Class getClass(String name)
028        {
029            try
030            {
031                return Class.forName(name);
032            }
033            catch (Exception e)
034            {
035                try
036                {
037                    return Class.forName("net.minecraft.src." + name);
038                }
039                catch (Exception e2)
040                {
041                    return null;
042                }
043            }
044        }
045    
046        /************************************************************************************************
047         * Risugami's AudioMod Compatibility
048         * http://www.minecraftforum.net/topic/75440-
049         *
050         * AudioMod adds a few extra codecs, loads audio from /resources/mods/*,
051         * introduces the concept of 'cave' sounds, which are determined by if
052         * the player is underneath a solid block.
053         *
054         * It also lowers the interval between background music songs to 6000
055         */
056        public static SoundPool audioModSoundPoolCave;
057    
058        /**
059         * Populates the sound pools with with sounds from the /resources/mods folder
060         * And sets the interval between background music to 6000
061         *
062         * @param mngr The SoundManager instance
063         */
064        public static void audioModLoad(SoundManager mngr)
065        {
066            audioModSoundPoolCave = new SoundPool();
067            audioModLoadModAudio("resources/mod/sound", mngr.soundPoolSounds);
068            audioModLoadModAudio("resources/mod/streaming", mngr.soundPoolStreaming);
069            audioModLoadModAudio("resources/mod/music", mngr.soundPoolMusic);
070            audioModLoadModAudio("resources/mod/cavemusic", audioModSoundPoolCave);
071    
072            if (mngr.MUSIC_INTERVAL == 12000)
073            {
074                mngr.MUSIC_INTERVAL = 6000;
075            }
076        }
077    
078        /**
079         * Walks the given path in the Minecraft app directory and adds audio to the SoundPool
080         * 
081         * @param path The path to walk
082         * @param pool The pool to add sound to
083         */
084        private static void audioModLoadModAudio(String path, SoundPool pool)
085        {
086            File folder = new File(Minecraft.getMinecraftDir(), path);
087    
088            try
089            {
090                audioModWalkFolder(folder, folder, pool);
091            }
092            catch (IOException ex)
093            {
094                FMLLog.log(Level.FINE, ex, "Loading Mod audio failed for folder: %s", path);
095                ex.printStackTrace();
096            }
097        }
098    
099        /**
100         * Walks the folder path recursively and calls pool.addSound on any file it finds.
101         *
102         * @param base The base path for the folder, determines the name when calling addSound
103         * @param folder The current folder
104         * @param pool The SoundPool to add the sound to
105         * @throws IOException
106         */
107        private static void audioModWalkFolder(File base, File folder, SoundPool pool) throws IOException
108        {
109            if (folder.exists() || folder.mkdirs())
110            {
111                for (File file : folder.listFiles())
112                {
113                    if (!file.getName().startsWith("."))
114                    {
115                        if (file.isDirectory())
116                        {
117                            audioModWalkFolder(base, file, pool);
118                        }
119                        else if (file.isFile())
120                        {
121                            String subpath = file.getPath().substring(base.getPath().length() + 1).replace('\\', '/');
122                            pool.addSound(subpath, file);
123                        }
124                    }
125                }
126            }
127        }
128    
129        /**
130         * Adds the IBXM codec and associates it with .xm, .s3m, and .mod
131         */
132        public static void audioModAddCodecs()
133        {
134            SoundSystemConfig.setCodec("xm",  CodecIBXM.class);
135            SoundSystemConfig.setCodec("s3m", CodecIBXM.class);
136            SoundSystemConfig.setCodec("mod", CodecIBXM.class);
137        }
138    
139        /**
140         * If the current player is underground, it picks a random song from the cave sound pool,
141         * if they are not it returns the passed in entry.
142         *
143         * @param soundManager The SoundManager instance
144         * @param current The currently selected entry
145         * @return A soundPool entry to be played as the background music
146         */
147        public static SoundPoolEntry audioModPickBackgroundMusic(SoundManager soundManager, SoundPoolEntry current)
148        {
149            Minecraft mc = FMLClientHandler.instance().getClient();
150            if (mc != null && mc.theWorld != null && audioModSoundPoolCave != null)
151            {
152                Entity ent = mc.renderViewEntity;
153                int x = MathHelper.truncateDoubleToInt(ent.posX);
154                int y = MathHelper.truncateDoubleToInt(ent.posY);
155                int z = MathHelper.truncateDoubleToInt(ent.posZ);
156                return (mc.theWorld.canBlockSeeTheSky(x, y, z) ? current : audioModSoundPoolCave.getRandomSound());
157            }
158            return current;
159        }
160    
161        /***********************************************************************************************************
162         * SDK's ModLoaderMP
163         * http://www.minecraftforum.net/topic/86765-
164         *
165         * ModLoaderMP was supposed to be a reliable server side version of ModLoader, however it has
166         * gotten the reputation of being really slow to update. Never having bugfixes, breaking compatibility
167         * with the client side ModLoader.
168         *
169         * So we have replaced it with our own system called FML (Forge ModLoader)
170         * it is a stand alone mod, that Forge relies on, and that is open source/community driven.
171         * https://github.com/cpw/FML
172         *
173         * However, for compatibilities sake, we provide the ModLoaderMP's hooks so that the end user
174         * does not need to make a choice between the two on the client side.
175         **/
176        private static int isMLMPInstalled = -1;
177    
178        /**
179         * Determine if ModLoaderMP is installed by checking for the existence of the BaseModMp class.
180         * @return True if BaseModMp was installed (indicating the existance of MLMP)
181         */
182        public static boolean isMLMPInstalled()
183        {
184            if (isMLMPInstalled == -1)
185            {
186                isMLMPInstalled = (getClass("ModLoaderMp") != null ? 1 : 0);
187            }
188            return isMLMPInstalled == 1;
189        }
190    
191        /**
192         * Attempts to spawn a vehicle using ModLoaderMP's vehicle spawn registry, if MLMP is not installed
193         * it returns the passed in currentEntity
194         *
195         * @param type The Type ID of the vehicle
196         * @param world The current world
197         * @param x The spawn X position
198         * @param y The spawn Y position
199         * @param z The spawn Z position
200         * @param thrower The entity that spawned the vehicle {possibly null}
201         * @param currentEntity The current value to return if MLMP is not installed
202         * @return The new spawned entity
203         * @throws Exception
204         */
205        public static Object mlmpVehicleSpawn(int type, World world, double x, double y, double z, Entity thrower, Object currentEntity) throws Exception
206        {
207            Class mlmp = getClass("ModLoaderMp");
208            if (!isMLMPInstalled() || mlmp == null)
209            {
210                return currentEntity;
211            }
212    
213            Object entry = mlmp.getDeclaredMethod("handleNetClientHandlerEntities", int.class).invoke(null, type);
214            if (entry == null)
215            {
216                return currentEntity;
217            }
218    
219            Class entityClass = (Class)entry.getClass().getDeclaredField("entityClass").get(entry);
220            Object ret = (Entity)entityClass.getConstructor(World.class, Double.TYPE, Double.TYPE, Double.TYPE).newInstance(world, x, y, z);
221    
222            if (entry.getClass().getDeclaredField("entityHasOwner").getBoolean(entry))
223            {
224                Field owner = entityClass.getField("owner");
225    
226                if (!Entity.class.isAssignableFrom(owner.getType()))
227                {
228                    throw new Exception(String.format("Entity\'s owner field must be of type Entity, but it is of type %s.", owner.getType()));
229                }
230    
231                if (thrower == null)
232                {
233                    System.out.println("Received spawn packet for entity with owner, but owner was not found.");
234                    FMLLog.fine("Received spawn packet for entity with owner, but owner was not found.");
235                }
236                else
237                {
238                    if (!owner.getType().isAssignableFrom(thrower.getClass()))
239                    {
240                        throw new Exception(String.format("Tried to assign an entity of type %s to entity owner, which is of type %s.", thrower.getClass(), owner.getType()));
241                    }
242    
243                    owner.set(ret, thrower);
244                }
245            }
246            return ret;
247        }
248    
249        /**
250         * Attempts to invoke ModLoaderMp.handleGUI if ModLoaderMP is installed.
251         * If not, it does nothing
252         *
253         * @param pkt The open window packet
254         */
255        public static void mlmpOpenWindow(Packet100OpenWindow pkt)
256        {
257            Class mlmp = getClass("ModLoaderMp");
258            if (!isMLMPInstalled() || mlmp == null)
259            {
260                return;
261            }
262    
263            try
264            {
265                mlmp.getDeclaredMethod("handleGUI", Packet100OpenWindow.class).invoke(null, pkt);
266            }
267            catch (Exception e)
268            {
269                e.printStackTrace();
270            }
271        }
272    }