1.16.5 Mob stacking

Discussion in 'Spigot Plugin Development' started by Jorngamernl, Jul 12, 2021.

  1. I want to make a mob stacking plugin.

    what is the best way to do that?
     
  2. Check in EntitySpawnEvent where an entity is spawning, make it as "main" entity, then, of there are spawning more entities like main, add to its custom name. Everytime an entity is killed, check if has custom name, reduce by 1 every kill
     
    • Funny Funny x 1
  3. How can I ensure that there is only 1 main entity of a certain type?
     
  4. Instead of using the entity's custom name, I would store the count in the main entity's persistent data container. This would also keep the stack size after the plugin instance stops. You can get the stack size from the mob and then add when a mob spawns around the main entity. When the main mob is killed, get the stack size, subtract it by one, then add it to a new mob that you spawn unless of course the stack size is now 0.

    Whenever a mob spawns, check around it for any mobs of its type. If there are, they should already be a 'main' entity and so you'd then just cancel the mob event of the mob that just spawned and then add 1 to the main entity's persistent data stack size, otherwise make that mob that just spawned the main entity by setting persistent data with a stack size of 1.
     
  5. Can you give me some code because I don't get it?
     
  6. My code now:
    Code (Java):
    @EventHandler
        public void OnEntitySpawn(EntitySpawnEvent e) {

            Entity entity = e.getEntity();
            EntityType etype = e.getEntityType();
            Location loc = entity.getLocation();
            PersistentDataContainer data = entity.getPersistentDataContainer();
            List<Entity> entities = getEntitiesAroundPoint(loc, 10);

            data.set(new NamespacedKey(SpawnersMain.getMain(), "type"), PersistentDataType.STRING, etype.toString());
            data.set(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER, 1);

            for(int i = 0; i < entities.size(); i++){
                Entity en = entities.get(i);
                PersistentDataContainer enData = en.getPersistentDataContainer();

                String enType = enData.get(new NamespacedKey(SpawnersMain.getMain(), "type"), PersistentDataType.STRING);
                String type = data.get(new NamespacedKey(SpawnersMain.getMain(), "type"), PersistentDataType.STRING);

                if(enType.equalsIgnoreCase(type)){
                    int enAmount = enData.get(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER);
                    int amount = data.get(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER);
                    int newAmount = enAmount + amount;

                    data.set(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER, newAmount);
                    e.setCancelled(true);

                }

            }

        }

        public static List<Entity> getEntitiesAroundPoint(Location location, double radius) {
            List<Entity> entities = new ArrayList<Entity>();
            World world = location.getWorld();

            // To find chunks we use chunk coordinates (not block coordinates!)
            int smallX = MathHelper.floor((location.getX() - radius) / 16.0D);
            int bigX = MathHelper.floor((location.getX() + radius) / 16.0D);
            int smallZ = MathHelper.floor((location.getZ() - radius) / 16.0D);
            int bigZ = MathHelper.floor((location.getZ() + radius) / 16.0D);

            for (int x = smallX; x <= bigX; x++) {
                for (int z = smallZ; z <= bigZ; z++) {
                    if (world.isChunkLoaded(x, z)) {
                        entities.addAll(Arrays.asList(world.getChunkAt(x, z).getEntities())); // Add all entities from this chunk to the list
                    }
                }
            }

            // Remove the entities that are within the box above but not actually in the sphere we defined with the radius and location
            // This code below could probably be replaced in Java 8 with a stream -> filter
            Iterator<Entity> entityIterator = entities.iterator(); // Create an iterator so we can loop through the list while removing entries
            while (entityIterator.hasNext()) {
                if (entityIterator.next().getLocation().distanceSquared(location) > radius * radius) { // If the entity is outside of the sphere...
                    entityIterator.remove(); // Remove it
                }
            }
            return entities;
        }

    But that doesn't work. What am I doing wrong?
     
  7. you could just do this or use our way both work, well this works 100% XD
    Code (Java):
    entity.getNearbyEntities(radius, radius, radius);
    //or
    world.getNearbyEntities(loc, radius, radius, radius)
     
  8. Code (Java):
    @EventHandler
        public void OnEntitySpawn(EntitySpawnEvent e) {

            Entity entity = e.getEntity();
            EntityType etype = e.getEntityType();
            Location loc = entity.getLocation();
            PersistentDataContainer data = entity.getPersistentDataContainer();
            List<Entity> entities = entity.getNearbyEntities(10, 10, 10);

            data.set(new NamespacedKey(SpawnersMain.getMain(), "type"), PersistentDataType.STRING, etype.toString());
            data.set(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER, 1);

            for(int i = 0; i < entities.size(); i++){
                Entity en = entities.get(i);
                PersistentDataContainer enData = en.getPersistentDataContainer();

                String enType = enData.get(new NamespacedKey(SpawnersMain.getMain(), "type"), PersistentDataType.STRING);
                String type = data.get(new NamespacedKey(SpawnersMain.getMain(), "type"), PersistentDataType.STRING);

                if(enType.equalsIgnoreCase(type)){
                    int enAmount = enData.get(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER);
                    int amount = data.get(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER);
                    int newAmount = enAmount + amount;

                    data.set(new NamespacedKey(SpawnersMain.getMain(), "amount"), PersistentDataType.INTEGER, newAmount);
                    e.setCancelled(true);

                }

            }

        }

    Changed the code to this but the mobs still don't stack
     
  9. I'll send some code because you certainly do know what you're doing. So you'll be able to learn from provided code hence no spoon-feeding :)

    Here you go :D
    Code (Java):
    import org.bukkit.NamespacedKey;
    import org.bukkit.entity.Entity;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.entity.EntitySpawnEvent;
    import org.bukkit.persistence.PersistentDataContainer;
    import org.bukkit.persistence.PersistentDataType;

    public class EntitySpawn implements Listener {

        private final Main plugin;

        public EntitySpawn(Main p) {
            plugin = p;
        }

        @EventHandler
        private void event(EntitySpawnEvent e) {
            Entity ent = e.getEntity();
            double yourRadius = 15F;

            for (Entity near : ent.getNearbyEntities(yourRadius, yourRadius, yourRadius)) {
                if (near.getType() != ent.getType()) continue;

                /* Nearby entity is of same type as spawned entity */

                // get container and create key
                PersistentDataContainer container = near.getPersistentDataContainer();
                NamespacedKey key = new NamespacedKey(plugin, "stack-size");

                // get current stack size and increase it
                int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 0) + 1;

                // increase stack size
                container.set(key, PersistentDataType.INTEGER, stackSize);
             
                // update nearby entity name
                near.setCustomName("Count: "+ stackSize);

                e.setCancelled(true);
                break; // break so that only 1 nearby entity stack size increases
            }
         
            /*
             * If there are no nearby entities of the same type,
             * the entity will simply spawn. Other entities will then
             * be able to stack onto this entity if they have the same type.
             */

        }
    }

    Just say if it doesn't work!
     
  10. Thank you, it worked
     
  11. I'm now trying to make that if you kill an entity that one of the stack-size will be taken off.

    Code (Java):
    @EventHandler
        private void OnEntityDamage(EntityDamageEvent e){
            LivingEntity ent = (LivingEntity) e.getEntity();

            if(ent.getHealth() <= 0){

                // get container and create key
                PersistentDataContainer container = ent.getPersistentDataContainer();
                NamespacedKey key = new NamespacedKey(SpawnersMain.getMain(), "stack-size");

                // get current stack size and decrease it
                int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 0) - 1;

                // decrease stack size
                container.set(key, PersistentDataType.INTEGER, stackSize);

                // update entity name
                ent.setCustomName("Count: " + stackSize);

                e.setCancelled(true);
            }
        }

    Why doesn't this work?
     
  12. I'd imagine that your code would somewhat work, but there are a few things you can fix:

    • Because you don't actually let the entity die, there would be no mob loot. I don't know if this it your intention but I'm mentioning the fact nonetheless.
    • There is no check to see if the stack size has reached 0, so the stack size will go into the negatives.
    • You're casting the entity from this event to a LivingEntity. Without a check, it could cause errors if the Entity is not an instanceof a LivingEntity. Make sure that you check if the Entity is an instanceof a EntityLiving, and if it's not, you return. You also don't need to save it as an EntityLiving, but there's nothing stopping you.
    • Once the entity is killed, decreasing the stack size, its health would still be around 0 allowing players to one-shot it every time. My suggestion for this would be to let the entity die, and then spawn a new entity of its type where it was, and set the new stack size persistent data to it.
    • The final thing I would suggest if using EntityDeathEvent instead of EntityDamageEvent, just because it makes your life a little easier. :)

    See if you can come up with something that works better, and I'll still be here if you need any more help! :D
     
    #12 CablePlays, Jul 12, 2021
    Last edited: Jul 12, 2021
  13. Code (Java):
    @EventHandler
        private void OnEntityDeath(EntityDeathEvent e){
            if(e.getEntity() instanceof LivingEntity){
                LivingEntity ent = e.getEntity();

                // get container and create key
                PersistentDataContainer container = ent.getPersistentDataContainer();
                NamespacedKey key = new NamespacedKey(SpawnersMain.getMain(), "stack-size");

                // get current stack size and increase it
                int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 0) - 1;

                if(stackSize != 0){

                    Entity ent1 = ent.getWorld().spawnEntity(ent.getLocation(), ent.getType());

                    // get container and create key
                    PersistentDataContainer container1 = ent.getPersistentDataContainer();
                    NamespacedKey key1 = new NamespacedKey(SpawnersMain.getMain(), "stack-size");

                    // increase stack size
                    container1.set(key1, PersistentDataType.INTEGER, stackSize);

                    // update nearby entity name
                    ent1.setCustomName("Count: "+ stackSize);

                }
            } else {
                return;
            }
        }

    This is my code now. It does not work. If I kill a mob it doesn't spawn a new one. How can i fix that?
     
  14. Add in statements that send messages to chat or the console so that you can debug. It's a highly required skill in any coding, and it's the only way to know what's wrong if there's no errors. Add multiple send message statements so that you can see what fires and what doesn't. Then you can change what needs to be changed. But something I will mention is that when you get the current stack size from the entity, the default value when subtracting should be 1 instead of 0:

    Code (Java):
    int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 0 /* Change from 0 to 1 */) - 1;
    So that if there is no saved stack size (meaning the entity only has a stack size of 1), it gets subtracted to 0 when it's killed, and not -1. This might prevent the if-statement from firing.
    One more thing for efficiency, you've created two saved NamespacedKeys, but they're both the same so you should delete the second one and just use the first key.
     
  15. Code (Java):
    @EventHandler
        private void OnEntityDeath(EntityDeathEvent e){
            if(e.getEntity() instanceof LivingEntity){
                LivingEntity ent = e.getEntity();

                // get container and create key
                PersistentDataContainer container = ent.getPersistentDataContainer();
                NamespacedKey key = new NamespacedKey(SpawnersMain.getMain(), "stack-size");

                // get current stack size and increase it
                int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 1) - 1;

                if(stackSize != 0){

                    SpawnersMain.getMain().getServer().getConsoleSender().sendMessage("1");

                    Entity ent1 = ent.getWorld().spawnEntity(ent.getLocation(), ent.getType());

                    // get container and create key
                    PersistentDataContainer container1 = ent.getPersistentDataContainer();

                    // increase stack size
                    container1.set(key, PersistentDataType.INTEGER, stackSize);

                    // update nearby entity name
                    ent1.setCustomName("Count: "+ stackSize);

                }
            } else {
                return;
            }

    I get a message in the console so it's not because of the if statement
     
  16. Hi! So I created the plugin and it works pretty well! You had the just of it, there were only a few things that prevented it from working properly. I'll post the code incase you want it, or you can try again with what I tell you. Also, I figured it out through the use of debugging, and you need to learn how to do it. You'd be able to solve this problem for yourself. :D

    One of the main problems was that when you killed the mob, it would spawn another mob where the mob you killed just was, but at the time you spawn it, the dead mob is still on the world and so it tries to stack the new mob onto the mob that you just killed. I fixed this by adding a check to see if the mob was dead. You also need to make sure to add the stack size persistent data to any new mobs that don't get stacked when they spawn, or else it will be 0 and when a mob is stacked on the mob, the count will be 1 instead of 2. You could also just change the default in getOrDefault() to 1 from 0.

    Here is the code, but it's in your best interest to try one last time! :)

    Code (Java):

    public class CreatureSpawn implements Listener {

        private final Main plugin;

        public CreatureSpawn(Main p) {
            plugin = p;
        }

        @EventHandler
        private void event(CreatureSpawnEvent e) {
            if (e.getSpawnReason() == CreatureSpawnEvent.SpawnReason.CUSTOM) return;
           
            LivingEntity ent = e.getEntity();
            NamespacedKey key = new NamespacedKey(plugin, "stack-size");

            for (Entity near : ent.getNearbyEntities(15, 15, 15)) {
                if (near.getType() != ent.getType() || near.isDead()) continue; // near is not same type or is dead

                // get container
                PersistentDataContainer container = near.getPersistentDataContainer();

                // update stack size
                int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 0);
                stackSize++;
                container.set(key, PersistentDataType.INTEGER, stackSize);

                // update name
                near.setCustomName(ChatColor.AQUA + "Count: " + ChatColor.GOLD + stackSize);

                // prevent spawn and adding to multiple nearby entities
                e.setCancelled(true);
                return;
            }

            // no nearby entities: Allow spawn, add name and set stack size
            ent.setCustomName(ChatColor.AQUA + "Count: " + ChatColor.GOLD + 1);
            ent.setCustomNameVisible(true);
            ent.getPersistentDataContainer().set(key, PersistentDataType.INTEGER, 1);
        }
    }

    Code (Java):

    public class EntityDeath implements Listener {

        private final Main plugin;

        public EntityDeath(Main p) {
            plugin = p;
        }

        @EventHandler
        private void event(EntityDeathEvent e) {
            if (!(e.getEntity() instanceof LivingEntity)) return;

            Entity ent = e.getEntity();
            PersistentDataContainer container = ent.getPersistentDataContainer();
            NamespacedKey key = new NamespacedKey(plugin, "stack-size");

            int stackSize = container.getOrDefault(key, PersistentDataType.INTEGER, 1);
            stackSize--;

            if (stackSize == 0) return; // no more stack: let entity die without respawn

            // spawn new entity
            Entity newEntity =
                    ent.getLocation().getWorld().spawnEntity(ent.getLocation(), ent.getType());

            // save stack size
            PersistentDataContainer secondContainer = newEntity.getPersistentDataContainer();
            secondContainer.set(key, PersistentDataType.INTEGER, stackSize);

            // set and show name
            newEntity.setCustomName(ChatColor.AQUA + "Count: " + ChatColor.GOLD + stackSize);
            newEntity.setCustomNameVisible(true);
        }
    }

    EDIT: Updated the code because there was a problem with entities being respawned getting stacked on nearby entities, so you'd kill a stacked entity and it would just disappear and add 1 stack size to another entity nearby. Fixed this with setting spawn reason to custom, and returning if the spawn reason is custom.
     
    #16 CablePlays, Jul 13, 2021
    Last edited: Jul 15, 2021
  17. Code (Java):
    Entity newEntity = ent.getLocation().getWorld().spawnEntity(ent.getLocation(), ent.getType(), CreatureSpawnEvent.SpawnReason.CUSTOM);
    My Intellij gives an error:
    Expected 2 arguments but found 3
     
  18. check what arguments the method uses and put those arguments in there,
    ctrl + p for example shows argumens which method accepts, or check docs, middleclick the spawnEntity, multiple ways
    Googled it for u:
    spawnEntity(Location loc, EntityType type)
     
    • Agree Agree x 1