Comprehensive Tutorials

Minecraft Forge 1.7.2/1.7.10 Custom Entity Attacks

Introduction


Very often when people make a custom entity, they run across this "issue" -- that their entity attacks but doesn't do damage.  For example the entity may jump at or collide with the player, but no damage is taken.

Note: if the entity doesn't even try to attack, then you have a problem with your AI.  In that case you should check out my tutorial on custom entity AI:  Jabelar's Custom Entity AI Tutorial.

Anyway, there are usually two problems causing the lack of damage from an attempted attack:
  1. No attackDamage attribute set.  By default, classes that extend the Entity classes have no attackDamage attribute registered.  To fix that see my tutorial on custom entity attributes: Jabelar's Custom Entity Attributes Tutorial.
  2. Missing or ineffective @Override of the attackAsMob() method.  This tutorial explains how to fix this.

How Entity Attack Damage Works In Minecraft


The damage done during an attack is actually somewhat convoluted in the Minecraft code, because the damage is processed by the entity class that is being attacked, rather than by the attacker class. This makes sense if you think about it, because it the entity being attacked may have various resistances (there is hurt resistance if attacks happen quickly after previous attacks, they may be immune to certain DamageSource types, they may have a potion effect, and so forth). So the idea is that the attacking entity really just calls the attacked entity's method for processing damage.

Key point: The attackEntityAsMob() method in the attacking class should call the attackEntityFrom() method of the attacked class passing the attackDamage attribute.

When Entity Attack Damage Goes Wrong


The attackEntityAsMob() method is inherited from the EntityLivingBase class, however that class doesn't inflict any damage; rather it does nothing but update the last attacker, and in fact it seems to do this backwards (bug?).  Let's look at that briefly, here is the code from the EntityLivingBase attackEntityAsMob() method:

public boolean attackEntityAsMob(Entity par1Entity)
{
    this.setLastAttacker(par1Entity);
    return false;
}

If you think about the logic in the above method, it seems to be backwards -- you are setting the last attacker of this which is the attacking entity to be the other entity.  It seems it really should be instead: par1Entity.setLastAttacker(this).  (However, it is possible that the method is misnamed or something, maybe it is meant to set the last attacked, but looking briefly through the method it seems to be named correctly.)

It is further interesting that vanilla mobs (see EntityMob class or EntityWolf class) seem to fully override this method and don't call this as super method.  So maybe it is known bug.

Tip: The attackEntityAsMob() method in EntityLivingBase is useless and maybe even buggy.  You need to ensure it is fully overridden.

If your custom entity extends EntityMob, then you are probably okay (and probably wouldn't have ended up at this tutorial!) because it does an @Override of the method and properly calls the attacked class' attackEntityFrom() method with attackDamage passed.

However, if your custom entity extends something else like EntityCreature, or EntityAnimal, then you probably need to add your own @Override.

Key point: Somewhere in your custom class, or inherited from its parent classes, there needs to be a proper @Override of the attackEntityAsMob() method.

Example Method


Since the attacking and attacked entities may have enchantments (i.e. a more sophisticated mob) you'd want to ensure those are applied within the method.  So for most cases you should generally copy the code from EntityMob.  EntityMob also has some obfuscated enchantments which I expect should probably be copied (if anyone knows what these do, let me know!).  Lastly, the actual knockback should be applied here.  For example:

@Override
public boolean attackEntityAsMob(Entity entityTarget)
{
    float attackDamage = (float)getEntityAttribute(SharedMonsterAttributes
          .attackDamage).getAttributeValue();
    int knockbackModifier = 0;

    if (entityTarget instanceof EntityLivingBase)
    {
        attackDamage += EnchantmentHelper.getEnchantmentModifierLiving(this, 
              (EntityLivingBase)entityTarget);
        knockbackModifier  += EnchantmentHelper.getKnockbackModifier(this, 
              (EntityLivingBase)entityTarget);
    }

    boolean isTargetHurt = entityTarget.attackEntityFrom(DamageSource
          .causeMobDamage(this), attackDamage);

    if (wasDamageDone)
    {
        if (knockbackModifier  > 0)
        {
            entityTarget.addVelocity((double)(-MathHelper.sin(rotationYaw * 
                  (float)Math.PI / 180.0F) * (float)knockbackModifier  * 0.5F), 
                   0.1D, (double)(MathHelper.cos(rotationYaw * 
                  (float)Math.PI / 180.0F) * (float)knockbackModifier  * 0.5F));
            motionX *= 0.6D;
            motionZ *= 0.6D;
        }

        int fireModifier = EnchantmentHelper.getFireAspectModifier(this);

        if (fireModifier > 0)
        {
            entityTarget.setFire(fireModifier * 4);
        }

        // I copied these enchantments from EntityMob, not sure what they do
        if (entityTarget instanceof EntityLivingBase)
        {
            EnchantmentHelper.func_151384_a((EntityLivingBase)entityTarget, this);
        }

        EnchantmentHelper.func_151385_b(this, entityTarget);
    }

    return isTargetHurt ;
}

Hopefully the example is fairly self-explanatory.  The effects of various enchantments are checked and applied if they are non-zero.

Conclusion


As you should see, the attackAsMob() method is a very key method for any custom entity that has the ability to attack.  Extending any class other than EntityMob will probably require you to @Override this method with similar code.

As always, if you see any issues with my tutorial please send your suggestions for correction and improvement!

No comments:

Post a Comment