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 }