Resource Creating Custom Entitys with PathFinderGoals [1.11]

Discussion in 'Spigot Plugin Development' started by MaveCrit, Apr 28, 2017.

  1. Hello there,
    Out there you can find many tutorials for Custom Entitys which have PathFinderGoals but for me none of them works with 1.11.
    So here you go, viewing the 1.11 guide for NMS entitys.
    I try my best to keep this as simple as possible, please try to understand what you see here! Do not just copy and paste everything cause I'm pretty sure everything will change again in 1.12.

    Lets get started!

    1. Creating a Custom Entity Class (A Zombie in this case)
    Code (Text):
    package de.mavecrit.pawars.entitys;

    import java.lang.reflect.Field;
    import java.util.Set;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;

    import net.minecraft.server.v1_11_R1.EntityHuman;
    import net.minecraft.server.v1_11_R1.EntitySheep;
    import net.minecraft.server.v1_11_R1.EntityZombie;
    import net.minecraft.server.v1_11_R1.GenericAttributes;
    import net.minecraft.server.v1_11_R1.PathfinderGoalFloat;
    import net.minecraft.server.v1_11_R1.PathfinderGoalHurtByTarget;
    import net.minecraft.server.v1_11_R1.PathfinderGoalLookAtPlayer;
    import net.minecraft.server.v1_11_R1.PathfinderGoalMeleeAttack;
    import net.minecraft.server.v1_11_R1.PathfinderGoalMoveThroughVillage;
    import net.minecraft.server.v1_11_R1.PathfinderGoalMoveTowardsRestriction;
    import net.minecraft.server.v1_11_R1.PathfinderGoalNearestAttackableTarget;
    import net.minecraft.server.v1_11_R1.PathfinderGoalRandomLookaround;
    import net.minecraft.server.v1_11_R1.PathfinderGoalRandomStroll;
    import net.minecraft.server.v1_11_R1.PathfinderGoalSelector;
    import net.minecraft.server.v1_11_R1.World;

    public class CustomZombie extends EntityZombie {

        public CustomZombie(World world) {
           super(world);
        }
    }
    Current state: This will create a completly default zombie without any special goals.


    2. Create a privateFieldGetter method (Same class)
    Code (Text):
        public static Object getPrivateField(String fieldName, Class clazz,
                Object object) {
            Field field;
            Object o = null;
            try {
                field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                o = field.get(object);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return o;
        }
    3. Clear the goals of the current Zombie

    Note: Old codes will throw a error here since 1.9 has changed the storing system to "Set"

    Code (Text):
        public CustomZombie(World world) {
           super(world);
         Set  goalB = (Set )getPrivateField("b", PathfinderGoalSelector.class, goalSelector); goalB.clear();
         Set  goalC = (Set )getPrivateField("c", PathfinderGoalSelector.class, goalSelector); goalC.clear();
         Set  targetB = (Set )getPrivateField("b", PathfinderGoalSelector.class, targetSelector); targetB.clear();
         Set  targetC = (Set )getPrivateField("c", PathfinderGoalSelector.class, targetSelector); targetC.clear();
        }
    Current state: The zombie just stands where it spawns, does not do anything.

    4. Add your own goals
    Code (Text):
         this.goalSelector.a(0, new PathfinderGoalFloat(this));
         this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F));
         this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 0.2D));
         this.goalSelector.a(4, new PathfinderGoalMeleeAttack(this, 1.0, true));
         this.goalSelector.a(6, new PathfinderGoalMoveThroughVillage(this, 0.2D, false));
         this.goalSelector.a(7, new PathfinderGoalRandomStroll(this, 0.2D));
         this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
       
         this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, false));
         this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.20000000298023224D);
    Current state: This will spawn a zombie which will look at humans, dont walk around and look around randomly. Of course its able to attack too but we did not setted any Target types

    5. Create the registry
    Code (Text):
    package de.mavecrit.pawars.entitys.nms;
    import com.google.common.collect.BiMap;
    import com.google.common.collect.HashBiMap;
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.util.HashMap;
    import java.util.Map;
    import net.minecraft.server.v1_11_R1.RegistryMaterials;
    import net.minecraft.server.v1_11_R1.Entity;
    import net.minecraft.server.v1_11_R1.EntityTypes;
    import net.minecraft.server.v1_11_R1.MinecraftKey;

    public class CustomEntityRegistry extends RegistryMaterials {

        private static CustomEntityRegistry instance = null;

        private final BiMap<MinecraftKey, Class<? extends Entity>> customEntities = HashBiMap.create();
        private final BiMap<Class<? extends Entity>, MinecraftKey> customEntityClasses = this.customEntities.inverse();
        private final Map<Class<? extends Entity>, Integer> customEntityIds = new HashMap<>();

        private final RegistryMaterials wrapped;

        private CustomEntityRegistry(RegistryMaterials original) {
            this.wrapped = original;
        }

        public static CustomEntityRegistry getInstance() {
            if (instance != null) {
                return instance;
            }

            instance = new CustomEntityRegistry(EntityTypes.b);

            try {
                //TODO: Update name on version change (RegistryMaterials)
                Field registryMaterialsField = EntityTypes.class.getDeclaredField("b");
                registryMaterialsField.setAccessible(true);

                Field modifiersField = Field.class.getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(registryMaterialsField, registryMaterialsField.getModifiers() & ~Modifier.FINAL);

                registryMaterialsField.set(null, instance);
            } catch (Exception e) {
                instance = null;

                throw new RuntimeException("Unable to override the old entity RegistryMaterials", e);
            }

            return instance;
        }

        public static void registerCustomEntity(int entityId, String entityName, Class<? extends Entity> entityClass) {
            getInstance().putCustomEntity(entityId, entityName, entityClass);
        }

        public void putCustomEntity(int entityId, String entityName, Class<? extends Entity> entityClass) {
            MinecraftKey minecraftKey = new MinecraftKey(entityName);

            this.customEntities.put(minecraftKey, entityClass);
            this.customEntityIds.put(entityClass, entityId);
        }

        @Override
        public Class<? extends Entity> get(Object key) {
            if (this.customEntities.containsKey(key)) {
                return this.customEntities.get(key);
            }

            return (Class<? extends Entity>) wrapped.get(key);
        }

        @Override
        public int a(Object key) { //TODO: Update name on version change (getId)
            if (this.customEntityIds.containsKey(key)) {
                return this.customEntityIds.get(key);
            }

            return this.wrapped.a(key);
        }

        @Override
        public MinecraftKey b(Object value) { //TODO: Update name on version change (getKey)
            if (this.customEntityClasses.containsKey(value)) {
                return this.customEntityClasses.get(value);
            }

            return (MinecraftKey) wrapped.b(value);
        }
    }

    6. Register the entity in your onEnable
    Note: You have to change the ID of the entity if you spawn something different than a Zombie.

    Code (Text):
    CustomEntityRegistry.registerCustomEntity(54, "zombie", CustomZombie.class);
    7. Spawn the zombie
    Code (Text):
        public void spawnZombie(World w){
            World nmsWorld = ((CraftWorld) w.getWorld()).getHandle();
            Location loc = new Location(w.getWorld(), 100, 100, 100);
         
            CustomZombie cz = new CustomZombie(nmsWorld);
            cz.setPosition(loc.getX(), loc.getY(), loc.getZ());
           
         
            nmsWorld.addEntity(cz);          
        }
    Current state: This will spawn the zombie we have created.

    8. Set Attack to the zombie or/and yaw
    Code (Text):
        public void spawnZombie(World w){
            World nmsWorld = ((CraftWorld) w.getWorld()).getHandle();
            Location loc = new Location(w.getWorld(), 100, 100, 100);
         
            CustomZombie cz = new CustomZombie(nmsWorld);
            cz.setPosition(loc.getX(), loc.getY(), loc.getZ());
            cz.h(loc.getYaw());
            cz.i(loc.getYaw());  
            cz.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(cz, EntityHuman.class, 0, true, false, null));
            nmsWorld.addEntity(cz);          
        }
       
    Current state: The zombie will spawn with pre defined yaw and will attack a Player when the player is in range

    9. Set attack damage, speed and follow range

    Code (Text):
    cz.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.28d);
                        cz.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(3);
                        cz.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(20);
    Current state: The zombie will spawn with a speed of 0.28d (Faster than original), a attack damage of 3 hearts and a follow range value of 20

    10. Create the pathfinder class
    Code (Text):
    package de.mavecrit.pawars.entitys.nms;

    import java.util.logging.Logger;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;

    import net.minecraft.server.v1_11_R1.EntityCreature;
    import net.minecraft.server.v1_11_R1.EntityInsentient;
    import net.minecraft.server.v1_11_R1.EntityLiving;
    import net.minecraft.server.v1_11_R1.Navigation;
    import net.minecraft.server.v1_11_R1.PathEntity;
    import net.minecraft.server.v1_11_R1.PathfinderGoal;
    import net.minecraft.server.v1_11_R1.RandomPositionGenerator;
    import net.minecraft.server.v1_11_R1.Vec3D;

    public class PathfinderGoalWalkToLoc extends PathfinderGoal {

        public PathfinderGoalWalkToLoc() {

        }

        @Override
        public boolean a() {
     
        }

        @Override
        public void c() {
        }

        @Override
        public boolean b() {
        }
    }

    11. Create variables to set the location of the goal

    Note: Old codes will break here cause Mojang changed everything in here to Vec3D

    Code (Text):
    package de.mavecrit.pawars.entitys.nms;

    import java.util.logging.Logger;

    import org.bukkit.Bukkit;
    import org.bukkit.Location;

    import net.minecraft.server.v1_11_R1.EntityCreature;
    import net.minecraft.server.v1_11_R1.EntityInsentient;
    import net.minecraft.server.v1_11_R1.EntityLiving;
    import net.minecraft.server.v1_11_R1.Navigation;
    import net.minecraft.server.v1_11_R1.PathEntity;
    import net.minecraft.server.v1_11_R1.PathfinderGoal;
    import net.minecraft.server.v1_11_R1.RandomPositionGenerator;
    import net.minecraft.server.v1_11_R1.Vec3D;

    public class PathfinderGoalWalkToLoc extends PathfinderGoal {

        // NMS Entity
        private EntityCreature b;

        // speed
        protected double a;

        // random PosX
        private double c;

        // random PosY
        private double d;

        // random PosZ
        private double e;

        public PathfinderGoalWalkToLoc(EntityCreature entitycreature, double d0, double x, double y, double z) {
            this.b = entitycreature;
            this.a = d0;
            this.d = y;
            this.c = x;
            this.e = z;
        }

        @Override
        public boolean a() {
            Vec3D vec3d = RandomPositionGenerator.a(this.b, 5, 4);
            if (vec3d == null) return false;
            return true;
        }

        @Override
        public void c() {
            Vec3D vec3d = RandomPositionGenerator.a(this.b, 5, 4);
            if (vec3d == null) return; // IN AIR
            this.b.getNavigation().a(c, d, e, 2);
        }

        @Override
        public boolean b() {
            if ((this.b.ticksLived - this.b.hurtTimestamp) > 100) {
                this.b.b((EntityLiving) null);
                return false;
            }
            return !this.b.getNavigation().n();
        }
    }
    Current state: This is the customPathfinder class in which you can set the location and spawn

    12. Let the zombie walk to a given location
    Code (Text):
        cz.goalSelector.a(3, new PathfinderGoalWalkToLoc(cz, 0.28d, loc.getX(), loc.getY(), loc.getZ()));
    Current state: The zombie spawns and walks to the given location with a speed of 0.28d


    So thats it!
    There you have your own custom entity which walks to a location you have given.

    Thanks for reading :)

    Special thanks to iSach for releasing UltraCosmetics for free! The code made it much more easier to me since I didn't found out that I need to use Vec3D from now. THANKS!
     
    • Useful Useful x 3
  2. Space for edits
     
  3. Very useful. I never made custom entities before!
     
  4. In newer versions the goalselectors are declared public. No need to get it with reflection.
    Create e new goalselector and assign it.


    Edit: nothing changed since 1.8, only visibility changed by spigot for convenience. It could be that there will be a Pathfindergoal API later on spigot.
     
    #4 ysl3000, Apr 28, 2017
    Last edited: Apr 28, 2017
    • Useful Useful x 1
  5. I hate to be this guy, but... entities.
     
  6. I also made a resource about this a while ago, yes; for 1.11, which should be more up-to-date seeing you use reflection here.
    Hopefully this covers something I didn't, though! :)