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.src.BiomeGenBase; 011 import net.minecraft.src.Entity; 012 import net.minecraft.src.EntityList; 013 import net.minecraft.src.EntityLiving; 014 import net.minecraft.src.EntityTracker; 015 import net.minecraft.src.EnumCreatureType; 016 import net.minecraft.src.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 025 import cpw.mods.fml.common.FMLCommonHandler; 026 import cpw.mods.fml.common.FMLLog; 027 import cpw.mods.fml.common.Loader; 028 import cpw.mods.fml.common.ModContainer; 029 import cpw.mods.fml.common.network.EntitySpawnPacket; 030 import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; 031 032 public class EntityRegistry 033 { 034 public class EntityRegistration 035 { 036 private Class<? extends Entity> entityClass; 037 private ModContainer container; 038 private String entityName; 039 private int modId; 040 private int trackingRange; 041 private int updateFrequency; 042 private boolean sendsVelocityUpdates; 043 private Function<EntitySpawnPacket, Entity> customSpawnCallback; 044 private boolean usesVanillaSpawning; 045 public EntityRegistration(ModContainer mc, Class<? extends Entity> entityClass, String entityName, int id, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates) 046 { 047 this.container = mc; 048 this.entityClass = entityClass; 049 this.entityName = entityName; 050 this.modId = id; 051 this.trackingRange = trackingRange; 052 this.updateFrequency = updateFrequency; 053 this.sendsVelocityUpdates = sendsVelocityUpdates; 054 } 055 public Class<? extends Entity> getEntityClass() 056 { 057 return entityClass; 058 } 059 public ModContainer getContainer() 060 { 061 return container; 062 } 063 public String getEntityName() 064 { 065 return entityName; 066 } 067 public int getModEntityId() 068 { 069 return modId; 070 } 071 public int getTrackingRange() 072 { 073 return trackingRange; 074 } 075 public int getUpdateFrequency() 076 { 077 return updateFrequency; 078 } 079 public boolean sendsVelocityUpdates() 080 { 081 return sendsVelocityUpdates; 082 } 083 084 public boolean usesVanillaSpawning() 085 { 086 return usesVanillaSpawning; 087 } 088 public boolean hasCustomSpawning() 089 { 090 return customSpawnCallback != null; 091 } 092 public Entity doCustomSpawning(EntitySpawnPacket packet) throws Exception 093 { 094 return customSpawnCallback.apply(packet); 095 } 096 public void setCustomSpawning(Function<EntitySpawnPacket, Entity> callable, boolean usesVanillaSpawning) 097 { 098 this.customSpawnCallback = callable; 099 this.usesVanillaSpawning = usesVanillaSpawning; 100 } 101 } 102 103 private static final EntityRegistry INSTANCE = new EntityRegistry(); 104 105 private BitSet availableIndicies; 106 private ListMultimap<ModContainer, EntityRegistration> entityRegistrations = ArrayListMultimap.create(); 107 private Map<String,ModContainer> entityNames = Maps.newHashMap(); 108 private BiMap<Class<? extends Entity>, EntityRegistration> entityClassRegistrations = HashBiMap.create(); 109 public static EntityRegistry instance() 110 { 111 return INSTANCE; 112 } 113 114 private EntityRegistry() 115 { 116 availableIndicies = new BitSet(256); 117 availableIndicies.set(1,255); 118 for (Object id : EntityList.IDtoClassMapping.keySet()) 119 { 120 availableIndicies.clear((Integer)id); 121 } 122 } 123 124 /** 125 * Register the mod entity type with FML 126 127 * @param entityClass The entity class 128 * @param entityName A unique name for the entity 129 * @param id A mod specific ID for the entity 130 * @param mod The mod 131 * @param trackingRange The range at which MC will send tracking updates 132 * @param updateFrequency The frequency of tracking updates 133 * @param sendsVelocityUpdates Whether to send velocity information packets as well 134 */ 135 public static void registerModEntity(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates) 136 { 137 instance().doModEntityRegistration(entityClass, entityName, id, mod, trackingRange, updateFrequency, sendsVelocityUpdates); 138 } 139 140 private void doModEntityRegistration(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates) 141 { 142 ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod); 143 EntityRegistration er = new EntityRegistration(mc, entityClass, entityName, id, trackingRange, updateFrequency, sendsVelocityUpdates); 144 try 145 { 146 entityClassRegistrations.put(entityClass, er); 147 entityNames.put(entityName, mc); 148 if (!EntityList.classToStringMapping.containsKey(entityClass)) 149 { 150 String entityModName = String.format("%s.%s", mc.getModId(), entityName); 151 EntityList.classToStringMapping.put(entityClass, entityModName); 152 EntityList.stringToClassMapping.put(entityModName, entityClass); 153 FMLLog.finest("Automatically registered mod %s entity %s as %s", mc.getModId(), entityName, entityModName); 154 } 155 else 156 { 157 FMLLog.fine("Skipping automatic mod %s entity registration for already registered class %s", mc.getModId(), entityClass.getName()); 158 } 159 } 160 catch (IllegalArgumentException e) 161 { 162 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()); 163 return; 164 } 165 entityRegistrations.put(mc, er); 166 } 167 168 public static void registerGlobalEntityID(Class <? extends Entity > entityClass, String entityName, int id) 169 { 170 if (EntityList.classToStringMapping.containsKey(entityClass)) 171 { 172 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", Loader.instance().activeModContainer().getModId(), entityClass); 173 return; 174 } 175 id = instance().validateAndClaimId(id); 176 EntityList.addMapping(entityClass, entityName, id); 177 } 178 179 private int validateAndClaimId(int id) 180 { 181 // workaround for broken ML 182 if (id < 0) 183 { 184 id += 3000; 185 } 186 if (!availableIndicies.get(id)) 187 { 188 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); 189 } 190 availableIndicies.clear(id); 191 return id; 192 } 193 194 public static void registerGlobalEntityID(Class <? extends Entity > entityClass, String entityName, int id, int backgroundEggColour, int foregroundEggColour) 195 { 196 instance().validateAndClaimId(id); 197 EntityList.addMapping(entityClass, entityName, id, backgroundEggColour, foregroundEggColour); 198 } 199 200 public static void addSpawn(Class <? extends EntityLiving > entityClass, int weightedProb, int min, int max, EnumCreatureType typeOfCreature, BiomeGenBase... biomes) 201 { 202 for (BiomeGenBase biome : biomes) 203 { 204 @SuppressWarnings("unchecked") 205 List<SpawnListEntry> spawns = biome.getSpawnableList(typeOfCreature); 206 207 for (SpawnListEntry entry : spawns) 208 { 209 //Adjusting an existing spawn entry 210 if (entry.entityClass == entityClass) 211 { 212 entry.itemWeight = weightedProb; 213 entry.minGroupCount = min; 214 entry.maxGroupCount = max; 215 break; 216 } 217 } 218 219 spawns.add(new SpawnListEntry(entityClass, weightedProb, min, max)); 220 } 221 } 222 223 public static void addSpawn(String entityName, int weightedProb, int min, int max, EnumCreatureType spawnList, BiomeGenBase... biomes) 224 { 225 Class <? extends Entity > entityClazz = (Class<? extends Entity>) EntityList.stringToClassMapping.get(entityName); 226 227 if (EntityLiving.class.isAssignableFrom(entityClazz)) 228 { 229 addSpawn((Class <? extends EntityLiving >) entityClazz, weightedProb, min, max, spawnList, biomes); 230 } 231 } 232 233 public static void removeSpawn(Class <? extends EntityLiving > entityClass, EnumCreatureType typeOfCreature, BiomeGenBase... biomes) 234 { 235 for (BiomeGenBase biome : biomes) 236 { 237 @SuppressWarnings("unchecked") 238 Iterator<SpawnListEntry> spawns = biome.getSpawnableList(typeOfCreature).iterator(); 239 240 while (spawns.hasNext()) 241 { 242 SpawnListEntry entry = spawns.next(); 243 if (entry.entityClass == entityClass) 244 { 245 spawns.remove(); 246 } 247 } 248 } 249 } 250 251 public static void removeSpawn(String entityName, EnumCreatureType spawnList, BiomeGenBase... biomes) 252 { 253 Class <? extends Entity > entityClazz = (Class<? extends Entity>) EntityList.stringToClassMapping.get(entityName); 254 255 if (EntityLiving.class.isAssignableFrom(entityClazz)) 256 { 257 removeSpawn((Class <? extends EntityLiving >) entityClazz, spawnList, biomes); 258 } 259 } 260 261 public static int findGlobalUniqueEntityId() 262 { 263 int res = instance().availableIndicies.nextSetBit(0); 264 if (res < 0) 265 { 266 throw new RuntimeException("No more entity indicies left"); 267 } 268 return res; 269 } 270 271 public EntityRegistration lookupModSpawn(Class<? extends Entity> clazz, boolean keepLooking) 272 { 273 Class<?> localClazz = clazz; 274 275 do 276 { 277 EntityRegistration er = entityClassRegistrations.get(localClazz); 278 if (er != null) 279 { 280 return er; 281 } 282 localClazz = localClazz.getSuperclass(); 283 keepLooking = (!Object.class.equals(localClazz)); 284 } 285 while (keepLooking); 286 287 return null; 288 } 289 290 public EntityRegistration lookupModSpawn(ModContainer mc, int modEntityId) 291 { 292 for (EntityRegistration er : entityRegistrations.get(mc)) 293 { 294 if (er.getModEntityId() == modEntityId) 295 { 296 return er; 297 } 298 } 299 return null; 300 } 301 302 public boolean tryTrackingEntity(EntityTracker entityTracker, Entity entity) 303 { 304 305 EntityRegistration er = lookupModSpawn(entity.getClass(), true); 306 if (er != null) 307 { 308 entityTracker.addEntityToTracker(entity, er.getTrackingRange(), er.getUpdateFrequency(), er.sendsVelocityUpdates()); 309 return true; 310 } 311 return false; 312 } 313 314 /** 315 * 316 * DO NOT USE THIS METHOD 317 * 318 * @param entityClass 319 * @param entityTypeId 320 * @param updateRange 321 * @param updateInterval 322 * @param sendVelocityInfo 323 * @return 324 */ 325 @Deprecated 326 public static EntityRegistration registerModLoaderEntity(Object mod, Class<? extends Entity> entityClass, int entityTypeId, int updateRange, int updateInterval, 327 boolean sendVelocityInfo) 328 { 329 String entityName = (String) EntityList.classToStringMapping.get(entityClass); 330 if (entityName == null) 331 { 332 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())); 333 } 334 instance().doModEntityRegistration(entityClass, entityName, entityTypeId, mod, updateRange, updateInterval, sendVelocityInfo); 335 return instance().entityClassRegistrations.get(entityClass); 336 } 337 338 }