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 }