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