package com.nisovin.magicspells.spells.targeted;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import com.nisovin.magicspells.MagicSpells;
import com.nisovin.magicspells.Spell;
import com.nisovin.magicspells.events.SpellTargetLocationEvent;
import com.nisovin.magicspells.materials.MagicMaterial;
import com.nisovin.magicspells.spelleffects.EffectPosition;
import com.nisovin.magicspells.spells.TargetedLocationSpell;
import com.nisovin.magicspells.spells.TargetedSpell;
import com.nisovin.magicspells.util.MagicConfig;
public class PulserSpell extends TargetedSpell implements TargetedLocationSpell {
private int yOffset;
private int totalPulses;
private int interval;
private int capPerPlayer;
private int maxDistanceSquared;
private MagicMaterial material;
private boolean unbreakable;
private boolean onlyCountOnSuccess;
private List<String> spellNames;
private List<TargetedLocationSpell> spells;
private String spellNameOnBreak;
private TargetedLocationSpell spellOnBreak;
private String strAtCap;
private HashMap<Block, Pulser> pulsers;
private PulserTicker ticker;
public PulserSpell(MagicConfig config, String spellName) {
super(config, spellName);
yOffset = getConfigInt("y-offset", 0);
totalPulses = getConfigInt("total-pulses", 5);
interval = getConfigInt("interval", 30);
capPerPlayer = getConfigInt("cap-per-player", 10);
maxDistanceSquared = getConfigInt("max-distance", 30);
maxDistanceSquared *= maxDistanceSquared;
material = MagicSpells.getItemNameResolver().resolveBlock(getConfigString("block-type", "diamond_block"));
unbreakable = getConfigBoolean("unbreakable", false);
onlyCountOnSuccess = getConfigBoolean("only-count-on-success", false);
spellNames = getConfigStringList("spells", null);
spellNameOnBreak = getConfigString("spell-on-break", null);
strAtCap = getConfigString("str-at-cap", "You have too many effects at once.");
pulsers = new HashMap<Block, Pulser>();
ticker = new PulserTicker();
}
@Override
public void initialize() {
super.initialize();
spells = new ArrayList<TargetedLocationSpell>();
if (spellNames != null && spellNames.size() > 0) {
for (String spellName : spellNames) {
Spell spell = MagicSpells.getSpellByInternalName(spellName);
if (spell != null && spell instanceof TargetedLocationSpell) {
spells.add((TargetedLocationSpell) spell);
}
}
}
if (spellNameOnBreak != null) {
Spell spell = MagicSpells.getSpellByInternalName(spellNameOnBreak);
if (spell != null && spell instanceof TargetedLocationSpell) {
spellOnBreak = (TargetedLocationSpell)spell;
} else {
MagicSpells.error("Pulser spell '" + internalName + "' has an invalid spell-on-break spell defined");
}
}
if (spells.size() == 0) {
MagicSpells.error("Pulser spell '" + internalName + "' has no spells defined!");
}
}
@Override
public PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
if (state == SpellCastState.NORMAL) {
if (capPerPlayer > 0) {
int count = 0;
for (Pulser pulser : pulsers.values()) {
if (pulser.caster.equals(player)) {
count++;
if (count >= capPerPlayer) {
sendMessage(player, strAtCap);
return PostCastAction.ALREADY_HANDLED;
}
}
}
}
List<Block> lastTwo = getLastTwoTargetedBlocks(player, power);
Block target = null;
if (lastTwo != null && lastTwo.size() == 2) {
target = lastTwo.get(0);
}
if (target == null) {
return noTarget(player);
}
if (yOffset > 0) {
target = target.getRelative(BlockFace.UP, yOffset);
} else if (yOffset < 0) {
target = target.getRelative(BlockFace.DOWN, yOffset);
}
if (target.getType() != Material.AIR
&& target.getType() != Material.SNOW
&& target.getType() != Material.LONG_GRASS) {
return noTarget(player);
}
if (target != null) {
SpellTargetLocationEvent event = new SpellTargetLocationEvent(this, player, target.getLocation(), power);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return noTarget(player);
} else {
target = event.getTargetLocation().getBlock();
power = event.getPower();
}
}
createPulser(player, target, power);
}
return PostCastAction.HANDLE_NORMALLY;
}
private void createPulser(Player caster, Block block, float power) {
material.setBlock(block);
pulsers.put(block, new Pulser(caster, block, power));
ticker.start();
if (caster != null) {
playSpellEffects(caster, block.getLocation());
} else {
playSpellEffects(EffectPosition.TARGET, block.getLocation());
}
}
@Override
public boolean castAtLocation(Player caster, Location target, float power) {
Block block = target.getBlock();
if (yOffset > 0) {
block = block.getRelative(BlockFace.UP, yOffset);
} else if (yOffset < 0) {
block = block.getRelative(BlockFace.DOWN, yOffset);
}
if (block.getType() == Material.AIR
|| block.getType() == Material.SNOW
|| block.getType() == Material.LONG_GRASS) {
createPulser(caster, block, power);
return true;
} else {
block = block.getRelative(BlockFace.UP);
if (block.getType() == Material.AIR
|| block.getType() == Material.SNOW
|| block.getType() == Material.LONG_GRASS) {
createPulser(caster, block, power);
return true;
} else {
return false;
}
}
}
@Override
public boolean castAtLocation(Location target, float power) {
return castAtLocation(null, target, power);
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
Pulser pulser = pulsers.get(event.getBlock());
if (pulser != null) {
event.setCancelled(true);
if (!unbreakable) {
pulser.stop();
event.getBlock().setType(Material.AIR);
pulsers.remove(event.getBlock());
}
}
}
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onEntityExplode(EntityExplodeEvent event) {
if (pulsers.size() > 0) {
Iterator<Block> iter = event.blockList().iterator();
while (iter.hasNext()) {
Block b = iter.next();
Pulser pulser = pulsers.get(b);
if (pulser != null) {
iter.remove();
if (!unbreakable) {
pulser.stop();
pulsers.remove(b);
}
}
}
}
}
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onPiston(BlockPistonExtendEvent event) {
if (pulsers.size() > 0) {
for (Block b : event.getBlocks()) {
Pulser pulser = pulsers.get(b);
if (pulser != null) {
event.setCancelled(true);
if (!unbreakable) {
pulser.stop();
pulsers.remove(b);
}
}
}
}
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
if (pulsers.size() > 0) {
Player player = event.getEntity();
Iterator<Pulser> iter = pulsers.values().iterator();
while (iter.hasNext()) {
Pulser pulser = iter.next();
if (pulser.caster != null && pulser.caster.equals(player)) {
pulser.stop();
iter.remove();
}
}
}
}
@Override
public void turnOff() {
for (Pulser p : new ArrayList<Pulser>(pulsers.values())) {
p.stop();
}
pulsers.clear();
ticker.stop();
}
public class Pulser {
Player caster;
Block block;
Location location;
float power;
int pulseCount;
public Pulser(Player caster, Block block, float power) {
this.caster = caster;
this.block = block;
this.location = block.getLocation().add(0.5, 0.5, 0.5);
this.power = power;
this.pulseCount = 0;
}
public boolean pulse() {
if (caster == null) {
if (material.equals(block)
&& block.getChunk().isLoaded()) {
return activate();
} else {
stop();
return true;
}
} else if (caster.isValid() && caster.isOnline()
&& material.equals(block)
&& block.getChunk().isLoaded()) {
if (maxDistanceSquared > 0
&& (!location.getWorld().equals(caster.getLocation().getWorld()) || location.distanceSquared(caster.getLocation()) > maxDistanceSquared)) {
stop();
return true;
} else {
return activate();
}
} else {
stop();
return true;
}
}
private boolean activate() {
boolean activated = false;
for (TargetedLocationSpell spell : spells) {
if (caster != null) {
activated = spell.castAtLocation(caster, location, power) || activated;
} else {
activated = spell.castAtLocation(location, power) || activated;
}
}
playSpellEffects(EffectPosition.DELAYED, location);
if (totalPulses > 0 && (activated || !onlyCountOnSuccess)) {
pulseCount += 1;
if (pulseCount >= totalPulses) {
stop();
return true;
}
}
return false;
}
public void stop() {
if (!block.getChunk().isLoaded()) block.getChunk().load();
block.setType(Material.AIR);
if (spellOnBreak != null) {
if (caster == null) {
spellOnBreak.castAtLocation(location, power);
} else if (caster.isValid()) {
spellOnBreak.castAtLocation(caster, location, power);
}
}
}
}
public class PulserTicker implements Runnable {
private int taskId = -1;
public void start() {
if (taskId < 0) {
taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(
MagicSpells.plugin, this, 0, interval);
}
}
public void stop() {
if (taskId > 0) {
Bukkit.getScheduler().cancelTask(taskId);
taskId = -1;
}
}
public void run() {
for (Map.Entry<Block, Pulser> entry : new HashMap<Block, Pulser>(pulsers).entrySet()) {
boolean remove = entry.getValue().pulse();
if (remove) {
pulsers.remove(entry.getKey());
}
}
if (pulsers.size() == 0) {
stop();
}
}
}
}