001package net.minecraftforge.client;
002
003import java.io.File;
004import java.io.IOException;
005import java.lang.reflect.Field;
006import java.util.logging.Level;
007
008import cpw.mods.fml.client.FMLClientHandler;
009import cpw.mods.fml.common.FMLLog;
010
011import paulscode.sound.SoundSystemConfig;
012import paulscode.sound.codecs.CodecIBXM;
013
014import net.minecraft.client.Minecraft;
015import net.minecraft.client.audio.SoundManager;
016import net.minecraft.client.audio.SoundPool;
017import net.minecraft.client.audio.SoundPoolEntry;
018import net.minecraft.entity.Entity;
019import net.minecraft.network.packet.Packet100OpenWindow;
020import net.minecraft.util.MathHelper;
021import net.minecraft.world.World;
022
023public 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}