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.entity.Entity;
011    import net.minecraft.entity.EntityList;
012    import net.minecraft.entity.EntityLiving;
013    import net.minecraft.entity.EntityTracker;
014    import net.minecraft.entity.EnumCreatureType;
015    import net.minecraft.world.biome.BiomeGenBase;
016    import net.minecraft.world.biome.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    }