package com.nisovin.magicspells.spells.targeted; import java.util.ArrayList; import java.util.List; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.Subspell; import com.nisovin.magicspells.events.SpellTargetEvent; import com.nisovin.magicspells.spelleffects.EffectPosition; import com.nisovin.magicspells.spells.TargetedEntityFromLocationSpell; import com.nisovin.magicspells.spells.TargetedEntitySpell; import com.nisovin.magicspells.spells.TargetedSpell; import com.nisovin.magicspells.util.MagicConfig; import com.nisovin.magicspells.util.TargetInfo; public class ChainSpell extends TargetedSpell implements TargetedEntitySpell, TargetedEntityFromLocationSpell { String spellNameToCast; Subspell spellToCast; ValidTargetChecker checker; int bounces; int bounceRange; int interval; boolean targetPlayers; boolean targetNonPlayers; public ChainSpell(MagicConfig config, String spellName) { super(config, spellName); spellNameToCast = getConfigString("spell", "heal"); bounces = getConfigInt("bounces", 3); bounceRange = getConfigInt("bounce-range", 8); interval = getConfigInt("interval", 10); targetPlayers = getConfigBoolean("target-players", true); targetNonPlayers = getConfigBoolean("target-non-players", false); } @Override public void initialize() { super.initialize(); Subspell spell = new Subspell(spellNameToCast); if (spell.process()) { spellToCast = spell; checker = spell.getSpell().getValidTargetChecker(); } else { MagicSpells.error("Invalid spell defined for ChainSpell '" + this.name + "'"); } } @Override public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) { if (state == SpellCastState.NORMAL) { TargetInfo<LivingEntity> target = getTargetedEntity(player, power, checker); if (target == null) { return noTarget(player); } chain(player, player.getLocation(), target.getTarget(), target.getPower()); sendMessages(player, target.getTarget()); return PostCastAction.NO_MESSAGES; } return PostCastAction.HANDLE_NORMALLY; } private void chain(Player player, Location start, LivingEntity target, float power) { List<LivingEntity> targets = new ArrayList<LivingEntity>(); List<Float> targetPowers = new ArrayList<Float>(); targets.add(target); targetPowers.add(power); // get targets LivingEntity current = target; int attempts = 0; while (targets.size() < bounces && attempts++ < bounces * 2) { List<Entity> entities = current.getNearbyEntities(bounceRange, bounceRange, bounceRange); for (Entity e : entities) { if (!(e instanceof LivingEntity)) { continue; } if (targets.contains(e)) { continue; } if (e instanceof Player) { if (!targetPlayers) { continue; } } else if (!targetNonPlayers) { continue; } if (checker != null && !checker.isValidTarget((LivingEntity)e)) { continue; } float thisPower = power; if (player != null) { SpellTargetEvent event = new SpellTargetEvent(this, player, (LivingEntity)e, thisPower); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { continue; } else { thisPower = event.getPower(); } } targets.add((LivingEntity)e); targetPowers.add(thisPower); current = (LivingEntity)e; break; } } // cast spell at targets if (player != null) { playSpellEffects(EffectPosition.CASTER, player); } else if (start != null) { playSpellEffects(EffectPosition.CASTER, start); } if (interval <= 0) { for (int i = 0; i < targets.size(); i++) { Location from = null; if (i == 0) { from = start; } else { from = targets.get(i-1).getLocation(); } castSpellAt(player, from, targets.get(i), targetPowers.get(i)); if (i > 0) { playSpellEffectsTrail(targets.get(i-1).getLocation(), targets.get(i).getLocation()); } else if (i == 0 && player != null) { playSpellEffectsTrail(player.getLocation(), targets.get(i).getLocation()); } playSpellEffects(EffectPosition.TARGET, targets.get(i)); } } else { new ChainBouncer(player, start, targets, power); } } private boolean castSpellAt(Player caster, Location from, LivingEntity target, float power) { if (spellToCast.isTargetedEntityFromLocationSpell() && from != null) { return spellToCast.castAtEntityFromLocation(caster, from, target, power); } else if (spellToCast.isTargetedEntitySpell()) { return spellToCast.castAtEntity(caster, target, power); } else if (spellToCast.isTargetedLocationSpell()) { return spellToCast.castAtLocation(caster, target.getLocation(), power); } return true; } @Override public boolean castAtEntity(Player caster, LivingEntity target, float power) { chain(caster, caster.getLocation(), target, power); return true; } @Override public boolean castAtEntity(LivingEntity target, float power) { chain(null, null, target, power); return true; } @Override public boolean castAtEntityFromLocation(Player caster, Location from, LivingEntity target, float power) { chain(caster, from, target, power); return true; } @Override public boolean castAtEntityFromLocation(Location from, LivingEntity target, float power) { chain(null, from, target, power); return true; } class ChainBouncer implements Runnable { Player caster; Location start; List<LivingEntity> targets; float power; int current = 0; int taskId; public ChainBouncer(Player caster, Location start, List<LivingEntity> targets, float power) { this.caster = caster; this.start = start; this.targets = targets; this.power = power; taskId = MagicSpells.scheduleRepeatingTask(this, 0, interval); } public void run() { Location from = null; if (current == 0) { from = start; } else { from = targets.get(current-1).getLocation(); } castSpellAt(caster, from, targets.get(current), power); if (current > 0) { playSpellEffectsTrail(targets.get(current-1).getLocation().add(0, .5, 0), targets.get(current).getLocation().add(0, .5, 0)); } else if (current == 0 && caster != null) { playSpellEffectsTrail(caster.getLocation().add(0, .5, 0), targets.get(current).getLocation().add(0, .5, 0)); } playSpellEffects(EffectPosition.TARGET, targets.get(current)); current++; if (current >= targets.size()) { MagicSpells.cancelTask(taskId); } } } }