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}