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}