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         * Trys 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         * @param path The path to walk
081         * @param pool The pool to add sound to
082         */
083        private static void audioModLoadModAudio(String path, SoundPool pool)
084        {
085            File folder = new File(Minecraft.getMinecraftDir(), path);
086    
087            try
088            {
089                audioModWalkFolder(folder, folder, pool);
090            }
091            catch (IOException ex)
092            {
093                FMLLog.log(Level.FINE, ex, "Loading Mod audio failed for folder: %s", path);
094                ex.printStackTrace();
095            }
096        }
097    
098        /**
099         * Walks the folder path recursively and calls pool.addSound on any file it finds.
100         *
101         * @param base The base path for the folder, determines the name when calling addSound
102         * @param folder The current folder
103         * @param pool The SoundPool to add the sound to
104         * @throws IOException
105         */
106        private static void audioModWalkFolder(File base, File folder, SoundPool pool) throws IOException
107        {
108            if (folder.exists() || folder.mkdirs())
109            {
110                for (File file : folder.listFiles())
111                {
112                    if (!file.getName().startsWith("."))
113                    {
114                        if (file.isDirectory())
115                        {
116                            audioModWalkFolder(base, file, pool);
117                        }
118                        else if (file.isFile())
119                        {
120                            String subpath = file.getPath().substring(base.getPath().length() + 1).replace('\\', '/');
121                            pool.addSound(subpath, file);
122                        }
123                    }
124                }
125            }
126        }
127    
128        /**
129         * Adds the IBXM codec and associates it with .xm, .s3m, and .mod
130         */
131        public static void audioModAddCodecs()
132        {
133            SoundSystemConfig.setCodec("xm",  CodecIBXM.class);
134            SoundSystemConfig.setCodec("s3m", CodecIBXM.class);
135            SoundSystemConfig.setCodec("mod", CodecIBXM.class);
136        }
137    
138        /**
139         * If the current player is underground, it picks a random song from the cave sound pool,
140         * if they are not it returns the passed in entry.
141         *
142         * @param soundManager The SoundManager instance
143         * @param current The currently selected entry
144         * @return A soundPool entry to be played as the background music
145         */
146        public static SoundPoolEntry audioModPickBackgroundMusic(SoundManager soundManager, SoundPoolEntry current)
147        {
148            Minecraft mc = FMLClientHandler.instance().getClient();
149            if (mc != null && mc.theWorld != null && audioModSoundPoolCave != null)
150            {
151                Entity ent = mc.renderViewEntity;
152                int x = MathHelper.truncateDoubleToInt(ent.posX);
153                int y = MathHelper.truncateDoubleToInt(ent.posY);
154                int z = MathHelper.truncateDoubleToInt(ent.posZ);
155                return (mc.theWorld.canBlockSeeTheSky(x, y, z) ? current : audioModSoundPoolCave.getRandomSound());
156            }
157            return current;
158        }
159    
160        /***********************************************************************************************************
161         * SDK's ModLoaderMP
162         * http://www.minecraftforum.net/topic/86765-
163         *
164         * ModLoaderMP was supposed to be a reliable server side version of ModLoader, however it has
165         * gotten the reputation of being really slow to update. Never having bugfixes, breaking compatibility
166         * with the client side ModLoader.
167         *
168         * So we have replaced it with our own system called FML (Forge ModLoader)
169         * it is a stand alone mod, that Forge relies on, and that is open source/community driven.
170         * https://github.com/cpw/FML
171         *
172         * However, for compatibilities sake, we provide the ModLoaderMP's hooks so that the end user
173         * does not need to make a choice between the two on the client side.
174         **/
175        private static int isMLMPInstalled = -1;
176    
177        /**
178         * Determine if ModLoaderMP is installed by checking for the existence of the BaseModMp class.
179         * @return True if BaseModMp was installed (indicating the existance of MLMP)
180         */
181        public static boolean isMLMPInstalled()
182        {
183            if (isMLMPInstalled == -1)
184            {
185                isMLMPInstalled = (getClass("ModLoaderMp") != null ? 1 : 0);
186            }
187            return isMLMPInstalled == 1;
188        }
189    
190        /**
191         * Attempts to spawn a vehicle using ModLoaderMP's vehicle spawn registry, if MLMP is not installed
192         * it returns the passed in currentEntity
193         *
194         * @param type The Type ID of the vehicle
195         * @param world The current world
196         * @param x The spawn X position
197         * @param y The spawn Y position
198         * @param z The spawn Z position
199         * @param thrower The entity that spawned the vehicle {possibly null}
200         * @param currentEntity The current value to return if MLMP is not installed
201         * @return The new spawned entity
202         * @throws Exception
203         */
204        public static Object mlmpVehicleSpawn(int type, World world, double x, double y, double z, Entity thrower, Object currentEntity) throws Exception
205        {
206            Class mlmp = getClass("ModLoaderMp");
207            if (!isMLMPInstalled() || mlmp == null)
208            {
209                return currentEntity;
210            }
211    
212            Object entry = mlmp.getDeclaredMethod("handleNetClientHandlerEntities", int.class).invoke(null, type);
213            if (entry == null)
214            {
215                return currentEntity;
216            }
217    
218            Class entityClass = (Class)entry.getClass().getDeclaredField("entityClass").get(entry);
219            Object ret = (Entity)entityClass.getConstructor(World.class, Double.TYPE, Double.TYPE, Double.TYPE).newInstance(world, x, y, z);
220    
221            if (entry.getClass().getDeclaredField("entityHasOwner").getBoolean(entry))
222            {
223                Field owner = entityClass.getField("owner");
224    
225                if (!Entity.class.isAssignableFrom(owner.getType()))
226                {
227                    throw new Exception(String.format("Entity\'s owner field must be of type Entity, but it is of type %s.", owner.getType()));
228                }
229    
230                if (thrower == null)
231                {
232                    System.out.println("Received spawn packet for entity with owner, but owner was not found.");
233                    FMLLog.fine("Received spawn packet for entity with owner, but owner was not found.");
234                }
235                else
236                {
237                    if (!owner.getType().isAssignableFrom(thrower.getClass()))
238                    {
239                        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()));
240                    }
241    
242                    owner.set(ret, thrower);
243                }
244            }
245            return ret;
246        }
247    
248        /**
249         * Attempts to invoke ModLoaderMp.handleGUI if ModLoaderMP is installed.
250         * If not, it does nothing
251         *
252         * @param pkt The open window packet
253         */
254        public static void mlmpOpenWindow(Packet100OpenWindow pkt)
255        {
256            Class mlmp = getClass("ModLoaderMp");
257            if (!isMLMPInstalled() || mlmp == null)
258            {
259                return;
260            }
261    
262            try
263            {
264                mlmp.getDeclaredMethod("handleGUI", Packet100OpenWindow.class).invoke(null, pkt);
265            }
266            catch (Exception e)
267            {
268                e.printStackTrace();
269            }
270        }
271    }