NMS getAttributeInstance(ATTACK_SPEED) for EntityZombie throws nullPointerException

Discussion in 'Plugin Development' started by MrDaniel, Nov 11, 2020.

  1. Offline

    MrDaniel

    Hello gamers,

    I am trying to add a ATTACK_SPEED modifier to custom zombie class which extends EntityZombie. The way I do that is by this funny line of code
    Code:
    getAttributeInstance(GenericAttributes.ATTACK_SPEED).setValue(1D);
    The problem is, that throws a nullPointerException. I am guessing it's because the attribute hasn't been initialized, but after looking at the nms classes, I couldn't figure out how to create the instance. I found
    Stacktrace (open)

    Code:
    11:58:36] [Server thread/INFO]: DespacitoMaster issued server command: /zombie
    [11:58:36] [Server thread/ERROR]: null
    org.bukkit.command.CommandException: Unhandled exception executing command 'zombie' in plugin ApocalypseZ v1.0
        at org.bukkit.command.PluginCommand.execute(PluginCommand.java:47) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:149) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at org.bukkit.craftbukkit.v1_16_R2.CraftServer.dispatchCommand(CraftServer.java:758) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.PlayerConnection.handleCommand(PlayerConnection.java:1697) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.PlayerConnection.a(PlayerConnection.java:1540) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.PacketPlayInChat.a(PacketPlayInChat.java:47) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.PacketPlayInChat.a(PacketPlayInChat.java:1) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.PlayerConnectionUtils.lambda$0(PlayerConnectionUtils.java:19) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.TickTask.run(SourceFile:18) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.IAsyncTaskHandler.executeTask(SourceFile:144) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.IAsyncTaskHandlerReentrant.executeTask(SourceFile:23) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.IAsyncTaskHandler.executeNext(SourceFile:118) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.MinecraftServer.ba(MinecraftServer.java:941) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.MinecraftServer.executeNext(MinecraftServer.java:934) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.IAsyncTaskHandler.executeAll(SourceFile:103) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.MinecraftServer.sleepForTick(MinecraftServer.java:917) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.MinecraftServer.w(MinecraftServer.java:850) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at net.minecraft.server.v1_16_R2.MinecraftServer.lambda$0(MinecraftServer.java:164) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        at java.lang.Thread.run(Unknown Source) [?:1.8.0_271]
    Caused by: java.lang.NullPointerException
        at eu.ladeira.apocalypse.Infected.setAttributes(Infected.java:65) ~[?:?]
        at eu.ladeira.apocalypse.Infected.<init>(Infected.java:40) ~[?:?]
        at eu.ladeira.apocalypse.commands.Zombie.onCommand(Zombie.java:27) ~[?:?]
        at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[spigot-1.16.3.jar:git-Spigot-57bbdd8-dea4138]
        ... 18 more
    


    Here are the things I have looked at and tried:

    Number 1 (open)
    1( I have tried overriding a static method, which I failed at, but I did notice it. I am guessing it initializes the GenericAttributes
    Code:
    public static AttributeProvider.Builder eS() {
            return EntityMonster.eR().a(GenericAttributes.FOLLOW_RANGE, 35.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.23000000417232513D).a(GenericAttributes.ATTACK_DAMAGE, 3.0D).a(GenericAttributes.ARMOR, 2.0D).a(GenericAttributes.SPAWN_REINFORCEMENTS).a(GenericAttributes.ATTACK_SPEED, 1.0D);
        }

    Number 2 (open)
    2) The getAttributeInstance() method comes from the EntityLiving class, this is the method
    Code:
    public AttributeModifiable getAttributeInstance(AttributeBase attributebase) {
        return getAttributeMap().a(attributebase);
    }
    The getAttributeMap() method returns a class called AttributeMapBase, this is the class code
    AttributeMapBase (open)

    Code:
    package net.minecraft.server.v1_16_R2;
    
    import com.google.common.collect.Maps;
    import com.google.common.collect.Multimap;
    import com.google.common.collect.Sets;
    import java.util.Collection;
    import java.util.Map;
    import java.util.Set;
    import java.util.UUID;
    import java.util.stream.Collectors;
    import javax.annotation.Nullable;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    public class AttributeMapBase {
      private static final Logger LOGGER = LogManager.getLogger();
      private final Map<AttributeBase, AttributeModifiable> b = Maps.newHashMap();
      private final Set<AttributeModifiable> c = Sets.newHashSet();
      private final AttributeProvider d;
      public AttributeMapBase(AttributeProvider var0) {
        this.d = var0;
      }
      private void a(AttributeModifiable var0) {
        if (var0.getAttribute().b())
          this.c.add(var0);
      }
      public Set<AttributeModifiable> getAttributes() {
        return this.c;
      }
      public Collection<AttributeModifiable> b() {
        return (Collection<AttributeModifiable>)this.b.values().stream().filter(var0 -> var0.getAttribute().b()).collect(Collectors.toList());
      }
      @Nullable
      public AttributeModifiable a(AttributeBase var0) {
        return this.b.computeIfAbsent(var0, var0 -> this.d.a(this::a, var0));
      }
      public boolean b(AttributeBase var0) {
        return (this.b.get(var0) != null || this.d.c(var0));
      }
      public boolean a(AttributeBase var0, UUID var1) {
        AttributeModifiable var2 = this.b.get(var0);
        return (var2 != null) ? ((var2.a(var1) != null)) : this.d.b(var0, var1);
      }
      public double c(AttributeBase var0) {
        AttributeModifiable var1 = this.b.get(var0);
        return (var1 != null) ? var1.getValue() : this.d.a(var0);
      }
      public double d(AttributeBase var0) {
        AttributeModifiable var1 = this.b.get(var0);
        return (var1 != null) ? var1.getBaseValue() : this.d.b(var0);
      }
      public double b(AttributeBase var0, UUID var1) {
        AttributeModifiable var2 = this.b.get(var0);
        return (var2 != null) ? var2.a(var1).getAmount() : this.d.a(var0, var1);
      }
      public void a(Multimap<AttributeBase, AttributeModifier> var0) {
        var0.asMap().forEach((var0, var1) -> {
              AttributeModifiable var2 = this.b.get(var0);
              if (var2 != null)
                var1.forEach(var2::removeModifier);
            });
      }
      public void b(Multimap<AttributeBase, AttributeModifier> var0) {
        var0.forEach((var0, var1) -> {
              AttributeModifiable var2 = a(var0);
              if (var2 != null) {
                var2.removeModifier(var1);
                var2.b(var1);
              }
            });
      }
      public NBTTagList c() {
        NBTTagList var0 = new NBTTagList();
        for (AttributeModifiable var2 : this.b.values())
          var0.add(var2.g());
        return var0;
      }
      public void a(NBTTagList var0) {
        for (int var1 = 0; var1 < var0.size(); var1++) {
          NBTTagCompound var2 = var0.getCompound(var1);
          String var3 = var2.getString("Name");
          SystemUtils.a(IRegistry.ATTRIBUTE.getOptional(MinecraftKey.a(var3)), var1 -> {
                AttributeModifiable var2 = a(var1);
                if (var2 != null)
                  var2.a(var0);
              }() -> LOGGER.warn("Ignoring unknown attribute '{}'", var0));
        }
      }
    }


    and this is the method a that is being called
    Code:
    @Nullable
      public AttributeModifiable a(AttributeBase var0) {
        return this.b.computeIfAbsent(var0, var0 -> this.d.a(this::a, var0));
      }
    The variable b is this:
    Code:
    private final Map<AttributeBase, AttributeModifiable> b = Maps.newHashMap();
    After scanning through all the methods in the AttributeMapBase class I couldn't find any that puts anything into the variable b.



    My infected class
    Infected (open)

    Code:
    package eu.ladeira.apocalypse;
    
    import org.bukkit.Location;
    import org.bukkit.craftbukkit.v1_16_R2.CraftWorld;
    import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
    
    import net.md_5.bungee.api.ChatColor;
    import net.minecraft.server.v1_16_R2.ChatMessage;
    import net.minecraft.server.v1_16_R2.EntityHuman;
    import net.minecraft.server.v1_16_R2.EntityIronGolem;
    import net.minecraft.server.v1_16_R2.EntityPigZombie;
    import net.minecraft.server.v1_16_R2.EntityTurtle;
    import net.minecraft.server.v1_16_R2.EntityVillagerAbstract;
    import net.minecraft.server.v1_16_R2.EntityZombie;
    import net.minecraft.server.v1_16_R2.GenericAttributes;
    import net.minecraft.server.v1_16_R2.IChatBaseComponent;
    import net.minecraft.server.v1_16_R2.PathfinderGoalHurtByTarget;
    import net.minecraft.server.v1_16_R2.PathfinderGoalMoveThroughVillage;
    import net.minecraft.server.v1_16_R2.PathfinderGoalNearestAttackableTarget;
    import net.minecraft.server.v1_16_R2.PathfinderGoalRandomStrollLand;
    import net.minecraft.server.v1_16_R2.PathfinderGoalZombieAttack;
    
    
    public class Infected extends EntityZombie {
       
        boolean strollRandomly;
        private int level;
       
        public Infected(Location loc) {
            super(((CraftWorld)loc.getWorld()).getHandle());
            this.setPosition(loc.getX(), loc.getY(), loc.getZ());
            this.strollRandomly = false;
           
            // TODO Make zombie attack quickly
           
            this.level = this.generateLevel(1, 40);
            this.setCustomName(generateName());
            this.setCustomNameVisible(true);
           
            setAttributes();
        }
       
        public int getLevel() {
            return this.level;
        }
       
        public int generateLevel(int min, int max) {
            return (int) (Math.random() * ((max - min) + 1) + min); // Generate random level between min and max
        }
       
        public IChatBaseComponent generateName() {
            if (this.level <= 10) {
                return new ChatMessage(ChatColor.WHITE + "" + level + ChatColor.RED + " Infected");
            } else if (this.level <= 30) {
                return new ChatMessage(ChatColor.WHITE + "" + level + ChatColor.DARK_RED + " Infected");
            } else if (this.level <= 40) {
                return new ChatMessage(ChatColor.WHITE + "" + level + ChatColor.LIGHT_PURPLE + " Infected");
            }
            return new ChatMessage(ChatColor.AQUA + "INVALID ZOMBIE LEVEL");
        }
       
        public void setAttributes() {
            // Generic Attributes
            getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(15D);
            getAttributeInstance(GenericAttributes.ATTACK_SPEED).setValue(1D);
           
            if (this.level >= 10) {
                getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.4D);
            }
            if (this.level >= 40) {
                getAttributeInstance(GenericAttributes.KNOCKBACK_RESISTANCE).setValue(2D);
                getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(0.8D);
            }
        }
       
        @Override
        protected void m() {
            this.goalSelector.a(2, new PathfinderGoalZombieAttack(this, 1.0D, false));
            this.goalSelector.a(6, new PathfinderGoalMoveThroughVillage(this, 1.0D, true, 4, this::eU));
            if (strollRandomly) {
                this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D));
            }
            this.targetSelector.a(1, (new PathfinderGoalHurtByTarget(this, new Class[0])).a(new Class[] { EntityPigZombie.class }));
            this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
            if (this.world.spigotConfig.zombieAggressiveTowardsVillager)
                this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillagerAbstract.class, false)); 
            this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true));
            this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, 10, true, false, EntityTurtle.bo));
           
        }
       
        public static void spawnInfected(Location loc) {
            Infected inf = new Infected(loc);
            ((CraftWorld)loc.getWorld()).getHandle().addEntity(inf, SpawnReason.CUSTOM);
        }
    }
    


    Thank you for coming for my interactive Ted Talk
     
  2. Offline

    MrDaniel

  3. Offline

    jonthe445

    This is a different way to approach the problem but why not look at the on hit event/test for infected and possibly modify damage/apply the damage twice. Idk if that accomplishes what you are trying to do but to me "attack speed" may apply to like skeley bow shooting speed.
     
  4. Offline

    MrDaniel

    What I am trying to accomplish is a fast and difficult to hit zombie that attacks very frequently. I could do it that way but I would like to see if it's possible using NMS
     

Share This Page