public ProjectileSpell(MagicConfig config, String spellName) {
    super(config, spellName);

    String projectileType = getConfigString("projectile", "arrow");
    if (projectileType.equalsIgnoreCase("arrow")) {
      projectileClass = Arrow.class;
    } else if (projectileType.equalsIgnoreCase("snowball")) {
      projectileClass = Snowball.class;
    } else if (projectileType.equalsIgnoreCase("egg")) {
      projectileClass = Egg.class;
    } else if (projectileType.equalsIgnoreCase("enderpearl")) {
      projectileClass = EnderPearl.class;
    } else if (projectileType.equalsIgnoreCase("potion")) {
      projectileClass = ThrownPotion.class;
    } else {
      ItemStack item = Util.getItemStackFromString(projectileType);
      if (item != null) {
        item.setAmount(0);
        projectileItem = item;
      }
    }
    if (projectileClass == null && projectileItem == null) {
      MagicSpells.error("Invalid projectile type on spell '" + internalName + "'");
    }
    velocity = getConfigFloat("velocity", 0);
    horizSpread = getConfigFloat("horizontal-spread", 0);
    vertSpread = getConfigFloat("vertical-spread", 0);
    applySpellPowerToVelocity = getConfigBoolean("apply-spell-power-to-velocity", false);
    requireHitEntity = getConfigBoolean("require-hit-entity", false);
    cancelDamage = getConfigBoolean("cancel-damage", true);
    removeProjectile = getConfigBoolean("remove-projectile", true);
    maxDistanceSquared = getConfigInt("max-distance", 0);
    maxDistanceSquared = maxDistanceSquared * maxDistanceSquared;
    effectInterval = getConfigInt("effect-interval", 0);
    spellNames = getConfigStringList("spells", null);
    aoeRadius = getConfigInt("aoe-radius", 0);
    targetPlayers = getConfigBoolean("target-players", false);
    allowTargetChange = getConfigBoolean("allow-target-change", true);
    projectileHasGravity = getConfigBoolean("gravity", true);
    strHitCaster = getConfigString("str-hit-caster", "");
    strHitTarget = getConfigString("str-hit-target", "");

    if (projectileClass != null) {
      projectiles = new HashMap<Projectile, ProjectileInfo>();
    } else if (projectileItem != null) {
      itemProjectiles = new HashMap<Item, ProjectileSpell.ProjectileInfo>();
    }
  }
  @Override
  public void initialize() {
    super.initialize();

    // create spell list
    spells = new ArrayList<Spell>();
    if (spellNames != null) {
      for (String spellName : spellNames) {
        Spell spell = MagicSpells.getSpellByInternalName(spellName);
        if (spell != null) {
          spells.add(spell);
        }
      }
    }
    if (spells.size() == 0) {
      MagicSpells.error("Passive spell '" + name + "' has no spells defined!");
      return;
    }

    // get trigger
    int trigCount = 0;
    if (triggers != null) {
      for (String strigger : triggers) {
        String type = strigger;
        String var = null;
        if (strigger.contains(" ")) {
          String[] data = Util.splitParams(strigger, 2);
          type = data[0];
          var = data[1];
        }
        type = type.toLowerCase();

        PassiveTrigger trigger = PassiveTrigger.getByName(type);
        if (trigger != null) {
          manager.registerSpell(this, trigger, var);
          trigCount++;
        } else {
          MagicSpells.error(
              "Invalid trigger '" + strigger + "' on passive spell '" + internalName + "'");
        }
      }
    }
    if (trigCount == 0) {
      MagicSpells.error("Passive spell '" + name + "' has no triggers defined!");
      return;
    }
  }
  public ItemProjectileSpell(MagicConfig config, String spellName) {
    super(config, spellName);

    speed = getConfigFloat("speed", 1);
    vertSpeedUsed = configKeyExists("vert-speed");
    vertSpeed = getConfigFloat("vert-speed", 0);
    hitRadius = getConfigFloat("hit-radius", 1);
    yOffset = getConfigFloat("y-offset", 0);
    projectileHasGravity = getConfigBoolean("gravity", true);

    if (configKeyExists("spell-on-hit-entity")) {
      spellOnHitEntity = new Subspell(getConfigString("spell-on-hit-entity", ""));
    }
    if (configKeyExists("spell-on-hit-ground")) {
      spellOnHitGround = new Subspell(getConfigString("spell-on-hit-ground", ""));
    }

    item = Util.getItemStackFromString(getConfigString("item", "iron_sword"));
  }
  public static Modifier factory(String s) {
    Modifier m = new Modifier();
    String[] data = Util.splitParams(s);
    if (data.length < 2) return null;

    // get condition
    m.condition = Condition.getConditionByName(data[0]);
    if (m.condition == null) return null;

    // get type and vars
    m.type = getTypeByName(data[1]);
    if (m.type == null && data.length > 2) {
      boolean varok = m.condition.setVar(data[1]);
      if (!varok) return null;
      m.type = getTypeByName(data[2]);
      if (data.length > 3) {
        m.modifierVar = data[3];
      }
    } else if (data.length > 2) {
      m.modifierVar = data[2];
    }

    // check type
    if (m.type == null) return null;

    // process modifiervar
    try {
      if (m.type == ModifierType.POWER
          || m.type == ModifierType.ADD_POWER
          || m.type == ModifierType.COOLDOWN
          || m.type == ModifierType.REAGENTS) {
        m.modifierVarFloat = Float.parseFloat(m.modifierVar);
      } else if (m.type == ModifierType.CAST_TIME) {
        m.modifierVarInt = Integer.parseInt(m.modifierVar);
      }
    } catch (NumberFormatException e) {
      return null;
    }

    // done
    return m;
  }
 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;
 }