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;
  }
 @Override
 public void initialize() {
   super.initialize();
   if (spellOnHitEntity != null && !spellOnHitEntity.process()) {
     spellOnHitEntity = null;
     MagicSpells.error("Invalid spell-on-hit-entity for " + internalName);
   }
   if (spellOnHitGround != null && !spellOnHitGround.process()) {
     spellOnHitGround = null;
     MagicSpells.error("Invalid spell-on-hit-ground for " + internalName);
   }
 }
  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);
  }
 boolean projectileHitLocation(Entity projectile, ProjectileInfo info) {
   if (!requireHitEntity
       && !info.done
       && (maxDistanceSquared == 0
           || projectile.getLocation().distanceSquared(info.start) <= maxDistanceSquared)) {
     if (aoeRadius == 0) {
       for (Subspell spell : spells) {
         if (spell.isTargetedLocationSpell()) {
           Location loc = projectile.getLocation();
           Util.setLocationFacingFromVector(loc, projectile.getVelocity());
           spell.castAtLocation(info.player, loc, info.power);
           playSpellEffects(EffectPosition.TARGET, loc);
         }
       }
       sendMessage(strHitCaster, info.player, MagicSpells.NULL_ARGS);
     } else {
       aoe(projectile, info);
     }
     info.done = true;
   }
   return true;
 }
  @Override
  public void initialize() {
    super.initialize();
    spells = new ArrayList<Subspell>();
    if (spellNames != null) {
      for (String spellName : spellNames) {
        Subspell spell = new Subspell(spellName);
        if (spell.process()) {
          spells.add(spell);
        } else {
          MagicSpells.error(
              "Projectile spell '"
                  + internalName
                  + "' attempted to add invalid spell '"
                  + spellName
                  + "'.");
        }
      }
    }
    if (spells.size() == 0) {
      MagicSpells.error("Projectile spell '" + internalName + "' has no spells!");
    }

    if (projectileClass != null) {
      if (projectileClass == EnderPearl.class) {
        registerEvents(new EnderTpListener());
      } else if (projectileClass == Egg.class) {
        registerEvents(new EggListener());
      } else if (projectileClass == ThrownPotion.class) {
        registerEvents(new PotionListener());
      }
      registerEvents(new ProjectileListener());
    } else if (projectileItem != null) {
      registerEvents(new PickupListener());
    }
  }