package com.nisovin.magicspells;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scoreboard.Scoreboard;
import org.bukkit.scoreboard.Team;
import org.bukkit.util.BlockIterator;
import com.nisovin.magicspells.castmodifiers.ModifierSet;
import com.nisovin.magicspells.events.SpellCastEvent;
import com.nisovin.magicspells.events.SpellCastedEvent;
import com.nisovin.magicspells.events.SpellTargetEvent;
import com.nisovin.magicspells.mana.ManaChangeReason;
import com.nisovin.magicspells.mana.ManaHandler;
import com.nisovin.magicspells.spelleffects.EffectPosition;
import com.nisovin.magicspells.spelleffects.SpellEffect;
import com.nisovin.magicspells.spells.PassiveSpell;
import com.nisovin.magicspells.util.BlockUtils;
import com.nisovin.magicspells.util.CastItem;
import com.nisovin.magicspells.util.ExperienceUtils;
import com.nisovin.magicspells.util.IntMap;
import com.nisovin.magicspells.util.MagicConfig;
import com.nisovin.magicspells.util.MoneyHandler;
import com.nisovin.magicspells.util.SpellReagents;
import com.nisovin.magicspells.util.TargetInfo;
import com.nisovin.magicspells.util.Util;
import com.nisovin.magicspells.util.ValidTargetList;
import com.nisovin.magicspells.variables.VariableManager;
public abstract class Spell implements Comparable<Spell>, Listener {
private MagicConfig config;
private boolean debug;
protected String internalName;
protected String name;
protected String profilingKey;
protected String[] aliases;
protected boolean helperSpell;
protected boolean alwaysGranted;
protected String permName;
protected List<String> incantations;
protected String description;
protected CastItem[] castItems;
protected CastItem[] rightClickCastItems;
protected CastItem[] consumeCastItems;
protected boolean castWithLeftClick;
protected boolean castWithRightClick;
protected String danceCastSequence;
protected boolean requireCastItemOnCommand;
protected boolean bindable;
protected HashSet<CastItem> bindableItems;
protected ItemStack spellIcon;
protected int broadcastRange;
protected int experience;
protected HashMap<EffectPosition, List<SpellEffect>> effects;
protected int minRange;
private int range;
protected boolean spellPowerAffectsRange;
protected boolean obeyLos;
protected ValidTargetList validTargetList;
protected boolean beneficial;
private DamageCause targetDamageCause;
private double targetDamageAmount;
protected HashSet<Byte> losTransparentBlocks;
protected int castTime;
protected boolean interruptOnMove;
protected boolean interruptOnTeleport;
protected boolean interruptOnDamage;
protected boolean interruptOnCast;
protected String spellNameOnInterrupt;
protected Spell spellOnInterrupt;
protected SpellReagents reagents;
protected float cooldown;
protected float serverCooldown;
protected List<String> rawSharedCooldowns;
protected HashMap<Spell, Float> sharedCooldowns;
protected boolean ignoreGlobalCooldown;
protected int charges;
protected String rechargeSound;
private List<String> modifierStrings;
private List<String> targetModifierStrings;
protected ModifierSet modifiers;
protected ModifierSet targetModifiers;
protected List<String> prerequisites;
protected List<String> replaces;
protected List<String> precludes;
protected Map<String, Integer> xpGranted;
protected Map<String, Integer> xpRequired;
protected List<String> worldRestrictions;
protected Map<String, Double> variableModsCast;
protected Map<String, Double> variableModsCasted;
protected Map<String, Double> variableModsTarget;
protected String soundOnCooldown;
protected String soundMissingReagents;
protected String strCost;
protected String strCastSelf;
protected String strCastOthers;
protected String strOnCooldown;
protected String strMissingReagents;
protected String strCantCast;
protected String strCantBind;
protected String strWrongWorld;
protected String strWrongCastItem;
protected String strCastStart;
protected String strInterrupted;
protected String strModifierFailed;
protected String strXpAutoLearned;
private HashMap<String, Long> nextCast;
private IntMap<String> chargesConsumed;
private long nextCastServer;
public Spell(MagicConfig config, String spellName) {
this.config = config;
this.internalName = spellName;
loadConfigData(config, spellName, "spells");
}
protected void loadConfigData(MagicConfig config, String spellName, String section) {
this.debug = config.getBoolean(section + "." + spellName + ".debug", false);
this.profilingKey = "Spell:" + this.getClass().getName().replace("com.nisovin.magicspells.spells.", "") + "-" + spellName;
this.name = config.getString(section + "." + spellName + ".name", spellName);
List<String> temp = config.getStringList(section + "." + spellName + ".aliases", null);
if (temp != null) {
aliases = new String[temp.size()];
aliases = temp.toArray(aliases);
}
this.helperSpell = config.getBoolean(section + "." + spellName + ".helper-spell", false);
this.alwaysGranted = config.getBoolean(section + "." + spellName + ".always-granted", false);
this.permName = config.getString(section + "." + spellName + ".permission-name", spellName);
this.incantations = config.getStringList(section + "." + spellName + ".incantations", null);
// general options
this.description = config.getString(section + "." + spellName + ".description", "");
if (config.contains(section + "." + spellName + ".cast-item")) {
String[] sItems = config.getString(section + "." + spellName + ".cast-item", "-5").trim().replace(" ", "").split(",");
this.castItems = new CastItem[sItems.length];
for (int i = 0; i < sItems.length; i++) {
ItemStack is = Util.getItemStackFromString(sItems[i]);
if (is != null) {
this.castItems[i] = new CastItem(is);
}
}
} else if (config.contains(section + "." + spellName + ".cast-items")) {
List<String> sItems = config.getStringList(section + "." + spellName + ".cast-items", null);
if (sItems == null) sItems = new ArrayList<String>();
this.castItems = new CastItem[sItems.size()];
for (int i = 0; i < castItems.length; i++) {
ItemStack is = Util.getItemStackFromString(sItems.get(i));
if (is != null) {
this.castItems[i] = new CastItem(is);
}
}
} else {
this.castItems = new CastItem[0];
}
if (config.contains(section + "." + spellName + ".right-click-cast-item")) {
String[] sItems = config.getString(section + "." + spellName + ".right-click-cast-item", "-5").trim().replace(" ", "").split(",");
this.rightClickCastItems = new CastItem[sItems.length];
for (int i = 0; i < sItems.length; i++) {
ItemStack is = Util.getItemStackFromString(sItems[i]);
if (is != null) {
this.rightClickCastItems[i] = new CastItem(is);
}
}
} else if (config.contains(section + "." + spellName + ".right-click-cast-items")) {
List<String> sItems = config.getStringList(section + "." + spellName + ".right-click-cast-items", null);
if (sItems == null) sItems = new ArrayList<String>();
this.rightClickCastItems = new CastItem[sItems.size()];
for (int i = 0; i < rightClickCastItems.length; i++) {
ItemStack is = Util.getItemStackFromString(sItems.get(i));
if (is != null) {
this.rightClickCastItems[i] = new CastItem(is);
}
}
} else {
this.rightClickCastItems = new CastItem[0];
}
if (config.contains(section + "." + spellName + ".consume-cast-item")) {
String[] sItems = config.getString(section + "." + spellName + ".consume-cast-item", "-5").trim().replace(" ", "").split(",");
this.consumeCastItems = new CastItem[sItems.length];
for (int i = 0; i < sItems.length; i++) {
ItemStack is = Util.getItemStackFromString(sItems[i]);
if (is != null) {
this.consumeCastItems[i] = new CastItem(is);
}
}
} else if (config.contains(section + "." + spellName + ".consume-cast-items")) {
List<String> sItems = config.getStringList(section + "." + spellName + ".consume-cast-items", null);
if (sItems == null) sItems = new ArrayList<String>();
this.consumeCastItems = new CastItem[sItems.size()];
for (int i = 0; i < consumeCastItems.length; i++) {
ItemStack is = Util.getItemStackFromString(sItems.get(i));
if (is != null) {
this.consumeCastItems[i] = new CastItem(is);
}
}
} else {
this.consumeCastItems = new CastItem[0];
}
this.castWithLeftClick = config.getBoolean(section + "." + spellName + ".cast-with-left-click", MagicSpells.plugin.castWithLeftClick);
this.castWithRightClick = config.getBoolean(section + "." + spellName + ".cast-with-right-click", MagicSpells.plugin.castWithRightClick);
this.danceCastSequence = config.getString(section + "." + spellName + ".dance-cast-sequence", null);
this.requireCastItemOnCommand = config.getBoolean(section + "." + spellName + ".require-cast-item-on-command", false);
this.bindable = config.getBoolean(section + "." + spellName + ".bindable", true);
List<String> bindables = config.getStringList(section + "." + spellName + ".bindable-items", null);
if (bindables != null) {
bindableItems = new HashSet<CastItem>();
for (String s : bindables) {
ItemStack is = Util.getItemStackFromString(s);
if (is != null) {
bindableItems.add(new CastItem(is));
}
}
}
String icontemp = config.getString(section + "." + spellName + ".spell-icon", null);
if (icontemp == null) {
spellIcon = null;
} else {
spellIcon = Util.getItemStackFromString(icontemp);
if (spellIcon != null && spellIcon.getType() != Material.AIR) {
spellIcon.setAmount(0);
if (!icontemp.contains("|")) {
ItemMeta iconMeta = spellIcon.getItemMeta();
iconMeta.setDisplayName(MagicSpells.getTextColor() + name);
spellIcon.setItemMeta(iconMeta);
}
}
}
this.broadcastRange = config.getInt(section + "." + spellName + ".broadcast-range", MagicSpells.plugin.broadcastRange);
this.experience = config.getInt(section + "." + spellName + ".experience", 0);
// cast time
this.castTime = config.getInt(section + "." + spellName + ".cast-time", 0);
this.interruptOnMove = config.getBoolean(section + "." + spellName + ".interrupt-on-move", true);
this.interruptOnTeleport = config.getBoolean(section + "." + spellName + ".interrupt-on-teleport", true);
this.interruptOnDamage = config.getBoolean(section + "." + spellName + ".interrupt-on-damage", false);
this.interruptOnCast = config.getBoolean(section + "." + spellName + ".interrupt-on-cast", true);
this.spellNameOnInterrupt = config.getString(section + "." + spellName + ".spell-on-interrupt", null);
// targeting
this.minRange = config.getInt(section + "." + spellName + ".min-range", 0);
this.range = config.getInt(section + "." + spellName + ".range", 20);
this.spellPowerAffectsRange = config.getBoolean(section + "." + spellName + ".spell-power-affects-range", false);
this.obeyLos = config.getBoolean(section + "." + spellName + ".obey-los", true);
if (config.contains(section + "." + spellName + ".can-target")) {
if (config.isList(section + "." + spellName + ".can-target")) {
validTargetList = new ValidTargetList(this, config.getStringList(section + "." + spellName + ".can-target", null));
} else {
validTargetList = new ValidTargetList(this, config.getString(section + "." + spellName + ".can-target", ""));
}
} else {
boolean targetPlayers = config.getBoolean(section + "." + spellName + ".target-players", true);
boolean targetNonPlayers = config.getBoolean(section + "." + spellName + ".target-non-players", true);
validTargetList = new ValidTargetList(targetPlayers, targetNonPlayers);
}
this.beneficial = config.getBoolean(section + "." + spellName + ".beneficial", isBeneficialDefault());
this.targetDamageCause = null;
String causeStr = config.getString(section + "." + spellName + ".target-damage-cause", null);
if (causeStr != null) {
for (DamageCause cause : DamageCause.values()) {
if (cause.name().equalsIgnoreCase(causeStr)) {
this.targetDamageCause = cause;
break;
}
}
}
this.targetDamageAmount = config.getDouble(section + "." + spellName + ".target-damage-amount", 0);
this.losTransparentBlocks = MagicSpells.getTransparentBlocks();
if (config.contains(section + "." + spellName + ".los-transparent-blocks")) {
this.losTransparentBlocks = new HashSet<Byte>(config.getByteList(section + "." + spellName + ".los-transparent-blocks", null));
this.losTransparentBlocks.add((byte)0);
}
// graphical effects
if (config.contains(section + "." + spellName + ".effects")) {
this.effects = new HashMap<EffectPosition, List<SpellEffect>>();
if (config.isList(section + "." + spellName + ".effects")) {
List<String> effectsList = config.getStringList(section + "." + spellName + ".effects", null);
if (effectsList != null) {
for (Object obj : effectsList) {
if (obj instanceof String) {
String eff = (String)obj;
String[] data = eff.split(" ", 3);
EffectPosition pos = getPositionFromString(data[0]);
if (pos != null) {
SpellEffect effect = SpellEffect.createNewEffectByName(data[1]);
if (effect != null) {
effect.loadFromString(data.length > 2 ? data[2] : null);
List<SpellEffect> e = effects.get(pos);
if (e == null) {
e = new ArrayList<SpellEffect>();
effects.put(pos, e);
}
e.add(effect);
}
}
}
}
}
} else if (config.isSection(section + "." + spellName + ".effects")) {
for (String key : config.getKeys(section + "." + spellName + ".effects")) {
ConfigurationSection effConf = config.getSection(section + "." + spellName + ".effects." + key);
EffectPosition pos = getPositionFromString(effConf.getString("position", ""));
if (pos != null) {
SpellEffect effect = SpellEffect.createNewEffectByName(effConf.getString("effect", ""));
if (effect != null) {
effect.loadFromConfiguration(effConf);
List<SpellEffect> e = effects.get(pos);
if (e == null) {
e = new ArrayList<SpellEffect>();
effects.put(pos, e);
}
e.add(effect);
}
}
}
}
}
// cost
reagents = getConfigReagents("cost");
if (reagents == null) reagents = new SpellReagents();
// cooldowns
this.cooldown = (float)config.getDouble(section + "." + spellName + ".cooldown", 0);
this.serverCooldown = (float)config.getDouble(section + "." + spellName + ".server-cooldown", 0);
this.rawSharedCooldowns = config.getStringList(section + "." + spellName + ".shared-cooldowns", null);
this.ignoreGlobalCooldown = config.getBoolean(section + "." + spellName + ".ignore-global-cooldown", false);
this.charges = config.getInt(section + "." + spellName + ".charges", 0);
this.rechargeSound = config.getString(section + "." + spellName + ".recharge-sound", "");
this.nextCast = new HashMap<String, Long>();
this.chargesConsumed = new IntMap<String>();
this.nextCastServer = 0;
// modifiers
this.modifierStrings = config.getStringList(section + "." + spellName + ".modifiers", null);
this.targetModifierStrings = config.getStringList(section + "." + spellName + ".target-modifiers", null);
// hierarchy options
this.prerequisites = config.getStringList(section + "." + spellName + ".prerequisites", null);
this.replaces = config.getStringList(section + "." + spellName + ".replaces", null);
this.precludes = config.getStringList(section + "." + spellName + ".precludes", null);
this.worldRestrictions = config.getStringList(section + "." + spellName + ".restrict-to-worlds", null);
List<String> sXpGranted = config.getStringList(section + "." + spellName + ".xp-granted", null);
List<String> sXpRequired = config.getStringList(section + "." + spellName + ".xp-required", null);
if (sXpGranted != null) {
xpGranted = new LinkedHashMap<String, Integer>();
for (String s : sXpGranted) {
String[] split = s.split(" ");
try {
int amt = Integer.parseInt(split[1]);
xpGranted.put(split[0], amt);
} catch (NumberFormatException e) {
MagicSpells.error("Error in xp-granted entry for spell '" + internalName + "': " + s);
}
}
}
if (sXpRequired != null) {
xpRequired = new LinkedHashMap<String, Integer>();
for (String s : sXpRequired) {
String[] split = s.split(" ");
try {
int amt = Integer.parseInt(split[1]);
xpRequired.put(split[0], amt);
} catch (NumberFormatException e) {
MagicSpells.error("Error in xp-required entry for spell '" + internalName + "': " + s);
}
}
}
// variable options
List<String> varModsCast = config.getStringList(section + "." + spellName + ".variable-mods-cast", null);
if (varModsCast != null && varModsCast.size() > 0) {
variableModsCast = new HashMap<String, Double>();
for (String s : varModsCast) {
try {
String[] data = s.split(" ");
String var = data[0];
double val = Double.parseDouble(data[1]);
variableModsCast.put(var, val);
} catch (Exception e) {
MagicSpells.error("Invalid variable-mods-cast option for spell '" + spellName + "': " + s);
}
}
}
List<String> varModsCasted = config.getStringList(section + "." + spellName + ".variable-mods-casted", null);
if (varModsCasted != null && varModsCasted.size() > 0) {
variableModsCasted = new HashMap<String, Double>();
for (String s : varModsCasted) {
try {
String[] data = s.split(" ");
String var = data[0];
double val = Double.parseDouble(data[1]);
variableModsCasted.put(var, val);
} catch (Exception e) {
MagicSpells.error("Invalid variable-mods-casted option for spell '" + spellName + "': " + s);
}
}
}
List<String> varModsTarget = config.getStringList(section + "." + spellName + ".variable-mods-target", null);
if (varModsTarget != null && varModsTarget.size() > 0) {
variableModsTarget = new HashMap<String, Double>();
for (String s : varModsTarget) {
try {
String[] data = s.split(" ");
String var = data[0];
double val = Double.parseDouble(data[1]);
variableModsTarget.put(var, val);
} catch (Exception e) {
MagicSpells.error("Invalid variable-mods-target option for spell '" + spellName + "': " + s);
}
}
}
this.soundOnCooldown = config.getString(section + "." + spellName + ".sound-on-cooldown", MagicSpells.plugin.soundFailOnCooldown);
if (this.soundOnCooldown != null && this.soundOnCooldown.isEmpty()) {
this.soundOnCooldown = null;
}
this.soundMissingReagents = config.getString(section + "." + spellName + ".sound-missing-reagents", MagicSpells.plugin.soundFailMissingReagents);
if (this.soundMissingReagents != null && this.soundMissingReagents.isEmpty()) {
this.soundMissingReagents = null;
}
// strings
this.strCost = config.getString(section + "." + spellName + ".str-cost", null);
this.strCastSelf = config.getString(section + "." + spellName + ".str-cast-self", null);
this.strCastOthers = config.getString(section + "." + spellName + ".str-cast-others", null);
this.strOnCooldown = config.getString(section + "." + spellName + ".str-on-cooldown", MagicSpells.plugin.strOnCooldown);
this.strMissingReagents = config.getString(section + "." + spellName + ".str-missing-reagents", MagicSpells.plugin.strMissingReagents);
this.strCantCast = config.getString(section + "." + spellName + ".str-cant-cast", MagicSpells.plugin.strCantCast);
this.strCantBind = config.getString(section + "." + spellName + ".str-cant-bind", null);
this.strWrongWorld = config.getString(section + "." + spellName + ".str-wrong-world", MagicSpells.plugin.strWrongWorld);
this.strWrongCastItem = config.getString(section + "." + spellName + ".str-wrong-cast-item", strCantCast);
this.strCastStart = config.getString(section + "." + spellName + ".str-cast-start", null);
this.strInterrupted = config.getString(section + "." + spellName + ".str-interrupted", null);
this.strModifierFailed = config.getString(section + "." + spellName + ".str-modifier-failed", null);
this.strXpAutoLearned = config.getString(section + "." + spellName + ".str-xp-auto-learned", MagicSpells.plugin.strXpAutoLearned);
if (this.strXpAutoLearned != null) {
strXpAutoLearned = strXpAutoLearned.replace("%s", this.name);
}
}
private EffectPosition getPositionFromString(String spos) {
if (spos.equalsIgnoreCase("start") || spos.equalsIgnoreCase("startcast")) {
return EffectPosition.START_CAST;
} else if (spos.equalsIgnoreCase("pos1") || spos.equalsIgnoreCase("position1") || spos.equalsIgnoreCase("caster") || spos.equalsIgnoreCase("actor")) {
return EffectPosition.CASTER;
} else if (spos.equalsIgnoreCase("pos2") || spos.equalsIgnoreCase("position2") || spos.equalsIgnoreCase("target")) {
return EffectPosition.TARGET;
} else if (spos.equalsIgnoreCase("line") || spos.equalsIgnoreCase("trail")) {
return EffectPosition.TRAIL;
} else if (spos.equalsIgnoreCase("disabled")) {
return EffectPosition.DISABLED;
} else if (spos.equalsIgnoreCase("delayed")) {
return EffectPosition.DELAYED;
} else if (spos.equalsIgnoreCase("special")) {
return EffectPosition.SPECIAL;
} else if (spos.equalsIgnoreCase("buff") || spos.equalsIgnoreCase("active")) {
return EffectPosition.BUFF;
} else if (spos.equalsIgnoreCase("orbit")) {
return EffectPosition.ORBIT;
}
return null;
}
protected SpellReagents getConfigReagents(String option) {
SpellReagents reagents = null;
List<String> costList = config.getStringList("spells." + internalName + "." + option, null);
if (costList != null && costList.size() > 0) {
reagents = new SpellReagents();
String[] data;
for (int i = 0; i < costList.size(); i++) {
String costVal = costList.get(i);
try {
// parse cost data
data = costVal.split(" ");
if (data[0].equalsIgnoreCase("health")) {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
reagents.setHealth(amt);
} else if (data[0].equalsIgnoreCase("mana")) {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
reagents.setMana(amt);
} else if (data[0].equalsIgnoreCase("hunger")) {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
reagents.setHunger(amt);
} else if (data[0].equalsIgnoreCase("experience")) {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
reagents.setExperience(amt);
} else if (data[0].equalsIgnoreCase("levels")) {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
reagents.setLevels(amt);
} else if (data[0].equalsIgnoreCase("durability")) {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
reagents.setDurability(amt);
} else if (data[0].equalsIgnoreCase("money")) {
float amt = 1;
if (data.length > 1) amt = Float.parseFloat(data[1]);
reagents.setMoney(amt);
} else if (data[0].equalsIgnoreCase("variable")) {
reagents.addVariable(data[1], Double.parseDouble(data[2]));
} else {
int amt = 1;
if (data.length > 1) amt = Integer.parseInt(data[1]);
ItemStack is = Util.getItemStackFromString(data[0]);
if (is != null) {
is.setAmount(amt);
reagents.addItem(is);
} else {
MagicSpells.error("Failed to process cost value for " + internalName + " spell: " + costVal);
}
}
} catch (Exception e) {
MagicSpells.error("Failed to process cost value for " + internalName + " spell: " + costVal);
}
}
}
return reagents;
}
/**
* This method is called immediately after all spells have been loaded.
*/
protected void initialize() {
// modifiers
if (modifierStrings != null && modifierStrings.size() > 0) {
debug(2, "Adding modifiers to " + internalName + " spell");
modifiers = new ModifierSet(modifierStrings);
modifierStrings = null;
}
if (targetModifierStrings != null && targetModifierStrings.size() > 0) {
debug(2, "Adding target modifiers to " + internalName + " spell");
targetModifiers = new ModifierSet(targetModifierStrings);
targetModifierStrings = null;
}
// process shared cooldowns
if (rawSharedCooldowns != null) {
this.sharedCooldowns = new HashMap<Spell,Float>();
for (String s : rawSharedCooldowns) {
String[] data = s.split(" ");
Spell spell = MagicSpells.getSpellByInternalName(data[0]);
float cd = Float.parseFloat(data[1]);
if (spell != null) {
this.sharedCooldowns.put(spell, cd);
}
}
rawSharedCooldowns.clear();
rawSharedCooldowns = null;
}
// register events
registerEvents();
// other processing
if (spellNameOnInterrupt != null && !spellNameOnInterrupt.isEmpty()) {
spellOnInterrupt = MagicSpells.getSpellByInternalName(spellNameOnInterrupt);
}
}
protected boolean configKeyExists(String key) {
return config.contains("spells." + internalName + "." + key);
}
/**
* Access an integer config value for this spell.
*
* @param key The key of the config value
* @param defaultValue The value to return if it does not exist in the config
*
* @return The config value, or defaultValue if it does not exist
*/
protected int getConfigInt(String key, int defaultValue) {
return config.getInt("spells." + internalName + "." + key, defaultValue);
}
/**
* Access a long config value for this spell.
*
* @param key The key of the config value
* @param defaultValue The value to return if it does not exist in the config
*
* @return The config value, or defaultValue if it does not exist
*/
protected long getConfigLong(String key, long defaultValue) {
return config.getLong("spells." + internalName + "." + key, defaultValue);
}
/**
* Access a boolean config value for this spell.
*
* @param key The key of the config value
* @param defaultValue The value to return if it does not exist in the config
*
* @return The config value, or defaultValue if it does not exist
*/
protected boolean getConfigBoolean(String key, boolean defaultValue) {
return config.getBoolean("spells." + internalName + "." + key, defaultValue);
}
/**
* Access a String config value for this spell.
*
* @param key The key of the config value
* @param defaultValue The value to return if it does not exist in the config
*
* @return The config value, or defaultValue if it does not exist
*/
protected String getConfigString(String key, String defaultValue) {
return config.getString("spells." + internalName + "." + key, defaultValue);
}
/**
* Access a float config value for this spell.
*
* @param key The key of the config value
* @param defaultValue The value to return if it does not exist in the config
*
* @return The config value, or defaultValue if it does not exist
*/
protected float getConfigFloat(String key, float defaultValue) {
return (float)config.getDouble("spells." + internalName + "." + key, defaultValue);
}
/**
* Access a double config value for this spell.
*
* @param key The key of the config value
* @param defaultValue The value to return if it does not exist in the config
*
* @return The config value, or defaultValue if it does not exist
*/
protected double getConfigDouble(String key, double defaultValue) {
return config.getDouble("spells." + internalName + "." + key, defaultValue);
}
protected List<Integer> getConfigIntList(String key, List<Integer> defaultValue) {
return config.getIntList("spells." + internalName + "." + key, defaultValue);
}
protected List<String> getConfigStringList(String key, List<String> defaultValue) {
return config.getStringList("spells." + internalName + "." + key, defaultValue);
}
protected Set<String> getConfigKeys(String key) {
return config.getKeys("spells." + internalName + "." + key);
}
protected ConfigurationSection getConfigSection(String key) {
return config.getSection("spells." + internalName + "." + key);
}
protected boolean isConfigString(String key) {
return config.isString("spells." + internalName + "." + key);
}
protected boolean isConfigSection(String key) {
return config.isSection("spells." + internalName + "." + key);
}
public final SpellCastResult cast(Player player) {
return cast(player, 1.0F, null);
}
public final SpellCastResult cast(Player player, String[] args) {
return cast(player, 1.0F, args);
}
public final SpellCastResult cast(Player player, float power, String[] args) {
SpellCastEvent spellCast = preCast(player, power, args);
if (spellCast == null) {
return new SpellCastResult(SpellCastState.CANT_CAST, PostCastAction.HANDLE_NORMALLY);
}
PostCastAction action;
int castTime = spellCast.getCastTime();
if (castTime <= 0 || spellCast.getSpellCastState() != SpellCastState.NORMAL) {
action = handleCast(spellCast);
} else if (!preCastTimeCheck(player, args)) {
action = PostCastAction.ALREADY_HANDLED;
} else {
action = PostCastAction.DELAYED;
sendMessage(player, strCastStart);
playSpellEffects(EffectPosition.START_CAST, player);
if (MagicSpells.plugin.useExpBarAsCastTimeBar) {
new DelayedSpellCastWithBar(spellCast);
} else {
new DelayedSpellCast(spellCast);
}
}
return new SpellCastResult(spellCast.getSpellCastState(), action);
}
protected SpellCastState getCastState(Player player) {
if (!MagicSpells.getSpellbook(player).canCast(this)) {
return SpellCastState.CANT_CAST;
} else if (worldRestrictions != null && !worldRestrictions.contains(player.getWorld().getName())) {
return SpellCastState.WRONG_WORLD;
} else if (MagicSpells.plugin.noMagicZones != null && MagicSpells.plugin.noMagicZones.willFizzle(player, this)) {
return SpellCastState.NO_MAGIC_ZONE;
} else if (onCooldown(player)) {
return SpellCastState.ON_COOLDOWN;
} else if (!hasReagents(player)) {
return SpellCastState.MISSING_REAGENTS;
} else {
return SpellCastState.NORMAL;
}
}
protected SpellCastEvent preCast(Player player, float power, String[] args) {
// get spell state
SpellCastState state = getCastState(player);
debug(2, " Spell cast state: " + state);
// call events
SpellCastEvent event = new SpellCastEvent(this, player, state, power, args, cooldown, reagents.clone(), castTime);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
debug(2, " Spell canceled");
return null;
} else {
if (event.haveReagentsChanged()) {
boolean hasReagents = hasReagents(player, event.getReagents());
if (!hasReagents && state != SpellCastState.MISSING_REAGENTS) {
event.setSpellCastState(SpellCastState.MISSING_REAGENTS);
debug(2, " Spell cast state changed: " + state);
} else if (hasReagents && state == SpellCastState.MISSING_REAGENTS) {
event.setSpellCastState(state = SpellCastState.NORMAL);
debug(2, " Spell cast state changed: " + state);
}
}
if (event.hasSpellCastStateChanged()) {
debug(2, " Spell cast state changed: " + state);
}
}
if (player.hasPermission("magicspells.nocasttime")) {
event.setCastTime(0);
}
return event;
}
private PostCastAction handleCast(SpellCastEvent spellCast) {
long start = System.nanoTime();
Player player = spellCast.getCaster();
SpellCastState state = spellCast.getSpellCastState();
String[] args = spellCast.getSpellArgs();
float power = spellCast.getPower();
debug(3, " Power: " + power);
debug(3, " Cooldown: " + cooldown);
if (MagicSpells.plugin.debug && args != null && args.length > 0) {
debug(3, " Args: {" + Util.arrayJoin(args, ',') + "}");
}
PostCastAction action = castSpell(player, state, power, args);
if (MagicSpells.plugin.enableProfiling) {
Long total = MagicSpells.plugin.profilingTotalTime.get(profilingKey);
if (total == null) total = (long)0;
total += (System.nanoTime() - start);
MagicSpells.plugin.profilingTotalTime.put(profilingKey, total);
Integer runs = MagicSpells.plugin.profilingRuns.get(profilingKey);
if (runs == null) runs = 0;
runs += 1;
MagicSpells.plugin.profilingRuns.put(profilingKey, runs);
}
postCast(spellCast, action);
return action;
}
protected void postCast(SpellCastEvent spellCast, PostCastAction action) {
debug(3, " Post-cast action: " + action);
Player player = spellCast.getCaster();
SpellCastState state = spellCast.getSpellCastState();
if (action != null && action != PostCastAction.ALREADY_HANDLED) {
if (state == SpellCastState.NORMAL) {
if (action == PostCastAction.HANDLE_NORMALLY || action == PostCastAction.COOLDOWN_ONLY || action == PostCastAction.NO_MESSAGES || action == PostCastAction.NO_REAGENTS) {
setCooldown(player, spellCast.getCooldown());
}
if (action == PostCastAction.HANDLE_NORMALLY || action == PostCastAction.REAGENTS_ONLY || action == PostCastAction.NO_MESSAGES || action == PostCastAction.NO_COOLDOWN) {
removeReagents(player, spellCast.getReagents());
}
if (action == PostCastAction.HANDLE_NORMALLY || action == PostCastAction.MESSAGES_ONLY || action == PostCastAction.NO_COOLDOWN || action == PostCastAction.NO_REAGENTS) {
sendMessages(player);
}
if (experience > 0) {
player.giveExp(experience);
}
} else if (state == SpellCastState.ON_COOLDOWN) {
MagicSpells.sendMessage(player, formatMessage(strOnCooldown, "%c", Math.round(getCooldown(player))+""));
if (soundOnCooldown != null) {
MagicSpells.getVolatileCodeHandler().playSound(player, soundOnCooldown, 1f, 1f);
}
} else if (state == SpellCastState.MISSING_REAGENTS) {
MagicSpells.sendMessage(player, strMissingReagents);
if (MagicSpells.plugin.showStrCostOnMissingReagents && strCost != null && !strCost.isEmpty()) {
MagicSpells.sendMessage(player, " (" + strCost + ")");
}
if (soundMissingReagents != null) {
MagicSpells.getVolatileCodeHandler().playSound(player, soundMissingReagents, 1f, 1f);
}
} else if (state == SpellCastState.CANT_CAST) {
MagicSpells.sendMessage(player, strCantCast);
} else if (state == SpellCastState.NO_MAGIC_ZONE) {
MagicSpells.plugin.noMagicZones.sendNoMagicMessage(player, this);
} else if (state == SpellCastState.WRONG_WORLD) {
MagicSpells.sendMessage(player, strWrongWorld);
}
}
SpellCastedEvent event = new SpellCastedEvent(this, player, state, spellCast.getPower(), spellCast.getSpellArgs(), cooldown, reagents, action);
Bukkit.getPluginManager().callEvent(event);
}
public void sendMessages(Player player) {
sendMessage(player, strCastSelf, "%a", player.getDisplayName());
sendMessageNear(player, formatMessage(strCastOthers, "%a", player.getDisplayName()));
}
protected boolean preCastTimeCheck(Player player, String[] args) {
return true;
}
/**
* This method is called when a player casts a spell, either by command, with a wand item, or otherwise.
* @param player the player casting the spell
* @param state the state of the spell cast (normal, on cooldown, missing reagents, etc)
* @param power the power multiplier the spell should be cast with (1.0 is normal)
* @param args the spell arguments, if cast by command
* @return the action to take after the spell is processed
*/
public abstract PostCastAction castSpell(Player player, SpellCastState state, float power, String[] args);
public List<String> tabComplete(CommandSender sender, String partial) {
return null;
}
protected List<String> tabCompletePlayerName(CommandSender sender, String partial) {
ArrayList<String> matches = new ArrayList<String>();
partial = partial.toLowerCase();
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.getName().toLowerCase().startsWith(partial)) {
if (sender.isOp() || !(sender instanceof Player) || ((Player)sender).canSee(p)) {
matches.add(p.getName());
}
}
}
if (matches.size() > 0) {
return matches;
} else {
return null;
}
}
protected List<String> tabCompleteSpellName(CommandSender sender, String partial) {
return Util.tabCompleteSpellName(sender, partial);
}
/**
* This method is called when the spell is cast from the console.
* @param sender the console sender.
* @param args the command arguments
* @return true if the spell was handled, false otherwise
*/
public boolean castFromConsole(CommandSender sender, String[] args) {
return false;
}
public abstract boolean canCastWithItem();
public abstract boolean canCastByCommand();
public boolean canCastWithLeftClick() {
return castWithLeftClick;
}
public boolean canCastWithRightClick() {
return castWithRightClick;
}
public boolean isAlwaysGranted() {
return alwaysGranted;
}
public boolean isValidItemForCastCommand(ItemStack item) {
if (!requireCastItemOnCommand || castItems == null) {
return true;
} else if (item == null && castItems.length == 1 && castItems[0].getItemTypeId() == 0) {
return true;
} else {
for (CastItem castItem : castItems) {
if (castItem.equals(item)) {
return true;
}
}
return false;
}
}
public boolean canBind(CastItem item) {
if (!bindable) {
return false;
} else if (bindableItems == null) {
return true;
} else {
return bindableItems.contains(item);
}
}
public ItemStack getSpellIcon() {
return spellIcon;
}
public String getCostStr() {
if (strCost == null || strCost.equals("")) {
return null;
} else {
return strCost;
}
}
/**
* Check whether this spell is currently on cooldown for the specified player
* @param player The player to check
* @return whether the spell is on cooldown
*/
public boolean onCooldown(Player player) {
if (player.hasPermission("magicspells.nocooldown")) {
return false;
}
if (charges > 0) {
return chargesConsumed.get(player.getName()) >= charges;
}
if (serverCooldown > 0 && nextCastServer > System.currentTimeMillis()) {
return true;
}
Long next = nextCast.get(player.getName());
if (next != null) {
if (next > System.currentTimeMillis()) {
return true;
}
}
return false;
}
public float getCooldown() {
return cooldown;
}
/**
* Get how many seconds remain on the cooldown of this spell for the specified player
* @param player The player to check
* @return The number of seconds remaining in the cooldown
*/
public float getCooldown(Player player) {
if (charges > 0) return -1;
float cd = 0;
Long next = nextCast.get(player.getName());
if (next != null) {
float c = (next - System.currentTimeMillis()) / 1000F;
cd = c > 0 ? c : 0;
}
if (serverCooldown > 0 && nextCastServer > System.currentTimeMillis()) {
float c = (nextCastServer - System.currentTimeMillis()) / 1000F;
if (c > cd) cd = c;
}
return cd;
}
/**
* Begins the cooldown for the spell for the specified player
* @param player The player to set the cooldown for
*/
public void setCooldown(Player player, float cooldown) {
setCooldown(player, cooldown, true);
}
/**
* Begins the cooldown for the spell for the specified player
* @param player The player to set the cooldown for
*/
public void setCooldown(final Player player, float cooldown, boolean activateSharedCooldowns) {
if (cooldown > 0) {
if (charges <= 0) {
nextCast.put(player.getName(), System.currentTimeMillis() + (long)(cooldown * 1000L));
} else {
final String name = player.getName();
chargesConsumed.increment(name);
MagicSpells.scheduleDelayedTask(new Runnable() {
public void run() {
chargesConsumed.decrement(name);
if (rechargeSound != null && !rechargeSound.isEmpty()) {
MagicSpells.getVolatileCodeHandler().playSound(player, rechargeSound, 1.0F, 1.0F);
}
}
}, Math.round(20F * cooldown));
}
} else {
if (charges <= 0) {
nextCast.remove(player.getName());
} else {
chargesConsumed.remove(player.getName());
}
}
if (serverCooldown > 0) {
nextCastServer = System.currentTimeMillis() + (long)(serverCooldown * 1000L);
}
if (activateSharedCooldowns && sharedCooldowns != null) {
for (Map.Entry<Spell, Float> scd : sharedCooldowns.entrySet()) {
scd.getKey().setCooldown(player, scd.getValue(), false);
}
}
}
/**
* Checks if a player has the reagents required to cast this spell
* @param player the player to check
* @return true if the player has the reagents, false otherwise
*/
protected boolean hasReagents(Player player) {
return hasReagents(player, reagents);
}
/**
* Checks if a player has the reagents required to cast this spell
* @param player the player to check
* @param reagents the reagents to check for
* @return true if the player has the reagents, false otherwise
*/
protected boolean hasReagents(Player player, SpellReagents reagents) {
if (reagents == null) return true;
return hasReagents(player, reagents.getItemsAsArray(), reagents.getHealth(), reagents.getMana(), reagents.getHunger(), reagents.getExperience(), reagents.getLevels(), reagents.getDurability(), reagents.getMoney(), reagents.getVariables());
}
/**
* Checks if a player has the specified reagents, including health and mana
* @param player the player to check
* @param reagents the inventory item reagents to look for
* @param healthCost the health cost, in half-hearts
* @param manaCost the mana cost
* @return true if the player has all the reagents, false otherwise
*/
private boolean hasReagents(Player player, ItemStack[] reagents, int healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map<String, Double> variables) {
if (player.hasPermission("magicspells.noreagents")) {
return true;
}
if (healthCost > 0 && player.getHealth() <= healthCost) {
return false;
}
if (manaCost > 0 && (MagicSpells.plugin.mana == null || !MagicSpells.plugin.mana.hasMana(player, manaCost))) {
return false;
}
if (hungerCost > 0 && player.getFoodLevel() < hungerCost) {
return false;
}
if (experienceCost > 0 && !ExperienceUtils.hasExp(player, experienceCost)) {
return false;
}
if (levelsCost > 0 && player.getLevel() < levelsCost) {
return false;
}
if (durabilityCost > 0) {
ItemStack inHand = player.getItemInHand();
if (inHand == null || inHand.getDurability() >= inHand.getType().getMaxDurability()) {
return false;
}
}
if (moneyCost > 0) {
MoneyHandler moneyHandler = MagicSpells.getMoneyHandler();
if (moneyHandler == null || !moneyHandler.hasMoney(player, moneyCost)) {
return false;
}
}
if (reagents != null) {
for (ItemStack item : reagents) {
if (item != null && !inventoryContains(player.getInventory(), item)) {
return false;
}
}
}
if (variables != null) {
VariableManager varMan = MagicSpells.getVariableManager();
if (varMan == null) return false;
for (String var : variables.keySet()) {
double val = variables.get(var);
if (val > 0 && varMan.getValue(var, player) < val) {
return false;
}
}
}
return true;
}
/**
* Removes the reagent cost of this spell from the player's inventoryy.
* This does not check if the player has the reagents, use hasReagents() for that.
* @param player the player to remove reagents from
*/
protected void removeReagents(Player player) {
removeReagents(player, reagents);
}
/**
* Removes the specified reagents from the player's inventoryy.
* This does not check if the player has the reagents, use hasReagents() for that.
* @param player the player to remove the reagents from
* @param reagents the inventory item reagents to remove
*/
protected void removeReagents(Player player, ItemStack[] reagents) {
removeReagents(player, reagents, 0, 0, 0, 0, 0, 0, 0, null);
}
protected void removeReagents(Player player, SpellReagents reagents) {
removeReagents(player, reagents.getItemsAsArray(), reagents.getHealth(), reagents.getMana(), reagents.getHunger(), reagents.getExperience(), reagents.getLevels(), reagents.getDurability(), reagents.getMoney(), reagents.getVariables());
}
/**
* Removes the specified reagents, including health and mana, from the player's inventory.
* This does not check if the player has the reagents, use hasReagents() for that.
* @param player the player to remove the reagents from
* @param reagents the inventory item reagents to remove
* @param healthCost the health to remove
* @param manaCost the mana to remove
*/
private void removeReagents(Player player, ItemStack[] reagents, int healthCost, int manaCost, int hungerCost, int experienceCost, int levelsCost, int durabilityCost, float moneyCost, Map<String, Double> variables) {
if (player.hasPermission("magicspells.noreagents")) {
return;
}
if (reagents != null) {
for (ItemStack item : reagents) {
if (item != null) {
Util.removeFromInventory(player.getInventory(), item);
}
}
}
if (healthCost != 0) {
double h = player.getHealth() - healthCost;
if (h < 0) h = 0;
if (h > player.getMaxHealth()) h = player.getMaxHealth();
player.setHealth(h);
}
if (manaCost != 0) {
MagicSpells.plugin.mana.addMana(player, -manaCost, ManaChangeReason.SPELL_COST);
}
if (hungerCost != 0) {
int f = player.getFoodLevel() - hungerCost;
if (f < 0) f = 0;
if (f > 20) f = 20;
player.setFoodLevel(f);
}
if (experienceCost != 0) {
ExperienceUtils.changeExp(player, -experienceCost);
}
if (durabilityCost != 0) {
ItemStack inHand = player.getItemInHand();
if (inHand != null && inHand.getType().getMaxDurability() > 0) {
short newDura = (short) (inHand.getDurability() + durabilityCost);
if (newDura < 0) newDura = 0;
if (newDura >= inHand.getType().getMaxDurability()) {
player.setItemInHand(null);
} else {
inHand.setDurability(newDura);
player.setItemInHand(inHand);
}
}
}
if (moneyCost != 0) {
MoneyHandler moneyHandler = MagicSpells.getMoneyHandler();
if (moneyHandler != null) {
if (moneyCost > 0) {
moneyHandler.removeMoney(player, moneyCost);
} else {
moneyHandler.addMoney(player, moneyCost);
}
}
}
if (levelsCost != 0) {
int lvl = player.getLevel() - levelsCost;
if (lvl < 0) lvl = 0;
player.setLevel(lvl);
}
if (variables != null) {
VariableManager varMan = MagicSpells.getVariableManager();
if (varMan != null) {
for (String var : variables.keySet()) {
varMan.modify(var, player, -variables.get(var));
}
}
}
}
private boolean inventoryContains(Inventory inventory, ItemStack item) {
int count = 0;
ItemStack[] items = inventory.getContents();
for (int i = 0; i < 36; i++) {
if (items[i] != null && item.isSimilar(items[i])) {
count += items[i].getAmount();
}
if (count >= item.getAmount()) {
return true;
}
}
return false;
}
/*private void removeFromInventory(Inventory inventory, ItemStack item) {
int amt = item.getAmount();
ItemStack[] items = inventory.getContents();
for (int i = 0; i < items.length; i++) {
if (items[i] != null && item.isSimilar(items[i])) {
if (items[i].getAmount() > amt) {
items[i].setAmount(items[i].getAmount() - amt);
break;
} else if (items[i].getAmount() == amt) {
items[i] = null;
break;
} else {
amt -= items[i].getAmount();
items[i] = null;
}
}
}
inventory.setContents(items);
}*/
protected int getRange(float power) {
return spellPowerAffectsRange ? Math.round(range * power) : range;
}
/**
* Gets the player a player is currently looking at, ignoring other living entities
* @param player the player to get the target for
* @param range the maximum range to check
* @param checkLos whether to obey line-of-sight restrictions
* @return the targeted Player, or null if none was found
*/
protected TargetInfo<Player> getTargetedPlayer(Player player, float power) {
TargetInfo<LivingEntity> target = getTargetedEntity(player, power, true, null);
if (target != null && target.getTarget() instanceof Player) {
return new TargetInfo<Player>((Player)target.getTarget(), target.getPower());
} else {
return null;
}
}
protected TargetInfo<Player> getTargetPlayer(Player player, float power) {
return getTargetedPlayer(player, power);
}
protected TargetInfo<LivingEntity> getTargetedEntity(Player player, float power) {
return getTargetedEntity(player, power, false, null);
}
protected TargetInfo<LivingEntity> getTargetedEntity(Player player, float power, ValidTargetChecker checker) {
return getTargetedEntity(player, power, false, checker);
}
protected TargetInfo<LivingEntity> getTargetedEntity(Player player, float power, boolean forceTargetPlayers, ValidTargetChecker checker) {
// get nearby entities
int range = getRange(power);
List<Entity> ne = player.getNearbyEntities(range, range, range);
// get valid targets
List<LivingEntity> entities;
if (MagicSpells.plugin.checkWorldPvpFlag && validTargetList.canTargetPlayers() && !isBeneficial() && !player.getWorld().getPVP()) {
entities = validTargetList.filterTargetList(player, ne, false);
} else if (forceTargetPlayers) {
entities = validTargetList.filterTargetList(player, ne, true);
} else {
entities = validTargetList.filterTargetList(player, ne);
}
// find target
LivingEntity target = null;
BlockIterator bi;
try {
bi = new BlockIterator(player, range);
} catch (IllegalStateException e) {
return null;
}
Block b;
Location l;
int bx, by, bz;
double ex, ey, ez;
// do min range
for (int i = 0; i < minRange && bi.hasNext(); i++) {
bi.next();
}
// loop through player's line of sight
while (bi.hasNext()) {
b = bi.next();
bx = b.getX();
by = b.getY();
bz = b.getZ();
if (obeyLos && !BlockUtils.isTransparent(this, b)) {
// line of sight is broken, stop without target
break;
} else {
// check for entities near this block in the line of sight
for (LivingEntity e : entities) {
l = e.getLocation();
ex = l.getX();
ey = l.getY();
ez = l.getZ();
if ((bx-.75 <= ex && ex <= bx+1.75) && (bz-.75 <= ez && ez <= bz+1.75) && (by-1 <= ey && ey <= by+2.5)) {
// entity is close enough, set target and stop
target = e;
// check for invalid target
if (target != null && target instanceof Player && ((Player)target).getGameMode() == GameMode.CREATIVE) {
target = null;
continue;
}
// check for anti-magic-zone
if (target != null && MagicSpells.getNoMagicZoneManager() != null && MagicSpells.getNoMagicZoneManager().willFizzle(target.getLocation(), this)) {
target = null;
continue;
}
// check for teams
if (target != null && target instanceof Player && MagicSpells.plugin.checkScoreboardTeams) {
Scoreboard scoreboard = Bukkit.getScoreboardManager().getMainScoreboard();
Team playerTeam = scoreboard.getPlayerTeam(player);
Team targetTeam = scoreboard.getPlayerTeam((Player)target);
if (playerTeam != null && targetTeam != null) {
if (playerTeam.equals(targetTeam)) {
if (!playerTeam.allowFriendlyFire() && !this.isBeneficial()) {
target = null;
continue;
}
} else {
if (this.isBeneficial()) {
target = null;
continue;
}
}
}
}
// call event listeners
if (target != null) {
SpellTargetEvent event = new SpellTargetEvent(this, player, target, power);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
target = null;
continue;
} else {
target = event.getTarget();
power = event.getPower();
}
}
// call damage event
if (targetDamageCause != null) {
EntityDamageByEntityEvent event = new EntityDamageByEntityEvent(player, target, targetDamageCause, targetDamageAmount);
Bukkit.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
target = null;
continue;
}
}
// run checker
if (target != null && checker != null) {
if (!checker.isValidTarget(target)) {
target = null;
continue;
}
}
return new TargetInfo<LivingEntity>(target, power);
}
}
}
}
return null;
}
protected Block getTargetedBlock(LivingEntity entity, float power) {
return BlockUtils.getTargetBlock(this, entity, spellPowerAffectsRange ? Math.round(range * power) : range);
}
protected List<Block> getLastTwoTargetedBlocks(LivingEntity entity, float power) {
return BlockUtils.getLastTwoTargetBlock(this, entity, spellPowerAffectsRange ? Math.round(range * power) : range);
}
public HashSet<Byte> getLosTransparentBlocks() {
return losTransparentBlocks;
}
public boolean isTransparent(Block block) {
return losTransparentBlocks.contains((byte)block.getTypeId());
}
protected void playSpellEffects(Entity pos1, Entity pos2) {
playSpellEffects(EffectPosition.CASTER, pos1);
playSpellEffects(EffectPosition.TARGET, pos2);
playSpellEffectsTrail(pos1.getLocation(), pos2.getLocation());
}
protected void playSpellEffects(Entity pos1, Location pos2) {
playSpellEffects(EffectPosition.CASTER, pos1);
playSpellEffects(EffectPosition.TARGET, pos2);
playSpellEffectsTrail(pos1.getLocation(), pos2);
}
protected void playSpellEffects(Location pos1, Entity pos2) {
playSpellEffects(EffectPosition.CASTER, pos1);
playSpellEffects(EffectPosition.TARGET, pos2);
playSpellEffectsTrail(pos1, pos2.getLocation());
}
protected void playSpellEffects(Location pos1, Location pos2) {
playSpellEffects(EffectPosition.CASTER, pos1);
playSpellEffects(EffectPosition.TARGET, pos2);
playSpellEffectsTrail(pos1, pos2);
}
protected void playSpellEffects(EffectPosition pos, Entity entity) {
if (effects != null) {
List<SpellEffect> effectsList = effects.get(pos);
if (effectsList != null) {
for (SpellEffect effect : effectsList) {
effect.playEffect(entity);
}
}
}
}
protected void playSpellEffects(EffectPosition pos, Location location) {
if (effects != null) {
List<SpellEffect> effectsList = effects.get(pos);
if (effectsList != null) {
for (SpellEffect effect : effectsList) {
effect.playEffect(location);
}
}
}
}
protected void playSpellEffectsTrail(Location loc1, Location loc2) {
if (effects != null) {
List<SpellEffect> effectsList = effects.get(EffectPosition.TRAIL);
if (effectsList != null) {
for (SpellEffect effect : effectsList) {
System.out.println("yes?");
effect.playEffect(loc1, loc2);
}
}
}
}
protected void playSpellEffectsBuff(Entity entity, SpellEffect.SpellEffectActiveChecker checker) {
if (effects != null) {
List<SpellEffect> effectsList = effects.get(EffectPosition.BUFF);
if (effectsList != null) {
for (SpellEffect effect : effectsList) {
effect.playEffectWhileActiveOnEntity(entity, checker);
}
}
effectsList = effects.get(EffectPosition.ORBIT);
if (effectsList != null) {
for (SpellEffect effect : effectsList) {
effect.playEffectWhileActiveOrbit(entity, checker);
}
}
}
}
protected void registerEvents() {
registerEvents(this);
}
protected void registerEvents(Listener listener) {
MagicSpells.registerEvents(listener);
}
protected void unregisterEvents() {
unregisterEvents(this);
}
protected void unregisterEvents(Listener listener) {
HandlerList.unregisterAll(listener);
}
protected int scheduleDelayedTask(Runnable task, int delay) {
return MagicSpells.scheduleDelayedTask(task, delay);
}
protected int scheduleRepeatingTask(Runnable task, int delay, int interval) {
return MagicSpells.scheduleRepeatingTask(task, delay, interval);
}
/**
* Formats a string by performing the specified replacements.
* @param message the string to format
* @param replacements the replacements to make, in pairs.
* @return the formatted string
*/
protected String formatMessage(String message, String... replacements) {
return MagicSpells.formatMessage(message, replacements);
}
/**
* Sends a message to a player, first making the specified replacements. This method also does color replacement and has multi-line functionality.
* @param player the player to send the message to
* @param message the message to send
* @param replacements the replacements to be made, in pairs
*/
protected void sendMessage(Player player, String message, String... replacements) {
sendMessage(player, formatMessage(message, replacements));
}
/**
* Sends a message to a player. This method also does color replacement and has multi-line functionality.
* @param player the player to send the message to
* @param message the message to send
*/
protected void sendMessage(Player player, String message) {
MagicSpells.sendMessage(player, message);
}
/**
* Sends a message to all players near the specified player, within the configured broadcast range.
* @param player the "center" player used to find nearby players
* @param message the message to send
*/
protected void sendMessageNear(Player player, String message) {
sendMessageNear(player, null, message, broadcastRange);
}
/**
* Sends a message to all players near the specified player, within the specified broadcast range.
* @param player the "center" player used to find nearby players
* @param message the message to send
* @param range the broadcast range
*/
protected void sendMessageNear(Player player, Player ignore, String message, int range) {
if (message != null && !message.equals("") && !player.hasPermission("magicspells.silent")) {
String [] msgs = message.replaceAll("&([0-9a-f])", "\u00A7$1").split("\n");
List<Entity> entities = player.getNearbyEntities(range*2, range*2, range*2);
for (Entity entity : entities) {
if (entity instanceof Player && entity != player && entity != ignore) {
for (String msg : msgs) {
if (!msg.equals("")) {
((Player)entity).sendMessage(MagicSpells.plugin.textColor + msg);
}
}
}
}
}
}
public String getInternalName() {
return this.internalName;
}
public String getName() {
if (this.name != null && !this.name.isEmpty()) {
return this.name;
} else {
return this.internalName;
}
}
public String getPermissionName() {
return permName;
}
public boolean isHelperSpell() {
return helperSpell;
}
public String getCantBindError() {
return strCantBind;
}
public String[] getAliases() {
return this.aliases;
}
public List<String> getIncantations() {
return this.incantations;
}
public CastItem getCastItem() {
if (this.castItems.length == 1) {
return this.castItems[0];
} else {
return null;
}
}
public CastItem[] getCastItems() {
return this.castItems;
}
public CastItem[] getRightClickCastItems() {
return this.rightClickCastItems;
}
public CastItem[] getConsumeCastItems() {
return this.consumeCastItems;
}
public String getDanceCastSequence() {
return this.danceCastSequence;
}
public String getDescription() {
return this.description;
}
public SpellReagents getReagents() {
return this.reagents;
}
public String getConsoleName() {
return MagicSpells.plugin.strConsoleName;
}
public String getStrWrongCastItem() {
return strWrongCastItem;
}
public final boolean isBeneficial() {
return beneficial;
}
public boolean isBeneficialDefault() {
return false;
}
public ModifierSet getModifiers() {
return modifiers;
}
public ModifierSet getTargetModifiers() {
return targetModifiers;
}
public String getStrModifierFailed() {
return strModifierFailed;
}
public Map<String, Integer> getXpGranted() {
return xpGranted;
}
public Map<String, Integer> getXpRequired() {
return xpRequired;
}
public String getStrXpLearned() {
return strXpAutoLearned;
}
Map<String, Long> getCooldowns() {
return nextCast;
}
public Map<String, Double> getVariableModsCast() {
return variableModsCast;
}
public Map<String, Double> getVariableModsCasted() {
return variableModsCasted;
}
public Map<String, Double> getVariableModsTarget() {
return variableModsTarget;
}
void setCooldownManually(String name, long nextCast) {
this.nextCast.put(name, nextCast);
}
protected void debug(int level, String message) {
if (debug) MagicSpells.debug(level, message);
}
/**
* This method is called when the plugin is being disabled, for any reason.
*/
protected void turnOff() {
}
@Override
public int compareTo(Spell spell) {
return this.name.compareTo(spell.name);
}
@Override
public boolean equals(Object o) {
if (o instanceof Spell && ((Spell)o).internalName.equals(this.internalName)) {
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
return internalName.hashCode();
}
public enum SpellCastState {
NORMAL,
ON_COOLDOWN,
MISSING_REAGENTS,
CANT_CAST,
NO_MAGIC_ZONE,
WRONG_WORLD
}
public enum PostCastAction {
HANDLE_NORMALLY(true, true, true),
ALREADY_HANDLED(false, false, false),
NO_MESSAGES(true, true, false),
NO_REAGENTS(true, false, true),
NO_COOLDOWN(false, true, true),
MESSAGES_ONLY(false, false, true),
REAGENTS_ONLY(false, true, false),
COOLDOWN_ONLY(true, false, false),
DELAYED(false, false, false);
private boolean cooldown;
private boolean reagents;
private boolean messages;
private PostCastAction(boolean cooldown, boolean reagents, boolean messages) {
this.cooldown = cooldown;
this.reagents = reagents;
this.messages = messages;
}
public boolean setCooldown() {
return cooldown;
}
public boolean chargeReagents() {
return reagents;
}
public boolean sendMessages() {
return messages;
}
}
public class SpellCastResult {
public SpellCastState state;
public PostCastAction action;
public SpellCastResult(SpellCastState state, PostCastAction action) {
this.state = state;
this.action = action;
}
}
public class DelayedSpellCast implements Runnable, Listener {
private Player player;
private Location prevLoc;
private Spell spell;
private SpellCastEvent spellCast;
private int taskId;
private boolean cancelled = false;
public DelayedSpellCast(SpellCastEvent spellCast) {
this.player = spellCast.getCaster();
this.prevLoc = player.getLocation().clone();
this.spell = spellCast.getSpell();
this.spellCast = spellCast;
taskId = scheduleDelayedTask(this, spellCast.getCastTime());
registerEvents(this);
}
@Override
public void run() {
if (!cancelled && player.isOnline() && !player.isDead()) {
Location currLoc = player.getLocation();
if (!interruptOnMove || (Math.abs(currLoc.getX() - prevLoc.getX()) < .2 && Math.abs(currLoc.getY() - prevLoc.getY()) < .2 && Math.abs(currLoc.getZ() - prevLoc.getZ()) < .2)) {
if (!spell.hasReagents(player, reagents)) {
spellCast.setSpellCastState(SpellCastState.MISSING_REAGENTS);
}
spell.handleCast(spellCast);
} else {
interrupt();
}
}
unregisterEvents(this);
}
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onDamage(EntityDamageEvent event) {
if (interruptOnDamage && !cancelled && event.getEntity().equals(player)) {
cancelled = true;
Bukkit.getScheduler().cancelTask(taskId);
interrupt();
}
}
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onSpellCast(SpellCastEvent event) {
if (interruptOnCast && !cancelled && !(event.getSpell() instanceof PassiveSpell) && event.getCaster().equals(player)) {
cancelled = true;
Bukkit.getScheduler().cancelTask(taskId);
interrupt();
}
}
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onTeleport(PlayerTeleportEvent event) {
if (interruptOnTeleport && !cancelled && event.getPlayer().equals(player)) {
cancelled = true;
Bukkit.getScheduler().cancelTask(taskId);
interrupt();
}
}
private void interrupt() {
sendMessage(player, strInterrupted);
if (spellOnInterrupt != null) {
spellOnInterrupt.castSpell(player, SpellCastState.NORMAL, spellCast.getPower(), null);
}
}
}
public class DelayedSpellCastWithBar implements Runnable, Listener {
private Player player;
private Location prevLoc;
private Spell spell;
private SpellCastEvent spellCast;
private int castTime;
private int taskId;
private boolean cancelled = false;
private int interval = 5;
private int elapsed = 0;
public DelayedSpellCastWithBar(SpellCastEvent spellCast) {
this.player = spellCast.getCaster();
this.prevLoc = player.getLocation().clone();
this.spell = spellCast.getSpell();
this.spellCast = spellCast;
this.castTime = spellCast.getCastTime();
MagicSpells.getExpBarManager().lock(player, this);
taskId = scheduleRepeatingTask(this, interval, interval);
registerEvents(this);
}
@Override
public void run() {
if (!cancelled && player.isOnline() && !player.isDead()) {
elapsed += interval;
Location currLoc = player.getLocation();
if (!interruptOnMove || (Math.abs(currLoc.getX() - prevLoc.getX()) < .2 && Math.abs(currLoc.getY() - prevLoc.getY()) < .2 && Math.abs(currLoc.getZ() - prevLoc.getZ()) < .2)) {
if (elapsed >= castTime) {
if (!spell.hasReagents(player, reagents)) {
spellCast.setSpellCastState(SpellCastState.MISSING_REAGENTS);
}
spell.handleCast(spellCast);
cancelled = true;
}
MagicSpells.getExpBarManager().update(player, 0, ((float)elapsed / (float)castTime), this);
} else {
interrupt();
}
} else {
end();
}
}
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onDamage(EntityDamageEvent event) {
if (interruptOnDamage && !cancelled && event.getEntity().equals(player)) {
cancelled = true;
interrupt();
}
}
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onSpellCast(SpellCastEvent event) {
if (interruptOnCast && !cancelled && !(event.getSpell() instanceof PassiveSpell) && event.getCaster() != null && event.getCaster().equals(player)) {
cancelled = true;
interrupt();
}
}
@EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true)
public void onTeleport(PlayerTeleportEvent event) {
if (interruptOnTeleport && !cancelled && event.getPlayer().equals(player)) {
cancelled = true;
interrupt();
}
}
private void interrupt() {
sendMessage(player, strInterrupted);
end();
if (spellOnInterrupt != null) {
spellOnInterrupt.castSpell(player, SpellCastState.NORMAL, spellCast.getPower(), null);
}
}
private void end() {
cancelled = true;
Bukkit.getScheduler().cancelTask(taskId);
unregisterEvents(this);
MagicSpells.getExpBarManager().unlock(player, this);
MagicSpells.getExpBarManager().update(player, player.getLevel(), player.getExp());
ManaHandler mana = MagicSpells.getManaHandler();
if (mana != null) {
mana.showMana(player);
}
}
}
public ValidTargetChecker getValidTargetChecker() {
return null;
}
public interface ValidTargetChecker {
public boolean isValidTarget(LivingEntity entity);
}
}