Tutorial Use NMS To Allow Giants to Attack (1.7.9 & 1.7.10)

Discussion in 'Resources' started by AlphaRLee, Jul 24, 2015.

Thread Status:
Not open for further replies.
  1. Offline

    AlphaRLee

    Hi! I've been looking around everywhere for a hint on how to get giants to be able to attack, and I wasn't able to find any answers. I found a couple people lost in looking for how, so I'm hoping I'll be able to help them out a bit. :)

    This tutorial will cover how to use NMS to create a giant who is capable of more than just following its target, it will cover how to actually attack. If you really want, you can use this method to make any mob (that is a member of EntityMonster) to attack via melee

    **As of right now, I have not been able to figure out all pieces, so if you can share some knowledge, please add!
    **Compatibility is only for 1.7.10, and as far as I know, 1.7.9. I managed to build this in 1.7.9

    1. Create a custom giant class:
    I took advantage of this excellent tutorial here: https://forums.bukkit.org/threads/nms-tutorial-how-to-override-default-minecraft-mobs.216788/. Unfortunately, this topic is far too large to fit in this tutorial, so have fun with reading! Giants are called EntityGiantZombie in code by the way. (If you can already spawn customized NMS entities, skip this step).

    2. Override the attack method
    Right now, the giant relies on the EntityMonster's attack method, which was designed not to work for something of its size. It looks like this:
    Code:
    protected void a(Entity entity, float f) {
            if (this.attackTicks <= 0 && f < 2.0F && entity.boundingBox.e > this.boundingBox.b && entity.boundingBox.b < this.boundingBox.e) {
                this.attackTicks = 20;
                this.n(entity);
            }
        }
    If we follow the original code, line by line, it essentially says this:
    I. The function attack is called "a". The 2 input parameters is the entity being attacked (NMS) and the maximum distance this attack takes place.
    II. The field "attackTicks" iterates down by 1 every tick. When it reaches 0, check the following:
    -If the target is less than 2.0 "blocks" away AND
    -If the target's y coordinate (head) is above the giant's y coordinate (foot) AND
    -If the target's y coordinate (foot) is below the giant's y coordinate (foot)
    III. Pass all these, and reset the attack to take place 1 second later.
    IV. Attack the target.

    It's the standard model that works for most entities. However, Minecraft is a bit sneaky and promises that this doesn't work for giants. Most of the numbers in there don't work perfectly. The foot (this.boundingBox.e) is apparently at the same level as the head, the giant is thicker than 2 "blocks", and the attack doesn't work. So I added this to our custom giant class:

    Code:
        @Override
        protected void a(Entity entity, float f) {
            if (this.attackTicks <= 0 && f < 3.3F && entity.boundingBox.e > this.boundingBox.b - 12 && entity.boundingBox.b < this.boundingBox.e) {
                this.attackTicks = 20;
                entity.damageEntity(DamageSource.mobAttack(this), 25.0F);
            }
        }
    Now it says:
    I. The function attack is called "a". The 2 input parameters is the entity being attacked (NMS) and the maximum distance this attack takes place. This overrides the EntityMonster class for the custom giant
    II. The field "attackTicks" iterates down by 1 every tick. When it reaches 0, check the following:
    -If the target is less than 3.3 "blocks" away (trial and error found this to be suitable for giants chasing zombies) AND
    -If the target's y coordinate (head) is above the giant's y coordinate (foot, now calibrated to where it actually belongs) AND
    -If the target's y coordinate (foot) is below the giant's y coordinate (foot)
    III. Pass all these, and reset the attack to take place 1 second later.
    IV. Attack the target (this line is borrowed from the EntityEnderman class, who drowns a little in rain/water). Set the attack as a mobAttack and set the damage to 25.0F (This doesn't represent the number of hearts, trial and error will determine the true damage of this. A higher number is more damaging).

    If you managed to follow this successfully, then hooray! You now have a giant who can actually attack small beings. This isn't perfect, as examples shown zombies trying to attack the same giant just pushes it (Go modify the attack range of a zombie using a similar technique). In order to modify it to attack any entity, I recommend replacing the f value with the width of the target/2 + the width of the giant/2. If your math works but your code doesn't, display your numbers and look for more strange code. Some numbers that should work give strange values at times.

    Hopefully this helps some of you! If you have questions, please ask, I'll try to help!
     
  2. @AlphaRLee
    I don't want to be a buzzkill or something, but isn't it way easier to do using some of the zombie's pathfinders?
     
  3. Offline

    AlphaRLee

    @megamichiel I'm not actually in the position to agree or disagree with you, hahaha...I'm quite a beginner at this NMS stuff, but when I was trying, I couldn't add pathfindergoals to my entities, I could only modify entities who already had pathfinders. Perhaps I'm missing something important? If you know how to add them in (and hopefully make it work), then a brief explaination/demo would be great. Or a web link would work awesome too.

    If we can make as large a learning center for others to use (so that other's wouldn't fall into my trap), then that would be awesome for everybody. Thanks!
     
  4. Offline

    ShadowLAX

    @AlphaRLee You can add pathfinders to the constructor of the custom entity's class. Use reflection to get the fields "b" and "c" from both the goalSelector and targetSelector PathfinderGoalSelectors (all entities that indirectly extend EntityInsentient have them) and either clear the lists, or create a new UnsafeList and replace each one. Then just add the pathfinders you want, as long as they can be added to your specific entity. Here's an example:

    Code:
         public CustomGiant(org.bukkit.World world) {
                super(((CraftWorld)world).getHandle()); //I use bukkit's world as the parameter for convenience.
                try {
                    Field b = PathfinderGoalSelector.class.getDeclaredField("b"); //Get the fields using reflection
                    Field c = PathfinderGoalSelector.class.getDeclaredField("c");
                    b.setAccessible(true); //Set it as able to be modified
                    c.setAccessible(true);
                    ((List<?>)b.get(goalSelector)).clear(); //clear the default pathfinders that were set
                    ((List<?>)c.get(goalSelector)).clear();
                    ((List<?>)b.get(targetSelector)).clear();
                    ((List<?>)c.get(targetSelector)).clear();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, EntityHuman.class, 1.0D, false)); //Set your custom pathfinders, I took these from the EntityZombie class.
                this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<EntityHuman>(this, EntityHuman.class, true));
    }
     
  5. Offline

    AlphaRLee

    @ShadowLAX Thanks for the suggestion, but I've already tried that technique before. In fact, I just tried again, and still haven't succeeded. Anything without a pathfinder doesn't seem to accept new ones for me. My speculation is that they have code that overrides whatever pathfinders send in, but I'm not sure. Anybody able to clarify?
     
  6. Offline

    ShadowLAX

    @AlphaRLee What entity are you trying to modify? Maybe share the code you are using? Usually only entities that indirectly extend EntityInsentient are able to use pathfinders, so you may be trying to edit an Entity that won't accept pathfinders.
     
  7. Offline

    AlphaRLee

    @ShadowLAX I'm manipulating entities that extend their appropriate monster class. For example, I've extended entity Spider, entityzombie, entityBlaze, entityCreeper etc. They all indirectly extend EntityMonster, which extends entityInsentient. Sadly, that's not my case. But thanks for the suggestion.
     
  8. Offline

    ShadowLAX

    @AlphaRLee Hmm, I'm not sure what you are doing wrong then. I've been able to add new pathfinders and modify old ones to most of those before. If you post your code I might be able to assist you more.
     
  9. Offline

    AlphaRLee

    @ShadowLAX
    Let's try this:
    (Note: This is 1.7.9)
    Code:
    import java.lang.reflect.Field;
    import java.util.List;
    
    import me.alpharlee.mobwars.MWEntityInterface;
    import me.alpharlee.mobwars.MainMobWars;
    import me.alpharlee.mobwars.MobWarsGame;
    import me.alpharlee.mobwars.MobWarsSquad;
    import me.alpharlee.mobwars.TargetEntity;
    import me.alpharlee.mobwars.TeamType;
    import net.minecraft.server.v1_7_R3.DamageSource;
    import net.minecraft.server.v1_7_R3.Entity;
    import net.minecraft.server.v1_7_R3.EntityGiantZombie;
    import net.minecraft.server.v1_7_R3.EntityLiving;
    import net.minecraft.server.v1_7_R3.EntityPig;
    import net.minecraft.server.v1_7_R3.GenericAttributes;
    import net.minecraft.server.v1_7_R3.PathfinderGoalFloat;
    import net.minecraft.server.v1_7_R3.PathfinderGoalHurtByTarget;
    import net.minecraft.server.v1_7_R3.PathfinderGoalLookAtPlayer;
    import net.minecraft.server.v1_7_R3.PathfinderGoalMeleeAttack;
    import net.minecraft.server.v1_7_R3.PathfinderGoalMoveThroughVillage;
    import net.minecraft.server.v1_7_R3.PathfinderGoalMoveTowardsRestriction;
    import net.minecraft.server.v1_7_R3.PathfinderGoalNearestAttackableTarget;
    import net.minecraft.server.v1_7_R3.PathfinderGoalRandomLookaround;
    import net.minecraft.server.v1_7_R3.PathfinderGoalRandomStroll;
    import net.minecraft.server.v1_7_R3.PathfinderGoalSelector;
    import net.minecraft.server.v1_7_R3.World;
    
    public class MWEntityGiant extends EntityGiantZombie implements MWEntityInterface {
    
        public MobWarsGame game = null;
        public TeamType team = TeamType.NOTEAM;
        public MobWarsSquad squad = null;
    
        public boolean isCustom = false;
    
        public double moveToX = MainMobWars.USEDEFAULT;
        public double moveToY = MainMobWars.USEDEFAULT;
        public double moveToZ = MainMobWars.USEDEFAULT;
    
        public MWEntityGiant(World world) {
            super(world);
        }
    
        //Summon custom giant
        public MWEntityGiant (World world, MobWarsGame g, TeamType t, MobWarsSquad s) {
            super(world);
        
            this.isCustom = true;
            this.game = g;
            this.team = t;
            this.squad = s;
    
            this.setPathfinders();
        }
    
        public MobWarsGame getGame() {
            return this.game;
        }
    
        public TeamType getTeam() {
            return this.team;
        }
    
        public MobWarsSquad getSquad() {
            return this.squad;
        }
    
        public double getMoveToX() {
            return this.moveToX;
        }
    
        public double getMoveToY() {
            return this.moveToY;
        }
    
        public double getMoveToZ() {
            return this.moveToZ;
        }
    
        public void setMoveToX(double moveTo) {
            this.moveToX = moveTo;
        }
    
        public void setMoveToY(double moveTo) {
            this.moveToY = moveTo;
        }
    
        public void setMoveToZ(double moveTo) {
            this.moveToZ = moveTo;
        }
    
        public void setMoveToLocation(double toX, double toY, double toZ) {
            this.moveToX = toX;
            this.moveToY = toY;
            this.moveToZ = toZ;
        }
    
        private void setPathfinders() {
            //Change pathfinders to do nothing
            try {
                Field bField = PathfinderGoalSelector.class.getDeclaredField("b");
                bField.setAccessible(true);
                Field cField = PathfinderGoalSelector.class.getDeclaredField("c");
                cField.setAccessible(true);
                bField.set(goalSelector, new UnsafeList<PathfinderGoalSelector>());
                bField.set(targetSelector, new UnsafeList<PathfinderGoalSelector>());
                cField.set(goalSelector, new UnsafeList<PathfinderGoalSelector>());
                cField.set(targetSelector, new UnsafeList<PathfinderGoalSelector>());
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        
            //Change pathfinders to do default (changes so zombies hunt skeletons, not villagers)
            this.goalSelector.a(0, new PathfinderGoalFloat(this));
            this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, EntityPig.class, 1.0D, false));
            this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D));
            this.goalSelector.a(6, new PathfinderGoalMoveThroughVillage(this, 1.0D, false));
            this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityPig.class, 8.0F));
            this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
            this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, true));
            this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityPig.class, 0, true));
    
        }
    
        @Override
        protected void aC() {
            super.aC();
            this.getAttributeInstance(GenericAttributes.b).setValue(30.0); // Attack range
            this.getAttributeInstance(GenericAttributes.d).setValue(2.0D); //Speed
        }
    
        @Override
        protected void a(Entity entity, float f) {
        
            if (this.attackTicks <= 0) {
                this.game.mainClass.getLogger().info("f: " + f);
            }
        
            if (this.attackTicks <= 0 && f < 5.0F && entity.boundingBox.e > this.boundingBox.b - 12 && entity.boundingBox.b < this.boundingBox.e) {
                this.attackTicks = 20;
    
                entity.damageEntity(DamageSource.mobAttack(this), 25.0F);
            }
        }
    }
    
    Despite the different variations I tried, it ultimately repeats these procedures: Strip the existing pathfinders, add new pathfinders, call those pathfinders in the...drat, word slipped me, the opening class part. If you find any errors, please tell me, but mind you I might have already tried such a modification before. Thanks!

    EDIT: Changed pathFinderGoals to target pigs instead of humans
     
    Last edited: Jul 26, 2015
  10. Offline

    ShadowLAX

    @AlphaRLee What exactly is wrong/not working? I tested your code and it works perfectly for me.
     
  11. Offline

    AlphaRLee

    @ShadowLAX Oh, my bad, I forgot to change the pathfinder from Human. This would make giants behave like traditional giants. Try changing the findNearestTarget parameter to EntityPig
     
  12. Offline

    ShadowLAX

    @AlphaRLee PathfinderGoalNearestTarget and PathfinderGoalMeleeAttack go hand in hand with each other; NearestTarget finds the actually entity, while MeleeAttack actually allows the entity to attack the specified one by melee. Without one, the entity can not perform attacking properly. Change the MeleeAttack one to EntityPig as well and it will work.
     
  13. Offline

    AlphaRLee

    @ShadowLAX Thanks for the suggestion, I just realized that myself. Unfortunately, I've also tried that, but my giant just rotates on the spot (while I'm out of it's range). I've also edited LookAtPlayer with no luck.

    Ps. How did you spawn your custom entities? Perhaps my spawning methods are having problems.
     
  14. Offline

    ShadowLAX

    1) It shouldn't matter if you are in range if you are aim for it to target EntityPig, right? Put a pig or two within a couple blocks of the giant. Otherwise it has nothing to target.
    2) It shouldn't be anything in how it's spawned, but here:
    Code:
        public static void spawnEntity(Entity ent, Location l) {
            ent.setLocation(l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch());
            ((CraftWorld)l.getWorld()).addEntity(ent, SpawnReason.CUSTOM);
        }
     
Thread Status:
Not open for further replies.

Share This Page