    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}