001 package cpw.mods.fml.common.registry;
002
003 import java.util.BitSet;
004 import java.util.Iterator;
005 import java.util.List;
006 import java.util.Map;
007 import java.util.concurrent.Callable;
008 import java.util.logging.Level;
009
010 import net.minecraft.entity.Entity;
011 import net.minecraft.entity.EntityList;
012 import net.minecraft.entity.EntityLiving;
013 import net.minecraft.entity.EntityTracker;
014 import net.minecraft.entity.EnumCreatureType;
015 import net.minecraft.world.biome.BiomeGenBase;
016 import net.minecraft.world.biome.SpawnListEntry;
017
018 import com.google.common.base.Function;
019 import com.google.common.collect.ArrayListMultimap;
020 import com.google.common.collect.BiMap;
021 import com.google.common.collect.HashBiMap;
022 import com.google.common.collect.ListMultimap;
023 import com.google.common.collect.Maps;
024 import com.google.common.primitives.UnsignedBytes;
025 import com.google.common.primitives.UnsignedInteger;
026
027 import cpw.mods.fml.common.FMLCommonHandler;
028 import cpw.mods.fml.common.FMLLog;
029 import cpw.mods.fml.common.Loader;
030 import cpw.mods.fml.common.ModContainer;
031 import cpw.mods.fml.common.network.EntitySpawnPacket;
032 import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
033
034 public class EntityRegistry
035 {
036 public class EntityRegistration
037 {
038 private Class<? extends Entity> entityClass;
039 private ModContainer container;
040 private String entityName;
041 private int modId;
042 private int trackingRange;
043 private int updateFrequency;
044 private boolean sendsVelocityUpdates;
045 private Function<EntitySpawnPacket, Entity> customSpawnCallback;
046 private boolean usesVanillaSpawning;
047 public EntityRegistration(ModContainer mc, Class<? extends Entity> entityClass, String entityName, int id, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates)
048 {
049 this.container = mc;
050 this.entityClass = entityClass;
051 this.entityName = entityName;
052 this.modId = id;
053 this.trackingRange = trackingRange;
054 this.updateFrequency = updateFrequency;
055 this.sendsVelocityUpdates = sendsVelocityUpdates;
056 }
057 public Class<? extends Entity> getEntityClass()
058 {
059 return entityClass;
060 }
061 public ModContainer getContainer()
062 {
063 return container;
064 }
065 public String getEntityName()
066 {
067 return entityName;
068 }
069 public int getModEntityId()
070 {
071 return modId;
072 }
073 public int getTrackingRange()
074 {
075 return trackingRange;
076 }
077 public int getUpdateFrequency()
078 {
079 return updateFrequency;
080 }
081 public boolean sendsVelocityUpdates()
082 {
083 return sendsVelocityUpdates;
084 }
085
086 public boolean usesVanillaSpawning()
087 {
088 return usesVanillaSpawning;
089 }
090 public boolean hasCustomSpawning()
091 {
092 return customSpawnCallback != null;
093 }
094 public Entity doCustomSpawning(EntitySpawnPacket packet) throws Exception
095 {
096 return customSpawnCallback.apply(packet);
097 }
098 public void setCustomSpawning(Function<EntitySpawnPacket, Entity> callable, boolean usesVanillaSpawning)
099 {
100 this.customSpawnCallback = callable;
101 this.usesVanillaSpawning = usesVanillaSpawning;
102 }
103 }
104
105 private static final EntityRegistry INSTANCE = new EntityRegistry();
106
107 private BitSet availableIndicies;
108 private ListMultimap<ModContainer, EntityRegistration> entityRegistrations = ArrayListMultimap.create();
109 private Map<String,ModContainer> entityNames = Maps.newHashMap();
110 private BiMap<Class<? extends Entity>, EntityRegistration> entityClassRegistrations = HashBiMap.create();
111 public static EntityRegistry instance()
112 {
113 return INSTANCE;
114 }
115
116 private EntityRegistry()
117 {
118 availableIndicies = new BitSet(256);
119 availableIndicies.set(1,255);
120 for (Object id : EntityList.IDtoClassMapping.keySet())
121 {
122 availableIndicies.clear((Integer)id);
123 }
124 }
125
126 /**
127 * Register the mod entity type with FML
128
129 * @param entityClass The entity class
130 * @param entityName A unique name for the entity
131 * @param id A mod specific ID for the entity
132 * @param mod The mod
133 * @param trackingRange The range at which MC will send tracking updates
134 * @param updateFrequency The frequency of tracking updates
135 * @param sendsVelocityUpdates Whether to send velocity information packets as well
136 */
137 public static void registerModEntity(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates)
138 {
139 instance().doModEntityRegistration(entityClass, entityName, id, mod, trackingRange, updateFrequency, sendsVelocityUpdates);
140 }
141
142 private void doModEntityRegistration(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates)
143 {
144 ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod);
145 EntityRegistration er = new EntityRegistration(mc, entityClass, entityName, id, trackingRange, updateFrequency, sendsVelocityUpdates);
146 try
147 {
148 entityClassRegistrations.put(entityClass, er);
149 entityNames.put(entityName, mc);
150 if (!EntityList.classToStringMapping.containsKey(entityClass))
151 {
152 String entityModName = String.format("%s.%s", mc.getModId(), entityName);
153 EntityList.classToStringMapping.put(entityClass, entityModName);
154 EntityList.stringToClassMapping.put(entityModName, entityClass);
155 FMLLog.finest("Automatically registered mod %s entity %s as %s", mc.getModId(), entityName, entityModName);
156 }
157 else
158 {
159 FMLLog.fine("Skipping automatic mod %s entity registration for already registered class %s", mc.getModId(), entityClass.getName());
160 }
161 }
162 catch (IllegalArgumentException e)
163 {
164 FMLLog.log(Level.WARNING, e, "The mod %s tried to register the entity (name,class) (%s,%s) one or both of which are already registered", mc.getModId(), entityName, entityClass.getName());
165 return;
166 }
167 entityRegistrations.put(mc, er);
168 }
169
170 public static void registerGlobalEntityID(Class <? extends Entity > entityClass, String entityName, int id)
171 {
172 if (EntityList.classToStringMapping.containsKey(entityClass))
173 {
174 ModContainer activeModContainer = Loader.instance().activeModContainer();
175 String modId = "unknown";
176 if (activeModContainer != null)
177 {
178 modId = activeModContainer.getModId();
179 }
180 else
181 {
182 FMLLog.severe("There is a rogue mod failing to register entities from outside the context of mod loading. This is incredibly dangerous and should be stopped.");
183 }
184 FMLLog.warning("The mod %s tried to register the entity class %s which was already registered - if you wish to override default naming for FML mod entities, register it here first", modId, entityClass);
185 return;
186 }
187 id = instance().validateAndClaimId(id);
188 EntityList.addMapping(entityClass, entityName, id);
189 }
190
191 private int validateAndClaimId(int id)
192 {
193 // workaround for broken ML
194 int realId = id;
195 if (id < Byte.MIN_VALUE)
196 {
197 FMLLog.warning("Compensating for modloader out of range compensation by mod : entityId %d for mod %s is now %d", id, Loader.instance().activeModContainer().getModId(), realId);
198 realId += 3000;
199 }
200
201 if (realId < 0)
202 {
203 realId += Byte.MAX_VALUE;
204 }
205 try
206 {
207 UnsignedBytes.checkedCast(realId);
208 }
209 catch (IllegalArgumentException e)
210 {
211 FMLLog.log(Level.SEVERE, "The entity ID %d for mod %s is not an unsigned byte and may not work", id, Loader.instance().activeModContainer().getModId());
212 }
213
214 if (!availableIndicies.get(realId))
215 {
216 FMLLog.severe("The mod %s has attempted to register an entity ID %d which is already reserved. This could cause severe problems", Loader.instance().activeModContainer().getModId(), id);
217 }
218 availableIndicies.clear(realId);
219 return realId;
220 }
221
222 public static void registerGlobalEntityID(Class <? extends Entity > entityClass, String entityName, int id, int backgroundEggColour, int foregroundEggColour)
223 {
224 if (EntityList.classToStringMapping.containsKey(entityClass))
225 {
226 ModContainer activeModContainer = Loader.instance().activeModContainer();
227 String modId = "unknown";
228 if (activeModContainer != null)
229 {
230 modId = activeModContainer.getModId();
231 }
232 else
233 {
234 FMLLog.severe("There is a rogue mod failing to register entities from outside the context of mod loading. This is incredibly dangerous and should be stopped.");
235 }
236 FMLLog.warning("The mod %s tried to register the entity class %s which was already registered - if you wish to override default naming for FML mod entities, register it here first", modId, entityClass);
237 return;
238 }
239 instance().validateAndClaimId(id);
240 EntityList.addMapping(entityClass, entityName, id, backgroundEggColour, foregroundEggColour);
241 }
242
243 public static void addSpawn(Class <? extends EntityLiving > entityClass, int weightedProb, int min, int max, EnumCreatureType typeOfCreature, BiomeGenBase... biomes)
244 {
245 for (BiomeGenBase biome : biomes)
246 {
247 @SuppressWarnings("unchecked")
248 List<SpawnListEntry> spawns = biome.getSpawnableList(typeOfCreature);
249
250 for (SpawnListEntry entry : spawns)
251 {
252 //Adjusting an existing spawn entry
253 if (entry.entityClass == entityClass)
254 {
255 entry.itemWeight = weightedProb;
256 entry.minGroupCount = min;
257 entry.maxGroupCount = max;
258 break;
259 }
260 }
261
262 spawns.add(new SpawnListEntry(entityClass, weightedProb, min, max));
263 }
264 }
265
266 public static void addSpawn(String entityName, int weightedProb, int min, int max, EnumCreatureType spawnList, BiomeGenBase... biomes)
267 {
268 Class <? extends Entity > entityClazz = (Class<? extends Entity>) EntityList.stringToClassMapping.get(entityName);
269
270 if (EntityLiving.class.isAssignableFrom(entityClazz))
271 {
272 addSpawn((Class <? extends EntityLiving >) entityClazz, weightedProb, min, max, spawnList, biomes);
273 }
274 }
275
276 public static void removeSpawn(Class <? extends EntityLiving > entityClass, EnumCreatureType typeOfCreature, BiomeGenBase... biomes)
277 {
278 for (BiomeGenBase biome : biomes)
279 {
280 @SuppressWarnings("unchecked")
281 Iterator<SpawnListEntry> spawns = biome.getSpawnableList(typeOfCreature).iterator();
282
283 while (spawns.hasNext())
284 {
285 SpawnListEntry entry = spawns.next();
286 if (entry.entityClass == entityClass)
287 {
288 spawns.remove();
289 }
290 }
291 }
292 }
293
294 public static void removeSpawn(String entityName, EnumCreatureType spawnList, BiomeGenBase... biomes)
295 {
296 Class <? extends Entity > entityClazz = (Class<? extends Entity>) EntityList.stringToClassMapping.get(entityName);
297
298 if (EntityLiving.class.isAssignableFrom(entityClazz))
299 {
300 removeSpawn((Class <? extends EntityLiving >) entityClazz, spawnList, biomes);
301 }
302 }
303
304 public static int findGlobalUniqueEntityId()
305 {
306 int res = instance().availableIndicies.nextSetBit(0);
307 if (res < 0)
308 {
309 throw new RuntimeException("No more entity indicies left");
310 }
311 return res;
312 }
313
314 public EntityRegistration lookupModSpawn(Class<? extends Entity> clazz, boolean keepLooking)
315 {
316 Class<?> localClazz = clazz;
317
318 do
319 {
320 EntityRegistration er = entityClassRegistrations.get(localClazz);
321 if (er != null)
322 {
323 return er;
324 }
325 localClazz = localClazz.getSuperclass();
326 keepLooking = (!Object.class.equals(localClazz));
327 }
328 while (keepLooking);
329
330 return null;
331 }
332
333 public EntityRegistration lookupModSpawn(ModContainer mc, int modEntityId)
334 {
335 for (EntityRegistration er : entityRegistrations.get(mc))
336 {
337 if (er.getModEntityId() == modEntityId)
338 {
339 return er;
340 }
341 }
342 return null;
343 }
344
345 public boolean tryTrackingEntity(EntityTracker entityTracker, Entity entity)
346 {
347
348 EntityRegistration er = lookupModSpawn(entity.getClass(), true);
349 if (er != null)
350 {
351 entityTracker.addEntityToTracker(entity, er.getTrackingRange(), er.getUpdateFrequency(), er.sendsVelocityUpdates());
352 return true;
353 }
354 return false;
355 }
356
357 /**
358 *
359 * DO NOT USE THIS METHOD
360 *
361 * @param entityClass
362 * @param entityTypeId
363 * @param updateRange
364 * @param updateInterval
365 * @param sendVelocityInfo
366 */
367 @Deprecated
368 public static EntityRegistration registerModLoaderEntity(Object mod, Class<? extends Entity> entityClass, int entityTypeId, int updateRange, int updateInterval,
369 boolean sendVelocityInfo)
370 {
371 String entityName = (String) EntityList.classToStringMapping.get(entityClass);
372 if (entityName == null)
373 {
374 throw new IllegalArgumentException(String.format("The ModLoader mod %s has tried to register an entity tracker for a non-existent entity type %s", Loader.instance().activeModContainer().getModId(), entityClass.getCanonicalName()));
375 }
376 instance().doModEntityRegistration(entityClass, entityName, entityTypeId, mod, updateRange, updateInterval, sendVelocityInfo);
377 return instance().entityClassRegistrations.get(entityClass);
378 }
379
380 }