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