package com.nisovin.magicspells;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.nisovin.magicspells.castmodifiers.ModifierSet;
import com.nisovin.magicspells.events.MagicSpellsLoadedEvent;
import com.nisovin.magicspells.events.MagicSpellsLoadingEvent;
import com.nisovin.magicspells.events.SpellLearnEvent;
import com.nisovin.magicspells.events.SpellLearnEvent.LearnSource;
import com.nisovin.magicspells.mana.ManaHandler;
import com.nisovin.magicspells.mana.ManaSystem;
import com.nisovin.magicspells.materials.ItemNameResolver;
import com.nisovin.magicspells.materials.MagicItemNameResolver;
import com.nisovin.magicspells.spells.PassiveSpell;
import com.nisovin.magicspells.spells.passive.PassiveManager;
import com.nisovin.magicspells.util.BossBarManager;
import com.nisovin.magicspells.util.BossBarManager_V1_8;
import com.nisovin.magicspells.util.BossBarManager_V1_9;
import com.nisovin.magicspells.util.ExperienceBarManager;
import com.nisovin.magicspells.util.MagicConfig;
import com.nisovin.magicspells.util.MoneyHandler;
import com.nisovin.magicspells.util.Util;
import com.nisovin.magicspells.variables.VariableManager;
import com.nisovin.magicspells.volatilecode.*;
import com.nisovin.magicspells.zones.NoMagicZoneManager;
public class MagicSpells extends JavaPlugin {
public static MagicSpells plugin;
VolatileCodeHandle volatileCodeHandle;
boolean debug;
int debugLevel;
boolean enableErrorLogging;
boolean enableProfiling;
ChatColor textColor;
int broadcastRange;
boolean opsHaveAllSpells;
boolean defaultAllPermsFalse;
boolean ignoreGrantPerms;
boolean ignoreCastPerms;
boolean separatePlayerSpellsPerWorld;
boolean allowCycleToNoSpell;
boolean alwaysShowMessageOnCycle;
boolean onlyCycleToCastableSpells;
int spellIconSlot;
boolean allowCastWithFist;
boolean castWithLeftClick;
boolean castWithRightClick;
boolean ignoreDefaultBindings;
boolean showStrCostOnMissingReagents;
HashSet<Byte> losTransparentBlocks; // TODO: fix
List<Integer> ignoreCastItemDurability; // TODO: fix
HashMap<EntityType, String> entityNames;
int globalCooldown;
boolean castOnAnimate;
boolean useExpBarAsCastTimeBar;
boolean cooldownsPersistThroughReload;
boolean ignoreCastItemEnchants;
boolean ignoreCastItemNames;
boolean ignoreCastItemNameColors;
boolean checkWorldPvpFlag;
boolean checkScoreboardTeams;
boolean hidePredefinedItemTooltips;
boolean enableManaBars;
int manaPotionCooldown;
String strManaPotionOnCooldown;
HashMap<ItemStack, Integer> manaPotions;
String soundFailOnCooldown;
String soundFailMissingReagents;
// strings
String strCastUsage;
String strUnknownSpell;
String strSpellChange;
String strSpellChangeEmpty;
String strOnCooldown;
String strMissingReagents;
String strCantCast;
String strWrongWorld;
String strCantBind;
String strConsoleName;
String strXpAutoLearned;
// spell containers
HashMap<String, Spell> spells; // map internal names to spells
HashMap<String, Spell> spellNames; // map configured names to spells
ArrayList<Spell> spellsOrdered; // spells in loaded order
HashMap<String, Spellbook> spellbooks; // player spellbooks
HashMap<String, Spell> incantations; // map incantation strings to spells
// container vars
ManaHandler mana;
HashMap<Player, Long> manaPotionCooldowns;
NoMagicZoneManager noMagicZones;
BuffManager buffManager;
ExperienceBarManager expBarManager;
BossBarManager bossBarManager;
ItemNameResolver itemNameResolver;
MoneyHandler moneyHandler;
MagicXpHandler magicXpHandler;
VariableManager variableManager;
MagicLogger magicLogger;
LifeLengthTracker lifeLengthTracker;
// profiling
HashMap<String, Long> profilingTotalTime;
HashMap<String, Integer> profilingRuns;
@Override
public void onEnable() {
load();
}
void load() {
plugin = this;
PluginManager pm = plugin.getServer().getPluginManager();
// create storage stuff
spells = new HashMap<String,Spell>();
spellNames = new HashMap<String,Spell>();
spellsOrdered = new ArrayList<Spell>();
spellbooks = new HashMap<String,Spellbook>();
incantations = new HashMap<String,Spell>();
// make sure directories are created
this.getDataFolder().mkdir();
new File(this.getDataFolder(), "spellbooks").mkdir();
// load config
if (!(new File(getDataFolder(), "config.yml")).exists() && !(new File(getDataFolder(), "general.yml")).exists()) {
saveResource("general.yml", false);
if (!(new File(getDataFolder(), "mana.yml")).exists()) saveResource("mana.yml", false);
if (!(new File(getDataFolder(), "spells-command.yml")).exists()) saveResource("spells-command.yml", false);
if (!(new File(getDataFolder(), "spells-regular.yml")).exists()) saveResource("spells-regular.yml", false);
if (!(new File(getDataFolder(), "zones.yml")).exists()) saveResource("zones.yml", false);
}
MagicConfig config = new MagicConfig(this);
if (!config.isLoaded()) {
MagicSpells.log(Level.SEVERE, "Error in config file, stopping config load");
return;
}
boolean v1_9 = false;
if (config.getBoolean("general.enable-volatile-features", true)) {
try {
Class.forName("net.minecraft.server.v1_9_R1.MinecraftServer");
volatileCodeHandle = new VolatileCodeEnabled_1_9_R1();
v1_9 = true;
} catch (ClassNotFoundException e_1_9_r1) {
try {
Class.forName("net.minecraft.server.v1_8_R3.MinecraftServer");
volatileCodeHandle = new VolatileCodeEnabled_1_8_R3();
} catch (ClassNotFoundException e_1_8_r3) {
try {
Class.forName("net.minecraft.server.v1_8_R1.MinecraftServer");
volatileCodeHandle = new VolatileCodeEnabled_1_8_R1();
} catch (ClassNotFoundException e_1_8_r1) {
error("This MagicSpells version is not fully compatible with this server version.");
error("Some features have been disabled.");
error("See http://nisovin.com/magicspells/volatilefeatures for more information.");
if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
error("ProtocolLib found: some compatibility re-enabled");
volatileCodeHandle = new VolatileCodeProtocolLib();
} else {
volatileCodeHandle = new VolatileCodeDisabled();
}
}
}
}
} else {
volatileCodeHandle = new VolatileCodeDisabled();
}
debug = config.getBoolean("general.debug", false);
debugLevel = config.getInt("general.debug-level", 3);
enableErrorLogging = config.getBoolean("general.enable-error-logging", true);
enableProfiling = config.getBoolean("general.enable-profiling", false);
textColor = ChatColor.getByChar(config.getString("general.text-color", ChatColor.DARK_AQUA.getChar() + ""));
broadcastRange = config.getInt("general.broadcast-range", 20);
opsHaveAllSpells = config.getBoolean("general.ops-have-all-spells", true);
defaultAllPermsFalse = config.getBoolean("general.default-all-perms-false", false);
ignoreGrantPerms = config.getBoolean("general.ignore-grant-perms", false);
ignoreCastPerms = config.getBoolean("general.ignore-cast-perms", false);
separatePlayerSpellsPerWorld = config.getBoolean("general.separate-player-spells-per-world", false);
allowCycleToNoSpell = config.getBoolean("general.allow-cycle-to-no-spell", false);
alwaysShowMessageOnCycle = config.getBoolean("general.always-show-message-on-cycle", false);
onlyCycleToCastableSpells = config.getBoolean("general.only-cycle-to-castable-spells", true);
spellIconSlot = config.getInt("general.spell-icon-slot", -1);
allowCastWithFist = config.getBoolean("general.allow-cast-with-fist", false);
castWithLeftClick = config.getBoolean("general.cast-with-left-click", true);
castWithRightClick = config.getBoolean("general.cast-with-right-click", false);
ignoreDefaultBindings = config.getBoolean("general.ignore-default-bindings", false);
ignoreCastItemEnchants = config.getBoolean("general.ignore-cast-item-enchants", true);
ignoreCastItemNames = config.getBoolean("general.ignore-cast-item-names", false);
ignoreCastItemNameColors = config.getBoolean("general.ignore-cast-item-name-colors", false);
checkWorldPvpFlag = config.getBoolean("general.check-world-pvp-flag", true);
checkScoreboardTeams = config.getBoolean("general.check-scoreboard-teams", false);
showStrCostOnMissingReagents = config.getBoolean("general.show-str-cost-on-missing-reagents", true);
losTransparentBlocks = new HashSet<Byte>(config.getByteList("general.los-transparent-blocks", new ArrayList<Byte>()));
if (losTransparentBlocks.size() == 0) {
losTransparentBlocks.add((byte)0);
}
ignoreCastItemDurability = config.getIntList("general.ignore-cast-item-durability", new ArrayList<Integer>());
globalCooldown = config.getInt("general.global-cooldown", 500);
castOnAnimate = config.getBoolean("general.cast-on-animate", false);
useExpBarAsCastTimeBar = config.getBoolean("general.use-exp-bar-as-cast-time-bar", true);
cooldownsPersistThroughReload = config.getBoolean("general.cooldowns-persist-through-reload", true);
entityNames = new HashMap<EntityType, String>();
if (config.contains("general.entity-names")) {
Set<String> keys = config.getSection("general.entity-names").getKeys(false);
for (String key : keys) {
EntityType entityType = Util.getEntityType(key);
if (entityType != null) {
entityNames.put(entityType, config.getString("general.entity-names." + key, ""));
}
}
}
soundFailOnCooldown = config.getString("general.sound-on-cooldown", null);
soundFailMissingReagents = config.getString("general.sound-missing-reagents", null);
strCastUsage = config.getString("general.str-cast-usage", "Usage: /cast <spell>. Use /cast list to see a list of spells.");
strUnknownSpell = config.getString("general.str-unknown-spell", "You do not know a spell with that name.");
strSpellChange = config.getString("general.str-spell-change", "You are now using the %s spell.");
strSpellChangeEmpty = config.getString("general.str-spell-change-empty", "You are no longer using a spell.");
strOnCooldown = config.getString("general.str-on-cooldown", "That spell is on cooldown.");
strMissingReagents = config.getString("general.str-missing-reagents", "You do not have the reagents for that spell.");
strCantCast = config.getString("general.str-cant-cast", "You can't cast that spell right now.");
strCantBind = config.getString("general.str-cant-bind", "You cannot bind that spell to that item.");
strWrongWorld = config.getString("general.str-wrong-world", "You cannot cast that spell here.");
strConsoleName = config.getString("general.console-name", "Admin");
strXpAutoLearned = config.getString("general.str-xp-auto-learned", "You have learned the %s spell!");
enableManaBars = config.getBoolean("mana.enable-mana-system", false);
manaPotionCooldown = config.getInt("mana.mana-potion-cooldown", 30);
strManaPotionOnCooldown = config.getString("mana.str-mana-potion-on-cooldown", "You cannot use another mana potion yet.");
// create handling objects
if (enableManaBars) mana = new ManaSystem(config);
noMagicZones = new NoMagicZoneManager();
buffManager = new BuffManager(config.getInt("general.buff-check-interval", 0));
expBarManager = new ExperienceBarManager();
if (v1_9) {
bossBarManager = new BossBarManager_V1_9();
} else {
bossBarManager = new BossBarManager_V1_8();
}
itemNameResolver = new MagicItemNameResolver();
if (getServer().getPluginManager().isPluginEnabled("Vault")) {
moneyHandler = new MoneyHandler();
}
lifeLengthTracker = new LifeLengthTracker();
// call loading event
pm.callEvent(new MagicSpellsLoadingEvent(this));
// init permissions
log("Initializing permissions");
boolean opsIgnoreReagents = config.getBoolean("general.ops-ignore-reagents", true);
boolean opsIgnoreCooldowns = config.getBoolean("general.ops-ignore-cooldowns", true);
boolean opsIgnoreCastTimes = config.getBoolean("general.ops-ignore-cast-times", true);
addPermission(pm, "noreagents", opsIgnoreReagents? PermissionDefault.OP : PermissionDefault.FALSE, "Allows casting without needing reagents");
addPermission(pm, "nocooldown", opsIgnoreCooldowns? PermissionDefault.OP : PermissionDefault.FALSE, "Allows casting without being affected by cooldowns");
addPermission(pm, "nocasttime", opsIgnoreCastTimes? PermissionDefault.OP : PermissionDefault.FALSE, "Allows casting without being affected by cast times");
addPermission(pm, "notarget", PermissionDefault.FALSE, "Prevents being targeted by any targeted spells");
addPermission(pm, "silent", PermissionDefault.FALSE, "Prevents cast messages from being broadcast to players");
HashMap<String, Boolean> permGrantChildren = new HashMap<String,Boolean>();
HashMap<String, Boolean> permLearnChildren = new HashMap<String,Boolean>();
HashMap<String, Boolean> permCastChildren = new HashMap<String,Boolean>();
HashMap<String, Boolean> permTeachChildren = new HashMap<String,Boolean>();
// load predefined items
log("Loading predefined items...");
hidePredefinedItemTooltips = config.getBoolean("general.hide-predefined-items-tooltips", false);
if (hidePredefinedItemTooltips) {
log("... hiding tooltips!");
}
Util.predefinedItems.clear();
if (config.contains("general.predefined-items")) {
Set<String> predefinedItems = config.getKeys("general.predefined-items");
if (predefinedItems != null) {
for (String key : predefinedItems) {
if (config.isString("general.predefined-items." + key)) {
String s = config.getString("general.predefined-items." + key, null);
if (s != null) {
ItemStack is = Util.getItemStackFromString(s);
if (is != null) {
Util.predefinedItems.put(key, is);
} else {
MagicSpells.error("Invalid predefined item: " + key + ": " + s);
}
}
} else if (config.isSection("general.predefined-items." + key)) {
ConfigurationSection s = config.getSection("general.predefined-items." + key);
if (s != null) {
ItemStack is = Util.getItemStackFromConfig(s);
if (is != null) {
Util.predefinedItems.put(key, is);
} else {
MagicSpells.error("Invalid predefined item: " + key + ": (section)");
}
}
} else {
MagicSpells.error("Invalid predefined item: " + key);
}
}
}
}
log("..." + Util.predefinedItems.size() + " predefined items loaded");
// load variables
log("Loading variables...");
ConfigurationSection varSec = null;
if (config.contains("general.variables") && config.isSection("general.variables")) {
varSec = config.getSection("general.variables");
}
variableManager = new VariableManager(this, varSec);
log("..." + variableManager.count() + " variables loaded");
// load spells
log("Loading spells...");
loadSpells(config, pm, permGrantChildren, permLearnChildren, permCastChildren, permTeachChildren);
log("...spells loaded: " + spells.size());
if (spells.size() == 0) {
MagicSpells.error("No spells loaded!");
return;
}
log("Finalizing perms...");
// finalize spell permissions
addPermission(pm, "grant.*", PermissionDefault.FALSE, permGrantChildren);
addPermission(pm, "learn.*", defaultAllPermsFalse ? PermissionDefault.FALSE : PermissionDefault.TRUE, permLearnChildren);
addPermission(pm, "cast.*", defaultAllPermsFalse ? PermissionDefault.FALSE : PermissionDefault.TRUE, permCastChildren);
addPermission(pm, "teach.*", defaultAllPermsFalse ? PermissionDefault.FALSE : PermissionDefault.TRUE, permTeachChildren);
// advanced perms
addPermission(pm, "advanced.list", PermissionDefault.FALSE);
addPermission(pm, "advanced.forget", PermissionDefault.FALSE);
addPermission(pm, "advanced.scroll", PermissionDefault.FALSE);
HashMap<String, Boolean> advancedPermChildren = new HashMap<String,Boolean>();
advancedPermChildren.put("magicspells.advanced.list", true);
advancedPermChildren.put("magicspells.advanced.forget", true);
advancedPermChildren.put("magicspells.advanced.scroll", true);
addPermission(pm, "advanced.*", defaultAllPermsFalse? PermissionDefault.FALSE : PermissionDefault.OP, advancedPermChildren);
log("...done");
// load xp system
if (config.getBoolean("general.enable-magic-xp", false)) {
log("Loading xp system...");
magicXpHandler = new MagicXpHandler(this, config);
log("...xp system loaded");
}
// load in-game spell names, incantations, and initialize spells
log("Initializing spells...");
for (Spell spell : spells.values()) {
spellNames.put(ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', spell.getName().toLowerCase())), spell);
String[] aliases = spell.getAliases();
if (aliases != null && aliases.length > 0) {
for (String alias : aliases) {
if (!spellNames.containsKey(alias.toLowerCase())) {
spellNames.put(alias.toLowerCase(), spell);
}
}
}
List<String> incs = spell.getIncantations();
if (incs != null && incs.size() > 0) {
for (String s : incs) {
incantations.put(s.toLowerCase(), spell);
}
}
spell.initialize();
}
log("...done");
// load online player spellbooks
log("Loading online player spellbooks...");
for (Player p : getServer().getOnlinePlayers()) {
spellbooks.put(p.getName(), new Spellbook(p, this));
}
log("...done");
// initialize passive manager
log("Initializing passive manager...");
PassiveManager passiveManager = PassiveSpell.getManager();
if (passiveManager != null) {
passiveManager.initialize();
}
log("...done");
// load saved cooldowns
if (cooldownsPersistThroughReload) {
File file = new File(getDataFolder(), "cooldowns.txt");
Scanner scanner = null;
if (file.exists()) {
try {
scanner = new Scanner(file);
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (!line.isEmpty()) {
String[] data = line.split(":");
long cooldown = Long.parseLong(data[2]);
if (cooldown > System.currentTimeMillis()) {
Spell spell = getSpellByInternalName(data[0]);
if (spell != null) {
spell.setCooldownManually(data[1], cooldown);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (scanner != null) scanner.close();
file.delete();
}
}
log("Restored cooldowns");
}
// setup mana
if (enableManaBars) {
log("Enabling mana bars...");
// init
mana.initialize();
// setup online player mana bars
for (Player p : getServer().getOnlinePlayers()) {
mana.createManaBar(p);
}
// load mana potions
List<String> manaPots = config.getStringList("mana.mana-potions", null);
if (manaPots != null && manaPots.size() > 0) {
manaPotions = new LinkedHashMap<ItemStack,Integer>();
for (int i = 0; i < manaPots.size(); i++) {
String[] data = manaPots.get(i).split(" ");
if (data.length == 2 && data[1].matches("^[0-9]+$")) {
ItemStack item = Util.getItemStackFromString(data[0]);
if (item != null) {
manaPotions.put(item, Integer.parseInt(data[1]));
} else {
error("Invalid mana potion: " + manaPots.get(i));
}
} else {
error("Invalid mana potion: " + manaPots.get(i));
}
}
manaPotionCooldowns = new HashMap<Player,Long>();
}
log("...done");
}
// load no-magic zones
noMagicZones.load(config);
if (noMagicZones.zoneCount() == 0) {
noMagicZones = null;
}
// load listeners
log("Loading cast listeners...");
registerEvents(new MagicPlayerListener(this));
registerEvents(new MagicSpellListener(this));
registerEvents(new CastListener(this));
if (incantations.size() > 0) {
registerEvents(new MagicChatListener(this));
}
RightClickListener rightClickListener = new RightClickListener(this);
if (rightClickListener.hasRightClickCastItems()) {
registerEvents(rightClickListener);
}
ConsumeListener consumeListener = new ConsumeListener(this);
if (consumeListener.hasConsumeCastItems()) {
registerEvents(consumeListener);
}
if (config.getBoolean("general.enable-dance-casting", true)) {
new DanceCastListener(this, config);
}
ModifierSet.initializeModifierListeners();
log("...done");
// initialize logger
if (config.getBoolean("general.enable-logging", false)) {
magicLogger = new MagicLogger(this);
}
// register commands
CastCommand exec = new CastCommand(this, config.getBoolean("general.enable-tab-completion", true));
getCommand("magicspellcast").setExecutor(exec);
getCommand("magicspellmana").setExecutor(exec);
getCommand("magicspellxp").setExecutor(exec);
// setup profiling
if (enableProfiling) {
profilingTotalTime = new HashMap<String, Long>();
profilingRuns = new HashMap<String, Integer>();
}
// call loaded event
pm.callEvent(new MagicSpellsLoadedEvent(this));
log("MagicSpells loading complete!");
}
private void loadSpells(MagicConfig config, PluginManager pm, HashMap<String, Boolean> permGrantChildren, HashMap<String, Boolean> permLearnChildren, HashMap<String, Boolean> permCastChildren, HashMap<String, Boolean> permTeachChildren) {
// load spells from plugin folder
final List<File> jarList = new ArrayList<File>();
for (File file : getDataFolder().listFiles()) {
if (file.getName().endsWith(".jar")) {
jarList.add(file);
}
}
// create class loader
URL[] urls = new URL[jarList.size()+1];
ClassLoader cl = getClassLoader();
try {
urls[0] = getDataFolder().toURI().toURL();
for(int i = 1; i <= jarList.size(); i++) {
urls[i] = jarList.get(i-1).toURI().toURL();
}
cl = new URLClassLoader(urls, getClassLoader());
} catch (MalformedURLException e) {
e.printStackTrace();
}
// get spells from config
Set<String> spellKeys = config.getSpellKeys();
if (spellKeys == null) return;
for (String spellName : spellKeys) {
if (config.getBoolean("spells." + spellName + ".enabled", true)) {
long starttime = System.currentTimeMillis();
String className = "";
if (config.contains("spells." + spellName + ".spell-class")) {
className = config.getString("spells." + spellName + ".spell-class", "");
}
if (className == null || className.isEmpty()) {
error("Spell '" + spellName + "' does not have a spell-class property");
continue;
} else if (className.startsWith(".")) {
className = "com.nisovin.magicspells.spells" + className;
}
try {
// load spell class
Class<? extends Spell> spellClass = cl.loadClass(className).asSubclass(Spell.class);
Constructor<? extends Spell> constructor = spellClass.getConstructor(MagicConfig.class, String.class);
constructor.setAccessible(true);
Spell spell = constructor.newInstance(config, spellName);
spells.put(spellName.toLowerCase(), spell);
spellsOrdered.add(spell);
// add permissions
if (!spell.isHelperSpell()) {
String permName = spell.getPermissionName();
if (!spell.isAlwaysGranted()) {
addPermission(pm, "grant." + permName, PermissionDefault.FALSE);
permGrantChildren.put("magicspells.grant." + permName, true);
}
addPermission(pm, "learn." + permName, defaultAllPermsFalse ? PermissionDefault.FALSE : PermissionDefault.TRUE);
addPermission(pm, "cast." + permName, defaultAllPermsFalse ? PermissionDefault.FALSE : PermissionDefault.TRUE);
addPermission(pm, "teach." + permName, defaultAllPermsFalse ? PermissionDefault.FALSE : PermissionDefault.TRUE);
addPermission(pm, "tempgrant." + permName, PermissionDefault.FALSE);
permLearnChildren.put("magicspells.learn." + permName, true);
permCastChildren.put("magicspells.cast." + permName, true);
permTeachChildren.put("magicspells.teach." + permName, true);
}
// done
debug(2, "Loaded spell: " + spellName);
} catch (ClassNotFoundException e) {
error("Unable to load spell " + spellName + " (missing class " + className + ")");
} catch (NoSuchMethodException e) {
error("Unable to load spell " + spellName + " (malformed class)");
} catch (Exception e) {
error("Unable to load spell " + spellName + " (general error)");
e.printStackTrace();
}
long elapsed = System.currentTimeMillis() - starttime;
if (elapsed > 50) getLogger().warning("LONG SPELL LOAD TIME: " + spellName + ": " + elapsed + "ms");
}
}
}
private void addPermission(PluginManager pm, String perm, PermissionDefault permDefault) {
addPermission(pm, perm, permDefault, null, null);
}
private void addPermission(PluginManager pm, String perm, PermissionDefault permDefault, String description) {
addPermission(pm, perm, permDefault, null, description);
}
private void addPermission(PluginManager pm, String perm, PermissionDefault permDefault, Map<String,Boolean> children) {
addPermission(pm, perm, permDefault, children, null);
}
private void addPermission(PluginManager pm, String perm, PermissionDefault permDefault, Map<String,Boolean> children, String description) {
if (pm.getPermission("magicspells." + perm) == null) {
if (description == null) {
pm.addPermission(new Permission("magicspells." + perm, permDefault, children));
} else {
pm.addPermission(new Permission("magicspells." + perm, description, permDefault, children));
}
}
}
/**
* Gets the instance of the MagicSpells plugin
* @return the MagicSpells plugin
*/
public static MagicSpells getInstance() {
return plugin;
}
/**
* Gets all the spells currently loaded
* @return a Collection of Spell objects
*/
public static Collection<Spell> spells() {
return plugin.spells.values();
}
/**
* Gets a spell by its internal name (the key name in the config file)
* @param spellName the internal name of the spell to find
* @return the Spell found, or null if no spell with that name was found
*/
public static Spell getSpellByInternalName(String spellName) {
return plugin.spells.get(spellName.toLowerCase());
}
/**
* Gets a spell by its in-game name (the name specified with the 'name' config option)
* @param spellName the in-game name of the spell to find
* @return the Spell found, or null if no spell with that name was found
*/
public static Spell getSpellByInGameName(String spellName) {
return plugin.spellNames.get(spellName.toLowerCase());
}
/**
* Gets a player's spellbook, which contains known spells and handles spell permissions.
* If a player does not have a spellbook, one will be created.
* @param player the player to get a spellbook for
* @return the player's spellbook
*/
public static Spellbook getSpellbook(Player player) {
Spellbook spellbook = plugin.spellbooks.get(player.getName());
if (spellbook == null) {
spellbook = new Spellbook(player, plugin);
plugin.spellbooks.put(player.getName(), spellbook);
}
return spellbook;
}
public static ChatColor getTextColor() {
return plugin.textColor;
}
/**
* Gets a list of blocks that are considered transparent
* @return list of block types
*/
public static HashSet<Byte> getTransparentBlocks() {
return plugin.losTransparentBlocks;
}
/**
* Gets a map of entity types and their configured names, to be used when sending messages to players
* @return the map
*/
public static HashMap<EntityType, String> getEntityNames() {
return plugin.entityNames;
}
/**
* Checks whether to ignore the durability on the given type when using it as a cast item.
* @param type the type to check
* @return whether to ignore durability
*/
public static boolean ignoreCastItemDurability(int type) {
if (plugin.ignoreCastItemDurability != null && plugin.ignoreCastItemDurability.contains(type)) {
return true;
} else {
return false;
}
}
public static boolean ignoreCastItemEnchants() {
return plugin.ignoreCastItemEnchants;
}
public static boolean ignoreCastItemNames() {
return plugin.ignoreCastItemNames;
}
public static boolean ignoreCastItemNameColors() {
return plugin.ignoreCastItemNameColors;
}
public static boolean showStrCostOnMissingReagents() {
return plugin.showStrCostOnMissingReagents;
}
public static boolean hidePredefinedItemTooltips() {
return plugin.hidePredefinedItemTooltips;
}
/**
* Gets the handler for no-magic zones.
* @return the no-magic zone handler
*/
public static NoMagicZoneManager getNoMagicZoneManager() {
return plugin.noMagicZones;
}
public static BuffManager getBuffManager() {
return plugin.buffManager;
}
/**
* Gets the mana handler, which handles all mana transactions.
* @return the mana handler
*/
public static ManaHandler getManaHandler() {
return plugin.mana;
}
/**
* Sets the mana handler, which handles all mana transactions.
* @param handler the mana handler
*/
public static void setManaHandler(ManaHandler handler) {
plugin.mana.turnOff();
plugin.mana = handler;
}
public static VolatileCodeHandle getVolatileCodeHandler() {
return plugin.volatileCodeHandle;
}
public static ExperienceBarManager getExpBarManager() {
return plugin.expBarManager;
}
public static BossBarManager getBossBarManager() {
return plugin.bossBarManager;
}
public static ItemNameResolver getItemNameResolver() {
return plugin.itemNameResolver;
}
public static void setItemNameResolver(ItemNameResolver resolver) {
plugin.itemNameResolver = resolver;
}
public static MoneyHandler getMoneyHandler() {
return plugin.moneyHandler;
}
public static MagicXpHandler getMagicXpHandler() {
return plugin.magicXpHandler;
}
public static VariableManager getVariableManager() {
return plugin.variableManager;
}
public static LifeLengthTracker getLifeLengthTracker() {
return plugin.lifeLengthTracker;
}
/**
* 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
*/
static public String formatMessage(String message, String... replacements) {
if (message == null) return null;
String msg = message;
for (int i = 0; i < replacements.length; i+=2) {
if (replacements[i] != null) {
if (replacements[i+1] != null) {
msg = msg.replace(replacements[i], replacements[i+1]);
} else {
msg = msg.replace(replacements[i], "");
}
}
}
return msg;
}
/**
* 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
*/
static public 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
*/
static public void sendMessage(Player player, String message) {
if (message != null && !message.equals("")) {
// do var replacements
message = doVariableReplacements(player, message);
// send messages
String [] msgs = message.replaceAll("&([0-9a-fk-or])", "\u00A7$1").split("\n");
for (String msg : msgs) {
if (!msg.equals("")) {
player.sendMessage(plugin.textColor + msg);
}
}
}
}
static private Pattern chatVarMatchPattern = Pattern.compile("%var:[A-Za-z0-9_]+(:[0-9]+)?%", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
static public String doVariableReplacements(Player player, String string) {
if (string != null && plugin.variableManager != null && string.contains("%var")) {
Matcher matcher = chatVarMatchPattern.matcher(string);
while (matcher.find()) {
String varText = matcher.group();
String[] varData = varText.substring(5, varText.length() - 1).split(":");
double val = plugin.variableManager.getValue(varData[0], player);
String sval = varData.length == 1 ? Util.getStringNumber(val, -1) : Util.getStringNumber(val, Integer.parseInt(varData[1]));
string = string.replace(varText, sval);
}
}
return string;
}
public static void registerEvents(final Listener listener) {
Method[] methods;
try {
methods = listener.getClass().getDeclaredMethods();
} catch (NoClassDefFoundError e) {
return;
}
for (int i = 0; i < methods.length; i++) {
final Method method = methods[i];
final EventHandler eh = method.getAnnotation(EventHandler.class);
if (eh == null) continue;
final Class<?> checkClass = method.getParameterTypes()[0];
if (!Event.class.isAssignableFrom(checkClass) || method.getParameterTypes().length != 1) {
plugin.getLogger().severe("Wrong method arguments used for event type registered");
continue;
}
final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
method.setAccessible(true);
EventExecutor executor = new EventExecutor() {
final String eventKey = plugin.enableProfiling ? "Event:" + listener.getClass().getName().replace("com.nisovin.magicspells.","") + "." + method.getName() + "(" + eventClass.getSimpleName() + ")" : null;
public void execute(Listener listener, Event event) {
try {
if (!eventClass.isAssignableFrom(event.getClass())) {
return;
}
long start = System.nanoTime();
method.invoke(listener, event);
if (plugin.enableProfiling) {
Long total = plugin.profilingTotalTime.get(eventKey);
if (total == null) total = (long)0;
total += (System.nanoTime() - start);
plugin.profilingTotalTime.put(eventKey, total);
Integer runs = plugin.profilingRuns.get(eventKey);
if (runs == null) runs = 0;
runs += 1;
plugin.profilingRuns.put(eventKey, runs);
}
} catch (Exception ex) {
handleException(ex);
}
}
};
plugin.getServer().getPluginManager().registerEvent(eventClass, listener, eh.priority(), executor, plugin, eh.ignoreCancelled());
}
}
public static int scheduleDelayedTask(final Runnable task, int delay) {
return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, !plugin.enableErrorLogging ? task : new Runnable() {
public void run() {
try {
task.run();
} catch (Exception e) {
handleException(e);
}
}
}, delay);
}
public static int scheduleRepeatingTask(final Runnable task, int delay, int interval) {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, !plugin.enableErrorLogging ? task : new Runnable() {
public void run() {
try {
task.run();
} catch (Exception e) {
handleException(e);
}
}
}, delay, interval);
}
public static void cancelTask(int taskId) {
Bukkit.getScheduler().cancelTask(taskId);
}
public static void handleException(Exception ex) {
if (plugin.enableErrorLogging) {
plugin.getLogger().severe("AN EXCEPTION HAS OCCURED:");
PrintWriter writer = null;
try {
File folder = new File(plugin.getDataFolder(), "errors");
if (!folder.exists()) folder.mkdir();
writer = new PrintWriter(new File(folder, System.currentTimeMillis() + ".txt"));
Throwable t = ex;
while (t != null) {
plugin.getLogger().severe(" " + t.getMessage() + " (" + t.getClass().getName() + ")");
t.printStackTrace(writer);
writer.println();
t = t.getCause();
}
plugin.getLogger().severe("This error has been saved in the errors folder");
writer.println("Server version: " + Bukkit.getServer().getVersion());
writer.println("MagicSpells version: " + plugin.getDescription().getVersion());
} catch (Exception x) {
plugin.getLogger().severe("ERROR HANDLING EXCEPTION");
x.printStackTrace();
ex.printStackTrace();
} finally {
if (writer != null) writer.close();
}
} else {
ex.printStackTrace();
}
}
static void profilingReport() {
if (plugin.profilingTotalTime != null && plugin.profilingRuns != null) {
PrintWriter writer = null;
try {
writer = new PrintWriter(new File(plugin.getDataFolder(), "profiling_report_" + System.currentTimeMillis() + ".txt"));
long totalTime = 0;
writer.println("Key\tRuns\tAvg\tTotal");
for (String key : plugin.profilingTotalTime.keySet()) {
long time = plugin.profilingTotalTime.get(key);
int runs = plugin.profilingRuns.get(key);
totalTime += time;
writer.println(key + "\t" + runs + "\t" + (time/runs/1000000F) + "ms\t" + (time/1000000F) + "ms");
}
writer.println();
writer.println("TOTAL TIME: " + (totalTime/1000000F) + "ms");
} catch (Exception ex) {
error("Failed to save profiling report");
handleException(ex);
} finally {
if (writer != null) writer.close();
}
plugin.profilingTotalTime.clear();
plugin.profilingRuns.clear();
}
}
/**
* Writes a debug message to the console if the debug option is enabled.
* Uses debug level 2.
* @param message the message to write to the console
*/
public static void debug(String message) {
debug(2, message);
}
/**
* Writes a debug message to the console if the debug option is enabled.
* @param level the debug level to log with
* @param message the message to write to the console
*/
public static void debug(int level, String message) {
if (plugin.debug && level <= plugin.debugLevel) {
log(Level.INFO, message);
}
}
public static void log(String message) {
log(Level.INFO, message);
}
public static void error(String message) {
log(Level.WARNING, message);
}
/**
* Writes an error message to the console.
* @param level the error level
* @param message the error message
*/
public static void log(Level level, String message) {
plugin.getLogger().log(level, message);
}
public static boolean profilingEnabled() {
return plugin.enableProfiling;
}
public static void addProfile(String key, long time) {
if (plugin.enableProfiling) {
Long total = plugin.profilingTotalTime.get(key);
if (total == null) total = (long)0;
total += time;
plugin.profilingTotalTime.put(key, total);
Integer runs = plugin.profilingRuns.get(key);
if (runs == null) runs = 0;
runs += 1;
plugin.profilingRuns.put(key, runs);
}
}
/**
* Teaches a player a spell (adds it to their spellbook)
* @param player the player to teach
* @param spellName the spell name, either the in-game name or the internal name
* @return whether the spell was taught to the player
*/
public static boolean teachSpell(Player player, String spellName) {
Spell spell = plugin.spellNames.get(spellName.toLowerCase());
if (spell == null) {
spell = plugin.spells.get(spellName.toLowerCase());
if (spell == null) {
return false;
}
}
Spellbook spellbook = getSpellbook(player);
if (spellbook == null || spellbook.hasSpell(spell) || !spellbook.canLearn(spell)) {
return false;
} else {
// call event
SpellLearnEvent event = new SpellLearnEvent(spell, player, LearnSource.OTHER, null);
plugin.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return false;
} else {
spellbook.addSpell(spell);
spellbook.save();
return true;
}
}
}
void unload() {
// turn off spells
for (Spell spell : spells.values()) {
spell.turnOff();
}
PassiveSpell.resetManager();
// save cooldowns
if (cooldownsPersistThroughReload) {
File file = new File(getDataFolder(), "cooldowns.txt");
if (file.exists()) file.delete();
try {
FileWriter writer = new FileWriter(file);
for (Spell spell : spells.values()) {
Map<String, Long> cooldowns = spell.getCooldowns();
for (String name : cooldowns.keySet()) {
long cooldown = cooldowns.get(name);
if (cooldown > System.currentTimeMillis()) {
writer.append(spell.getInternalName() + ":" + name + ":" + cooldown + "\n");
}
}
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
file.delete();
}
}
// turn off buff manager
if (buffManager != null) {
buffManager.turnOff();
buffManager = null;
}
// clear memory
spells.clear();
spells = null;
spellNames.clear();
spellNames = null;
spellsOrdered.clear();
spellsOrdered = null;
spellbooks.clear();
spellbooks = null;
incantations.clear();
incantations = null;
if (magicXpHandler != null) {
magicXpHandler.saveAll();
magicXpHandler = null;
}
if (mana != null) {
mana.turnOff();
mana = null;
}
if (manaPotionCooldowns != null) {
manaPotionCooldowns.clear();
manaPotionCooldowns = null;
}
if (noMagicZones != null) {
noMagicZones.turnOff();
noMagicZones = null;
}
if (magicLogger != null) {
magicLogger.disable();
magicLogger = null;
}
if (variableManager != null) {
variableManager.disable();
variableManager = null;
}
if (bossBarManager != null) {
bossBarManager.turnOff();
bossBarManager = null;
}
expBarManager = null;
itemNameResolver = null;
moneyHandler = null;
losTransparentBlocks = null;
ignoreCastItemDurability = null;
entityNames = null;
profilingTotalTime = null;
profilingRuns = null;
strCastUsage = null;
strUnknownSpell = null;
strSpellChange = null;
strSpellChangeEmpty = null;
strOnCooldown = null;
strMissingReagents = null;
strCantCast = null;
strWrongWorld = null;
strCantBind = null;
strConsoleName = null;
// remove star permissions (to allow new spells to be added to them)
getServer().getPluginManager().removePermission("magicspells.grant.*");
getServer().getPluginManager().removePermission("magicspells.cast.*");
getServer().getPluginManager().removePermission("magicspells.learn.*");
getServer().getPluginManager().removePermission("magicspells.teach.*");
// unregister all listeners
HandlerList.unregisterAll(this);
// cancel all tasks
Bukkit.getScheduler().cancelTasks(this);
plugin = null;
}
@Override
public void onDisable() {
unload();
}
}
/*
* TODO:
*
* - Use MagicPlayer (Caster/PlayerCaster) across the entire plugin
* - Allow spells to be cast by something other than players, like blocks and other entities
* - Move NoMagicZoneWorldGuard and NoMagicZoneResidence outside of the core plugin
*
*/