package com.nisovin.magicspells.spells;
import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import com.nisovin.magicspells.BuffManager;
import com.nisovin.magicspells.MagicSpells;
import com.nisovin.magicspells.events.SpellCastEvent;
import com.nisovin.magicspells.spelleffects.EffectPosition;
import com.nisovin.magicspells.spelleffects.SpellEffect;
import com.nisovin.magicspells.util.MagicConfig;
import com.nisovin.magicspells.util.SpellReagents;
import com.nisovin.magicspells.util.TargetInfo;
public abstract class BuffSpell extends TargetedSpell implements TargetedEntitySpell {
private BuffSpell thisSpell;
protected boolean targeted;
protected boolean toggle;
protected int healthCost = 0;
protected int manaCost = 0;
protected int hungerCost = 0;
protected int experienceCost = 0;
protected int levelsCost = 0;
protected SpellReagents reagents;
protected int useCostInterval;
protected int numUses;
protected float duration;
protected boolean powerAffectsDuration;
protected boolean cancelOnGiveDamage;
protected boolean cancelOnTakeDamage;
protected boolean cancelOnDeath;
protected boolean cancelOnTeleport;
protected boolean cancelOnChangeWorld;
protected boolean cancelOnSpellCast;
protected boolean cancelOnLogout;
protected String strFade;
private boolean castWithItem;
private boolean castByCommand;
private HashMap<String,Integer> useCounter;
private HashMap<String,Long> durationEndTime;
public BuffSpell(MagicConfig config, String spellName) {
super(config, spellName);
thisSpell = this;
targeted = getConfigBoolean("targeted", false);
toggle = getConfigBoolean("toggle", true);
reagents = getConfigReagents("use-cost");
useCostInterval = getConfigInt("use-cost-interval", 0);
numUses = getConfigInt("num-uses", 0);
duration = getConfigFloat("duration", 0);
powerAffectsDuration = getConfigBoolean("power-affects-duration", true);
cancelOnGiveDamage = getConfigBoolean("cancel-on-give-damage", false);
cancelOnTakeDamage = getConfigBoolean("cancel-on-take-damage", false);
cancelOnDeath = getConfigBoolean("cancel-on-death", false);
cancelOnTeleport = getConfigBoolean("cancel-on-teleport", false);
cancelOnChangeWorld = getConfigBoolean("cancel-on-change-world", false);
cancelOnSpellCast = getConfigBoolean("cancel-on-spell-cast", false);
cancelOnLogout = getConfigBoolean("cancel-on-logout", false);
if (cancelOnGiveDamage || cancelOnTakeDamage) {
registerEvents(new DamageListener());
}
if (cancelOnDeath) {
registerEvents(new DeathListener());
}
if (cancelOnTeleport) {
registerEvents(new TeleportListener());
}
if (cancelOnChangeWorld) {
registerEvents(new ChangeWorldListener());
}
if (cancelOnSpellCast) {
registerEvents(new SpellCastListener());
}
if (cancelOnLogout) {
registerEvents(new QuitListener());
}
strFade = getConfigString("str-fade", "");
if (numUses > 0 || (reagents != null && useCostInterval > 0)) {
useCounter = new HashMap<String,Integer>();
}
if (duration > 0) {
durationEndTime = new HashMap<String,Long>();
}
castWithItem = getConfigBoolean("can-cast-with-item", true);
castByCommand = getConfigBoolean("can-cast-by-command", true);
}
public boolean canCastWithItem() {
return castWithItem;
}
public boolean canCastByCommand() {
return castByCommand;
}
@Override
public final PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args) {
Player target;
if (targeted) {
TargetInfo<Player> targetInfo = getTargetedPlayer(player, power);
if (targetInfo == null) return noTarget(player);
target = targetInfo.getTarget();
power = targetInfo.getPower();
} else {
target = player;
}
PostCastAction action = activate(player, target, power, args, state == SpellCastState.NORMAL);
if (targeted && action == PostCastAction.HANDLE_NORMALLY) {
sendMessages(player, target);
return PostCastAction.NO_MESSAGES;
}
return action;
}
@Override
public boolean castAtEntity(Player caster, LivingEntity target, float power) {
if (target instanceof Player) {
return activate(caster, (Player)target, power, null, true) == PostCastAction.HANDLE_NORMALLY;
} else {
return false;
}
}
@Override
public boolean castAtEntity(LivingEntity target, float power) {
if (target instanceof Player) {
return activate(null, (Player)target, power, null, true) == PostCastAction.HANDLE_NORMALLY;
} else {
return false;
}
}
private PostCastAction activate(Player caster, Player target, float power, String[] args, boolean normal) {
if (isActive(target)) {
if (toggle) {
turnOff(target);
return PostCastAction.ALREADY_HANDLED;
} else {
if (normal) {
boolean ok = recastBuff(target, power, args);
if (ok) {
startSpellDuration(target, power);
if (caster == null) {
playSpellEffects(EffectPosition.TARGET, target);
} else {
playSpellEffects(caster, target);
}
}
}
return PostCastAction.HANDLE_NORMALLY;
}
}
if (normal) {
boolean ok = castBuff(target, power, args);
if (ok) {
startSpellDuration(target, power);
if (caster == null) {
playSpellEffects(EffectPosition.TARGET, target);
} else {
playSpellEffects(caster, target);
}
}
}
return PostCastAction.HANDLE_NORMALLY;
}
public abstract boolean castBuff(Player player, float power, String[] args);
public boolean recastBuff(Player player, float power, String[] args) {
return true;
}
public void setAsEverlasting() {
duration = 0;
numUses = 0;
useCostInterval = 0;
}
/**
* Begins counting the spell duration for a player
* @param player the player to begin counting duration
*/
private void startSpellDuration(final Player player, float power) {
if (duration > 0 && durationEndTime != null) {
float dur = duration;
if (powerAffectsDuration) dur *= power;
durationEndTime.put(player.getName(), System.currentTimeMillis() + Math.round(dur * 1000));
final String name = player.getName();
Bukkit.getScheduler().scheduleSyncDelayedTask(MagicSpells.plugin, new Runnable() {
public void run() {
Player p = Bukkit.getPlayerExact(name);
if (p == null) p = player;
if (p != null && isExpired(p)) {
turnOff(p);
}
}
}, Math.round(dur * 20) + 20); // overestimate ticks, since the duration is real-time ms based
}
playSpellEffectsBuff(player, new SpellEffect.SpellEffectActiveChecker() {
@Override
public boolean isActive(Entity entity) {
return thisSpell.isActiveAndNotExpired((Player)entity);
}
});
BuffManager buffman = MagicSpells.getBuffManager();
if (buffman != null) buffman.addBuff(player, this);
}
/**
* Checks whether the spell's duration has expired for a player
* @param player the player to check
* @return true if the spell has expired, false otherwise
*/
protected boolean isExpired(Player player) {
if (duration <= 0 || durationEndTime == null) {
return false;
} else {
Long endTime = durationEndTime.get(player.getName());
if (endTime == null) {
return false;
} else if (endTime > System.currentTimeMillis()) {
return false;
} else {
return true;
}
}
}
public boolean isActiveAndNotExpired(Player player) {
if (duration > 0 && isExpired(player)) return false;
return isActive(player);
}
/**
* Checks if this buff spell is active for the specified player
* @param player the player to check
* @return true if the spell is active, false otherwise
*/
public abstract boolean isActive(Player player);
/**
* Adds a use to the spell for the player. If the number of uses exceeds the amount allowed, the spell will immediately expire.
* This does not automatically charge the use cost.
* @param player the player to add the use for
* @return the player's current number of uses (returns 0 if the use counting feature is disabled)
*/
protected int addUse(Player player) {
if (numUses > 0 || (reagents != null && useCostInterval > 0)) {
Integer uses = useCounter.get(player.getName());
if (uses == null) {
uses = 1;
} else {
uses++;
}
if (numUses > 0 && uses >= numUses) {
turnOff(player);
} else {
useCounter.put(player.getName(), uses);
}
return uses;
} else {
return 0;
}
}
/**
* Removes this spell's use cost from the player's inventory. If the reagents aren't available, the spell will expire.
* @param player the player to remove the cost from
* @return true if the reagents were removed, or if the use cost is disabled, false otherwise
*/
protected boolean chargeUseCost(Player player) {
if (reagents != null && useCostInterval > 0 && useCounter != null && useCounter.containsKey(player.getName())) {
int uses = useCounter.get(player.getName());
if (uses % useCostInterval == 0) {
if (hasReagents(player, reagents)) {
removeReagents(player, reagents);
return true;
} else {
turnOff(player);
return false;
}
}
}
return true;
}
/**
* Adds a use to the spell for the player. If the number of uses exceeds the amount allowed, the spell will immediately expire.
* Removes this spell's use cost from the player's inventory. This does not return anything, to get useful return values, use
* addUse() and chargeUseCost().
* @param player the player to add a use and charge cost to
*/
protected void addUseAndChargeCost(Player player) {
addUse(player);
chargeUseCost(player);
}
/**
* Turns off this spell for the specified player. This can be called from many situations, including when the spell expires or the uses run out.
* When overriding this function, you should always be sure to call super.turnOff(player).
* @param player
*/
public final void turnOff(Player player) {
if (isActive(player)) {
if (useCounter != null) useCounter.remove(player.getName());
if (durationEndTime != null) durationEndTime.remove(player.getName());
BuffManager buffman = MagicSpells.getBuffManager();
if (buffman != null) buffman.removeBuff(player, this);
sendMessage(player, strFade);
playSpellEffects(EffectPosition.DISABLED, player);
turnOffBuff(player);
}
}
protected abstract void turnOffBuff(Player player);
@Override
protected abstract void turnOff();
@Override
public boolean isBeneficialDefault() {
return true;
}
public boolean isTargeted() {
return targeted;
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
if (isActive(event.getPlayer()) && isExpired(event.getPlayer())) {
turnOff(event.getPlayer());
}
}
public class DamageListener implements Listener {
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onPlayerDamage(EntityDamageEvent event) {
if (cancelOnTakeDamage && event.getEntity() instanceof Player && isActive((Player)event.getEntity())) {
turnOff((Player)event.getEntity());
} else if (cancelOnGiveDamage && event instanceof EntityDamageByEntityEvent) {
EntityDamageByEntityEvent evt = (EntityDamageByEntityEvent)event;
if (evt.getDamager() instanceof Player && isActive((Player)evt.getDamager())) {
turnOff((Player)evt.getDamager());
} else if (evt.getDamager() instanceof Projectile && ((Projectile)evt.getDamager()).getShooter() instanceof LivingEntity) {
LivingEntity shooter = (LivingEntity)((Projectile)evt.getDamager()).getShooter();
if (shooter instanceof Player && isActive((Player)shooter)) {
turnOff((Player)shooter);
}
}
}
}
}
public class DeathListener implements Listener {
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
if (isActive(event.getEntity())) {
turnOff(event.getEntity());
}
}
}
public class TeleportListener implements Listener {
@EventHandler(priority=EventPriority.LOWEST)
public void onTeleport(PlayerTeleportEvent event) {
if (isActive(event.getPlayer())) {
if (!event.getFrom().getWorld().getName().equals(event.getTo().getWorld().getName()) || event.getFrom().toVector().distanceSquared(event.getTo().toVector()) > 25) {
turnOff(event.getPlayer());
}
}
}
}
public class ChangeWorldListener implements Listener {
@EventHandler(priority=EventPriority.LOWEST)
public void onChangeWorld(PlayerChangedWorldEvent event) {
if (isActive(event.getPlayer())) {
turnOff(event.getPlayer());
}
}
}
public class SpellCastListener implements Listener {
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onChangeWorld(SpellCastEvent event) {
if (thisSpell != event.getSpell() && event.getSpellCastState() == SpellCastState.NORMAL && isActive(event.getCaster())) {
turnOff(event.getCaster());
}
}
}
public class QuitListener implements Listener {
@EventHandler(priority=EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
if (isActive(event.getPlayer())) {
turnOff(event.getPlayer());
}
}
}
}