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    
025    import cpw.mods.fml.common.FMLCommonHandler;
026    import cpw.mods.fml.common.FMLLog;
027    import cpw.mods.fml.common.Loader;
028    import cpw.mods.fml.common.ModContainer;
029    import cpw.mods.fml.common.network.EntitySpawnPacket;
030    import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
031    
032    public class EntityRegistry
033    {
034        public class EntityRegistration
035        {
036            private Class<? extends Entity> entityClass;
037            private ModContainer container;
038            private String entityName;
039            private int modId;
040            private int trackingRange;
041            private int updateFrequency;
042            private boolean sendsVelocityUpdates;
043            private Function<EntitySpawnPacket, Entity> customSpawnCallback;
044            private boolean usesVanillaSpawning;
045            public EntityRegistration(ModContainer mc, Class<? extends Entity> entityClass, String entityName, int id, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates)
046            {
047                this.container = mc;
048                this.entityClass = entityClass;
049                this.entityName = entityName;
050                this.modId = id;
051                this.trackingRange = trackingRange;
052                this.updateFrequency = updateFrequency;
053                this.sendsVelocityUpdates = sendsVelocityUpdates;
054            }
055            public Class<? extends Entity> getEntityClass()
056            {
057                return entityClass;
058            }
059            public ModContainer getContainer()
060            {
061                return container;
062            }
063            public String getEntityName()
064            {
065                return entityName;
066            }
067            public int getModEntityId()
068            {
069                return modId;
070            }
071            public int getTrackingRange()
072            {
073                return trackingRange;
074            }
075            public int getUpdateFrequency()
076            {
077                return updateFrequency;
078            }
079            public boolean sendsVelocityUpdates()
080            {
081                return sendsVelocityUpdates;
082            }
083    
084            public boolean usesVanillaSpawning()
085            {
086                return usesVanillaSpawning;
087            }
088            public boolean hasCustomSpawning()
089            {
090                return customSpawnCallback != null;
091            }
092            public Entity doCustomSpawning(EntitySpawnPacket packet) throws Exception
093            {
094                return customSpawnCallback.apply(packet);
095            }
096            public void setCustomSpawning(Function<EntitySpawnPacket, Entity> callable, boolean usesVanillaSpawning)
097            {
098                this.customSpawnCallback = callable;
099                this.usesVanillaSpawning = usesVanillaSpawning;
100            }
101        }
102    
103        private static final EntityRegistry INSTANCE = new EntityRegistry();
104    
105        private BitSet availableIndicies;
106        private ListMultimap<ModContainer, EntityRegistration> entityRegistrations = ArrayListMultimap.create();
107        private Map<String,ModContainer> entityNames = Maps.newHashMap();
108        private BiMap<Class<? extends Entity>, EntityRegistration> entityClassRegistrations = HashBiMap.create();
109        public static EntityRegistry instance()
110        {
111            return INSTANCE;
112        }
113    
114        private EntityRegistry()
115        {
116            availableIndicies = new BitSet(256);
117            availableIndicies.set(1,255);
118            for (Object id : EntityList.IDtoClassMapping.keySet())
119            {
120                availableIndicies.clear((Integer)id);
121            }
122        }
123    
124        /**
125         * Register the mod entity type with FML
126    
127         * @param entityClass The entity class
128         * @param entityName A unique name for the entity
129         * @param id A mod specific ID for the entity
130         * @param mod The mod
131         * @param trackingRange The range at which MC will send tracking updates
132         * @param updateFrequency The frequency of tracking updates
133         * @param sendsVelocityUpdates Whether to send velocity information packets as well
134         */
135        public static void registerModEntity(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates)
136        {
137            instance().doModEntityRegistration(entityClass, entityName, id, mod, trackingRange, updateFrequency, sendsVelocityUpdates);
138        }
139    
140        private void doModEntityRegistration(Class<? extends Entity> entityClass, String entityName, int id, Object mod, int trackingRange, int updateFrequency, boolean sendsVelocityUpdates)
141        {
142            ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod);
143            EntityRegistration er = new EntityRegistration(mc, entityClass, entityName, id, trackingRange, updateFrequency, sendsVelocityUpdates);
144            try
145            {
146                entityClassRegistrations.put(entityClass, er);
147                entityNames.put(entityName, mc);
148                if (!EntityList.classToStringMapping.containsKey(entityClass))
149                {
150                    String entityModName = String.format("%s.%s", mc.getModId(), entityName);
151                    EntityList.classToStringMapping.put(entityClass, entityModName);
152                    EntityList.stringToClassMapping.put(entityModName, entityClass);
153                    FMLLog.finest("Automatically registered mod %s entity %s as %s", mc.getModId(), entityName, entityModName);
154                }
155                else
156                {
157                    FMLLog.fine("Skipping automatic mod %s entity registration for already registered class %s", mc.getModId(), entityClass.getName());
158                }
159            }
160            catch (IllegalArgumentException e)
161            {
162                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());
163                return;
164            }
165            entityRegistrations.put(mc, er);
166        }
167    
168        public static void registerGlobalEntityID(Class <? extends Entity > entityClass, String entityName, int id)
169        {
170            if (EntityList.classToStringMapping.containsKey(entityClass))
171            {
172                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", Loader.instance().activeModContainer().getModId(), entityClass);
173                return;
174            }
175            id = instance().validateAndClaimId(id);
176            EntityList.addMapping(entityClass, entityName, id);
177        }
178    
179        private int validateAndClaimId(int id)
180        {
181            // workaround for broken ML
182            if (id < 0)
183            {
184                id += 3000;
185            }
186            if (!availableIndicies.get(id))
187            {
188                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);
189            }
190            availableIndicies.clear(id);
191            return id;
192        }
193    
194        public static void registerGlobalEntityID(Class <? extends Entity > entityClass, String entityName, int id, int backgroundEggColour, int foregroundEggColour)
195        {
196            instance().validateAndClaimId(id);
197            EntityList.addMapping(entityClass, entityName, id, backgroundEggColour, foregroundEggColour);
198        }
199    
200        public static void addSpawn(Class <? extends EntityLiving > entityClass, int weightedProb, int min, int max, EnumCreatureType typeOfCreature, BiomeGenBase... biomes)
201        {
202            for (BiomeGenBase biome : biomes)
203            {
204                @SuppressWarnings("unchecked")
205                List<SpawnListEntry> spawns = biome.getSpawnableList(typeOfCreature);
206    
207                for (SpawnListEntry entry : spawns)
208                {
209                    //Adjusting an existing spawn entry
210                    if (entry.entityClass == entityClass)
211                    {
212                        entry.itemWeight = weightedProb;
213                        entry.minGroupCount = min;
214                        entry.maxGroupCount = max;
215                        break;
216                    }
217                }
218    
219                spawns.add(new SpawnListEntry(entityClass, weightedProb, min, max));
220            }
221        }
222    
223        public static void addSpawn(String entityName, int weightedProb, int min, int max, EnumCreatureType spawnList, BiomeGenBase... biomes)
224        {
225            Class <? extends Entity > entityClazz = (Class<? extends Entity>) EntityList.stringToClassMapping.get(entityName);
226    
227            if (EntityLiving.class.isAssignableFrom(entityClazz))
228            {
229                addSpawn((Class <? extends EntityLiving >) entityClazz, weightedProb, min, max, spawnList, biomes);
230            }
231        }
232    
233        public static void removeSpawn(Class <? extends EntityLiving > entityClass, EnumCreatureType typeOfCreature, BiomeGenBase... biomes)
234        {
235            for (BiomeGenBase biome : biomes)
236            {
237                @SuppressWarnings("unchecked")
238                Iterator<SpawnListEntry> spawns = biome.getSpawnableList(typeOfCreature).iterator();
239    
240                while (spawns.hasNext())
241                {
242                    SpawnListEntry entry = spawns.next();
243                    if (entry.entityClass == entityClass)
244                    {
245                        spawns.remove();
246                    }
247                }
248            }
249        }
250    
251        public static void removeSpawn(String entityName, EnumCreatureType spawnList, BiomeGenBase... biomes)
252        {
253            Class <? extends Entity > entityClazz = (Class<? extends Entity>) EntityList.stringToClassMapping.get(entityName);
254    
255            if (EntityLiving.class.isAssignableFrom(entityClazz))
256            {
257                removeSpawn((Class <? extends EntityLiving >) entityClazz, spawnList, biomes);
258            }
259        }
260    
261        public static int findGlobalUniqueEntityId()
262        {
263            int res = instance().availableIndicies.nextSetBit(0);
264            if (res < 0)
265            {
266                throw new RuntimeException("No more entity indicies left");
267            }
268            return res;
269        }
270    
271        public EntityRegistration lookupModSpawn(Class<? extends Entity> clazz, boolean keepLooking)
272        {
273            Class<?> localClazz = clazz;
274    
275            do
276            {
277                EntityRegistration er = entityClassRegistrations.get(localClazz);
278                if (er != null)
279                {
280                    return er;
281                }
282                localClazz = localClazz.getSuperclass();
283                keepLooking = (!Object.class.equals(localClazz));
284            }
285            while (keepLooking);
286    
287            return null;
288        }
289    
290        public EntityRegistration lookupModSpawn(ModContainer mc, int modEntityId)
291        {
292            for (EntityRegistration er : entityRegistrations.get(mc))
293            {
294                if (er.getModEntityId() == modEntityId)
295                {
296                    return er;
297                }
298            }
299            return null;
300        }
301    
302        public boolean tryTrackingEntity(EntityTracker entityTracker, Entity entity)
303        {
304    
305            EntityRegistration er = lookupModSpawn(entity.getClass(), true);
306            if (er != null)
307            {
308                entityTracker.addEntityToTracker(entity, er.getTrackingRange(), er.getUpdateFrequency(), er.sendsVelocityUpdates());
309                return true;
310            }
311            return false;
312        }
313    
314        /**
315         *
316         * DO NOT USE THIS METHOD
317         *
318         * @param entityClass
319         * @param entityTypeId
320         * @param updateRange
321         * @param updateInterval
322         * @param sendVelocityInfo
323         * @return
324         */
325        @Deprecated
326        public static EntityRegistration registerModLoaderEntity(Object mod, Class<? extends Entity> entityClass, int entityTypeId, int updateRange, int updateInterval,
327                boolean sendVelocityInfo)
328        {
329            String entityName = (String) EntityList.classToStringMapping.get(entityClass);
330            if (entityName == null)
331            {
332                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()));
333            }
334            instance().doModEntityRegistration(entityClass, entityName, entityTypeId, mod, updateRange, updateInterval, sendVelocityInfo);
335            return instance().entityClassRegistrations.get(entityClass);
336        }
337    
338    }