public boolean projectileHitEntity(Entity projectile, LivingEntity target, ProjectileInfo info) {
    if (!info.done
        && (maxDistanceSquared == 0
            || projectile.getLocation().distanceSquared(info.start) <= maxDistanceSquared)) {
      if (aoeRadius == 0) {
        float power = info.power;

        // check player
        if (!targetPlayers && target instanceof Player) return false;

        // call target event
        SpellTargetEvent evt = new SpellTargetEvent(this, info.player, target, power);
        Bukkit.getPluginManager().callEvent(evt);
        if (evt.isCancelled()) {
          return false;
        } else if (allowTargetChange) {
          target = evt.getTarget(); // TODO make an alternative to overriding the parameter
          power = evt.getPower();
        }

        // run spells
        for (Subspell spell : spells) {
          if (spell.isTargetedEntitySpell()) {
            spell.castAtEntity(info.player, target, power);
            playSpellEffects(EffectPosition.TARGET, target);
          } else if (spell.isTargetedLocationSpell()) {
            spell.castAtLocation(info.player, target.getLocation(), power);
            playSpellEffects(EffectPosition.TARGET, target.getLocation());
          }
        }

        // send messages
        String entityName;
        if (target instanceof Player) {
          entityName = ((Player) target).getDisplayName();
        } else {
          EntityType entityType = target.getType();
          entityName = MagicSpells.getEntityNames().get(entityType);
          if (entityName == null) {
            entityName = entityType.name().toLowerCase();
          }
        }
        sendMessage(
            formatMessage(strHitCaster, "%t", entityName), info.player, MagicSpells.NULL_ARGS);
        if (target instanceof Player) {
          sendMessage(
              formatMessage(strHitTarget, "%a", info.player.getDisplayName()),
              (Player) target,
              MagicSpells.NULL_ARGS);
        }
      } else {
        aoe(projectile, info);
      }

      info.done = true;
    }
    return true;
  }
  private void aoe(Entity projectile, ProjectileInfo info) {
    playSpellEffects(EffectPosition.SPECIAL, projectile.getLocation());
    List<Entity> entities = projectile.getNearbyEntities(aoeRadius, aoeRadius, aoeRadius);
    for (Entity entity : entities) {
      if (entity instanceof LivingEntity
          && (targetPlayers || !(entity instanceof Player))
          && !entity.equals(info.player)) {
        LivingEntity target = (LivingEntity) entity;
        float power = info.power;

        // call target event
        SpellTargetEvent evt = new SpellTargetEvent(this, info.player, target, power);
        Bukkit.getPluginManager().callEvent(evt);
        if (evt.isCancelled()) {
          continue;
        } else if (allowTargetChange) {
          target = evt.getTarget();
        }
        power = evt.getPower();

        // run spells
        for (Subspell spell : spells) {
          if (spell.isTargetedEntitySpell()) {
            spell.castAtEntity(info.player, target, power);
            playSpellEffects(EffectPosition.TARGET, target);
          } else if (spell.isTargetedLocationSpell()) {
            spell.castAtLocation(info.player, target.getLocation(), power);
            playSpellEffects(EffectPosition.TARGET, target.getLocation());
          }
        }

        // send message if player
        if (target instanceof Player) {
          sendMessage(
              formatMessage(strHitTarget, "%a", info.player.getDisplayName()),
              (Player) target,
              MagicSpells.NULL_ARGS);
        }
      }
    }
    sendMessage(strHitCaster, info.player, MagicSpells.NULL_ARGS);
  }