    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}