    001package cpw.mods.fml.common.registry;
    002
    003import java.util.BitSet;
    004import java.util.Iterator;
    005import java.util.List;
    006import java.util.Map;
    007import java.util.concurrent.Callable;
    008import java.util.logging.Level;
    009
    010import net.minecraft.entity.Entity;
    011import net.minecraft.entity.EntityList;
    012import net.minecraft.entity.EntityLiving;
    013import net.minecraft.entity.EntityTracker;
    014import net.minecraft.entity.EnumCreatureType;
    015import net.minecraft.world.biome.BiomeGenBase;
    016import net.minecraft.world.biome.SpawnListEntry;
    017
    018import com.google.common.base.Function;
    019import com.google.common.collect.ArrayListMultimap;
    020import com.google.common.collect.BiMap;
    021import com.google.common.collect.HashBiMap;
    022import com.google.common.collect.ListMultimap;
    023import com.google.common.collect.Maps;
    024import com.google.common.primitives.UnsignedBytes;
    025import com.google.common.primitives.UnsignedInteger;
    026
    027import cpw.mods.fml.common.FMLCommonHandler;
    028import cpw.mods.fml.common.FMLLog;
    029import cpw.mods.fml.common.Loader;
    030import cpw.mods.fml.common.ModContainer;
    031import cpw.mods.fml.common.network.EntitySpawnPacket;
    032import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
    033
    034public 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}