@Override
 public boolean bH() {
   if (npc == null) return super.bH();
   boolean protectedDefault = npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true);
   if (!protectedDefault || !npc.data().get(NPC.LEASH_PROTECTED_METADATA, protectedDefault))
     return super.bH();
   if (super.bH()) {
     a(true, false); // clearLeash with client update
   }
   return false; // shouldLeash
 }
  @Override
  public void j_() {
    super.j_();
    if (npc == null) return;
    boolean navigating = npc.getNavigator().isNavigating();
    updatePackets(navigating);
    if (gravity
        && !navigating
        && getBukkitEntity() != null
        && Util.isLoaded(getBukkitEntity().getLocation(LOADED_LOCATION))
        && !NMS.inWater(getBukkitEntity())) {
      move(0, -0.2, 0);
      // gravity. also works around an entity.onGround not updating issue
      // (onGround is normally updated by the client)
    }
    if (!npc.data().get("removefromplayerlist", true)) g();
    if (Math.abs(motX) < EPSILON && Math.abs(motY) < EPSILON && Math.abs(motZ) < EPSILON)
      motX = motY = motZ = 0;

    NMS.updateSenses(this);
    if (navigating) {
      Navigation navigation = getNavigation();
      if (!navigation.f()) navigation.e();
      moveOnCurrentHeading();
    } else if (motX != 0 || motZ != 0 || motY != 0) {
      e(0, 0); // is this necessary? it does controllable but sometimes
      // players sink into the ground
    }

    if (noDamageTicks > 0) --noDamageTicks;
    npc.update();
  }
  @Override
  public void setSkinName(String name, boolean forceUpdate) {
    Preconditions.checkNotNull(name);

    npc.data().setPersistent(NPC.PLAYER_SKIN_UUID_METADATA, name.toLowerCase());
    skinTracker.notifySkinChange(forceUpdate);
  }
  @Override
  public String getSkinName() {
    MetadataStore meta = npc.data();

    String skinName = meta.get(NPC.PLAYER_SKIN_UUID_METADATA);
    if (skinName == null) {
      skinName = ChatColor.stripColor(getName());
    }
    return skinName.toLowerCase();
  }
 @Override
 public void m() {
   if (npc != null) {
     npc.update();
     if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) {
       super.m();
     }
   } else {
     super.m();
   }
 }
 @Override
 public void g(double x, double y, double z) {
   if (npc == null) {
     super.g(x, y, z);
     return;
   }
   if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) {
     if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) super.g(x, y, z);
     return;
   }
   Vector vector = new Vector(x, y, z);
   NPCPushEvent event = Util.callPushEvent(npc, vector);
   if (!event.isCancelled()) {
     vector = event.getCollisionVector();
     super.g(vector.getX(), vector.getY(), vector.getZ());
   }
   // when another entity collides, this method is called to push the
   // NPC so we prevent it from doing anything if the event is
   // cancelled.
 }
 @Override
 public boolean damageEntity(DamageSource damagesource, float f) {
   if (npc == null || !npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true))
     return super.damageEntity(damagesource, f);
   return false;
 }
 @Override
 public boolean isCollidable() {
   return npc == null ? super.isCollidable() : npc.data().get(NPC.COLLIDABLE_METADATA, true);
 }