// when a vehicle is damaged
  @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
  public void onVehicleDamage(VehicleDamageEvent event) {
    // all of this is anti theft code
    if (!GriefPrevention.instance.config_claims_preventTheft) return;

    // determine which player is attacking, if any
    Player attacker = null;
    Entity damageSource = event.getAttacker();
    if (damageSource instanceof Player) {
      attacker = (Player) damageSource;
    } else if (damageSource instanceof Arrow) {
      Arrow arrow = (Arrow) damageSource;
      if (arrow.getShooter() instanceof Player) {
        attacker = (Player) arrow.getShooter();
      }
    } else if (damageSource instanceof ThrownPotion) {
      ThrownPotion potion = (ThrownPotion) damageSource;
      if (potion.getShooter() instanceof Player) {
        attacker = (Player) potion.getShooter();
      }
    }

    // NOTE: vehicles can be pushed around.
    // so unless precautions are taken by the owner, a resourceful thief might find ways to steal
    // anyway
    Claim cachedClaim = null;
    PlayerData playerData = null;
    if (attacker != null) {
      playerData = this.dataStore.getPlayerData(attacker.getName());
      cachedClaim = playerData.lastClaim;
    }

    Claim claim = this.dataStore.getClaimAt(event.getVehicle().getLocation(), false, cachedClaim);

    // if it's claimed
    if (claim != null) {
      // if damaged by anything other than a player, cancel the event
      if (attacker == null) {
        event.setCancelled(true);
      }

      // otherwise the player damaging the entity must have permission
      else {
        String noContainersReason = claim.allowContainers(attacker);
        if (noContainersReason != null) {
          event.setCancelled(true);
          GriefPrevention.sendMessage(
              attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName());
        }

        // cache claim for later
        if (playerData != null) {
          playerData.lastClaim = claim;
        }
      }
    }
  }
  // when an entity is damaged
  @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
  public void onEntityDamage(EntityDamageEvent event) {
    // only actually interested in entities damaging entities (ignoring environmental damage)
    if (!(event instanceof EntityDamageByEntityEvent)) return;

    // monsters are never protected
    if (event.getEntity() instanceof Monster) return;

    EntityDamageByEntityEvent subEvent = (EntityDamageByEntityEvent) event;

    // determine which player is attacking, if any
    Player attacker = null;
    Entity damageSource = subEvent.getDamager();
    if (damageSource instanceof Player) {
      attacker = (Player) damageSource;
    } else if (damageSource instanceof Arrow) {
      Arrow arrow = (Arrow) damageSource;
      if (arrow.getShooter() instanceof Player) {
        attacker = (Player) arrow.getShooter();
      }
    } else if (damageSource instanceof ThrownPotion) {
      ThrownPotion potion = (ThrownPotion) damageSource;
      if (potion.getShooter() instanceof Player) {
        attacker = (Player) potion.getShooter();
      }
    }

    // if the attacker is a player and defender is a player (pvp combat)
    if (attacker != null && event.getEntity() instanceof Player) {
      // FEATURE: prevent pvp in the first minute after spawn, and prevent pvp when one or both
      // players have no inventory

      // doesn't apply when the attacker has the no pvp immunity permission
      // this rule is here to allow server owners to have a world with no spawn camp protection by
      // assigning permissions based on the player's world
      if (attacker.hasPermission("griefprevention.nopvpimmunity")) return;

      Player defender = (Player) (event.getEntity());

      PlayerData defenderData =
          this.dataStore.getPlayerData(((Player) event.getEntity()).getName());
      PlayerData attackerData = this.dataStore.getPlayerData(attacker.getName());

      // otherwise if protecting spawning players
      if (GriefPrevention.instance.config_pvp_protectFreshSpawns) {
        if (defenderData.pvpImmune) {
          event.setCancelled(true);
          GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.ThatPlayerPvPImmune);
          return;
        }

        if (attackerData.pvpImmune) {
          event.setCancelled(true);
          GriefPrevention.sendMessage(attacker, TextMode.Err, Messages.CantFightWhileImmune);
          return;
        }
      }

      // FEATURE: prevent players who very recently participated in pvp combat from hiding inventory
      // to protect it from looting
      // FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated

      long now = Calendar.getInstance().getTimeInMillis();
      defenderData.lastPvpTimestamp = now;
      defenderData.lastPvpPlayer = attacker.getName();
      attackerData.lastPvpTimestamp = now;
      attackerData.lastPvpPlayer = defender.getName();
    }

    // FEATURE: protect claimed animals, boats, minecarts
    // NOTE: animals can be lead with wheat, vehicles can be pushed around.
    // so unless precautions are taken by the owner, a resourceful thief might find ways to steal
    // anyway

    // if theft protection is enabled
    if (event instanceof EntityDamageByEntityEvent) {
      // if the entity is an non-monster creature (remember monsters disqualified above), or a
      // vehicle
      if ((subEvent.getEntity() instanceof Creature
          && GriefPrevention.instance.config_claims_protectCreatures)) {
        Claim cachedClaim = null;
        PlayerData playerData = null;
        if (attacker != null) {
          playerData = this.dataStore.getPlayerData(attacker.getName());
          cachedClaim = playerData.lastClaim;
        }

        Claim claim =
            this.dataStore.getClaimAt(event.getEntity().getLocation(), false, cachedClaim);

        // if it's claimed
        if (claim != null) {
          // if damaged by anything other than a player, cancel the event
          if (attacker == null) {
            event.setCancelled(true);
          }

          // otherwise the player damaging the entity must have permission
          else {
            String noContainersReason = claim.allowContainers(attacker);
            if (noContainersReason != null) {
              event.setCancelled(true);
              GriefPrevention.sendMessage(
                  attacker, TextMode.Err, Messages.NoDamageClaimedEntity, claim.getOwnerName());
            }

            // cache claim for later
            if (playerData != null) {
              playerData.lastClaim = claim;
            }
          }
        }
      }
    }
  }