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