    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    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    }