/**
* BetonQuest - advanced quests for Bukkit
* Copyright (C) 2016 Jakub "Co0sh" Sapalski
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pl.betoncraft.betonquest;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.bstats.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import pl.betoncraft.betonquest.api.Condition;
import pl.betoncraft.betonquest.api.Objective;
import pl.betoncraft.betonquest.api.QuestEvent;
import pl.betoncraft.betonquest.api.Variable;
import pl.betoncraft.betonquest.commands.BackpackCommand;
import pl.betoncraft.betonquest.commands.CancelQuestCommand;
import pl.betoncraft.betonquest.commands.CompassCommand;
import pl.betoncraft.betonquest.commands.JournalCommand;
import pl.betoncraft.betonquest.commands.LangCommand;
import pl.betoncraft.betonquest.commands.QuestCommand;
import pl.betoncraft.betonquest.compatibility.Compatibility;
import pl.betoncraft.betonquest.conditions.AchievementCondition;
import pl.betoncraft.betonquest.conditions.AlternativeCondition;
import pl.betoncraft.betonquest.conditions.ArmorCondition;
import pl.betoncraft.betonquest.conditions.ArmorRatingCondition;
import pl.betoncraft.betonquest.conditions.CheckCondition;
import pl.betoncraft.betonquest.conditions.ChestItemCondition;
import pl.betoncraft.betonquest.conditions.ConjunctionCondition;
import pl.betoncraft.betonquest.conditions.EffectCondition;
import pl.betoncraft.betonquest.conditions.EmptySlotsCondition;
import pl.betoncraft.betonquest.conditions.ExperienceCondition;
import pl.betoncraft.betonquest.conditions.FlyingCondition;
import pl.betoncraft.betonquest.conditions.GameModeCondition;
import pl.betoncraft.betonquest.conditions.HandCondition;
import pl.betoncraft.betonquest.conditions.HealthCondition;
import pl.betoncraft.betonquest.conditions.HeightCondition;
import pl.betoncraft.betonquest.conditions.ItemCondition;
import pl.betoncraft.betonquest.conditions.JournalCondition;
import pl.betoncraft.betonquest.conditions.LocationCondition;
import pl.betoncraft.betonquest.conditions.MonstersCondition;
import pl.betoncraft.betonquest.conditions.ObjectiveCondition;
import pl.betoncraft.betonquest.conditions.PartyCondition;
import pl.betoncraft.betonquest.conditions.PermissionCondition;
import pl.betoncraft.betonquest.conditions.PointCondition;
import pl.betoncraft.betonquest.conditions.RandomCondition;
import pl.betoncraft.betonquest.conditions.ScoreboardCondition;
import pl.betoncraft.betonquest.conditions.SneakCondition;
import pl.betoncraft.betonquest.conditions.TagCondition;
import pl.betoncraft.betonquest.conditions.TestForBlockCondition;
import pl.betoncraft.betonquest.conditions.TimeCondition;
import pl.betoncraft.betonquest.conditions.VariableCondition;
import pl.betoncraft.betonquest.conditions.VehicleCondition;
import pl.betoncraft.betonquest.conditions.WeatherCondition;
import pl.betoncraft.betonquest.conditions.WorldCondition;
import pl.betoncraft.betonquest.config.Config;
import pl.betoncraft.betonquest.config.ConfigPackage;
import pl.betoncraft.betonquest.config.ConfigUpdater;
import pl.betoncraft.betonquest.conversation.CombatTagger;
import pl.betoncraft.betonquest.conversation.Conversation;
import pl.betoncraft.betonquest.conversation.ConversationColors;
import pl.betoncraft.betonquest.conversation.ConversationData;
import pl.betoncraft.betonquest.conversation.ConversationIO;
import pl.betoncraft.betonquest.conversation.ConversationResumer;
import pl.betoncraft.betonquest.conversation.CubeNPCListener;
import pl.betoncraft.betonquest.conversation.InventoryConvIO;
import pl.betoncraft.betonquest.conversation.SimpleConvIO;
import pl.betoncraft.betonquest.conversation.TellrawConvIO;
import pl.betoncraft.betonquest.database.Database;
import pl.betoncraft.betonquest.database.MySQL;
import pl.betoncraft.betonquest.database.PlayerData;
import pl.betoncraft.betonquest.database.SQLite;
import pl.betoncraft.betonquest.database.Saver;
import pl.betoncraft.betonquest.events.CancelEvent;
import pl.betoncraft.betonquest.events.ChestClearEvent;
import pl.betoncraft.betonquest.events.ChestGiveEvent;
import pl.betoncraft.betonquest.events.ChestTakeEvent;
import pl.betoncraft.betonquest.events.ClearEvent;
import pl.betoncraft.betonquest.events.CommandEvent;
import pl.betoncraft.betonquest.events.CompassEvent;
import pl.betoncraft.betonquest.events.ConversationEvent;
import pl.betoncraft.betonquest.events.DamageEvent;
import pl.betoncraft.betonquest.events.DoorEvent;
import pl.betoncraft.betonquest.events.EffectEvent;
import pl.betoncraft.betonquest.events.ExplosionEvent;
import pl.betoncraft.betonquest.events.FolderEvent;
import pl.betoncraft.betonquest.events.GiveEvent;
import pl.betoncraft.betonquest.events.GiveJournalEvent;
import pl.betoncraft.betonquest.events.IfElseEvent;
import pl.betoncraft.betonquest.events.JournalEvent;
import pl.betoncraft.betonquest.events.KillEvent;
import pl.betoncraft.betonquest.events.LanguageEvent;
import pl.betoncraft.betonquest.events.LeverEvent;
import pl.betoncraft.betonquest.events.LightningEvent;
import pl.betoncraft.betonquest.events.MessageEvent;
import pl.betoncraft.betonquest.events.ObjectiveEvent;
import pl.betoncraft.betonquest.events.PartyEvent;
import pl.betoncraft.betonquest.events.PointEvent;
import pl.betoncraft.betonquest.events.RunEvent;
import pl.betoncraft.betonquest.events.ScoreboardEvent;
import pl.betoncraft.betonquest.events.SetBlockEvent;
import pl.betoncraft.betonquest.events.SpawnMobEvent;
import pl.betoncraft.betonquest.events.SudoEvent;
import pl.betoncraft.betonquest.events.TagEvent;
import pl.betoncraft.betonquest.events.TakeEvent;
import pl.betoncraft.betonquest.events.TeleportEvent;
import pl.betoncraft.betonquest.events.TimeEvent;
import pl.betoncraft.betonquest.events.TitleEvent;
import pl.betoncraft.betonquest.events.VariableEvent;
import pl.betoncraft.betonquest.events.WeatherEvent;
import pl.betoncraft.betonquest.item.QuestItemHandler;
import pl.betoncraft.betonquest.objectives.ActionObjective;
import pl.betoncraft.betonquest.objectives.ArrowShootObjective;
import pl.betoncraft.betonquest.objectives.BlockObjective;
import pl.betoncraft.betonquest.objectives.BreedObjective;
import pl.betoncraft.betonquest.objectives.ChestPutObjective;
import pl.betoncraft.betonquest.objectives.ConsumeObjective;
import pl.betoncraft.betonquest.objectives.CraftingObjective;
import pl.betoncraft.betonquest.objectives.DelayObjective;
import pl.betoncraft.betonquest.objectives.DieObjective;
import pl.betoncraft.betonquest.objectives.EnchantObjective;
import pl.betoncraft.betonquest.objectives.ExperienceObjective;
import pl.betoncraft.betonquest.objectives.FishObjective;
import pl.betoncraft.betonquest.objectives.KillPlayerObjective;
import pl.betoncraft.betonquest.objectives.LocationObjective;
import pl.betoncraft.betonquest.objectives.LogoutObjective;
import pl.betoncraft.betonquest.objectives.MobKillObjective;
import pl.betoncraft.betonquest.objectives.PasswordObjective;
import pl.betoncraft.betonquest.objectives.PotionObjective;
import pl.betoncraft.betonquest.objectives.ShearObjective;
import pl.betoncraft.betonquest.objectives.SmeltingObjective;
import pl.betoncraft.betonquest.objectives.StepObjective;
import pl.betoncraft.betonquest.objectives.TameObjective;
import pl.betoncraft.betonquest.objectives.VariableObjective;
import pl.betoncraft.betonquest.objectives.VehicleObjective;
import pl.betoncraft.betonquest.utils.Debug;
import pl.betoncraft.betonquest.utils.PlayerConverter;
import pl.betoncraft.betonquest.utils.Updater;
import pl.betoncraft.betonquest.utils.Utils;
import pl.betoncraft.betonquest.variables.ItemAmountVariable;
import pl.betoncraft.betonquest.variables.LocationVariable;
import pl.betoncraft.betonquest.variables.NpcNameVariable;
import pl.betoncraft.betonquest.variables.ObjectivePropertyVariable;
import pl.betoncraft.betonquest.variables.PlayerNameVariable;
import pl.betoncraft.betonquest.variables.PointVariable;
import pl.betoncraft.betonquest.variables.VersionVariable;
/**
* Represents BetonQuest plugin
*
* @author Jakub Sapalski
*/
public final class BetonQuest extends JavaPlugin {
private final static String ERROR = "There was some error. Please send it to the"
+ " developer: <coosheck@gmail.com>";
private static BetonQuest instance;
private Database database;
private boolean isMySQLUsed;
private Saver saver;
private Compatibility compatibility;
private Updater updater;
private ConcurrentHashMap<String, PlayerData> playerDataMap = new ConcurrentHashMap<>();
private static HashMap<String, Class<? extends Condition>> conditionTypes = new HashMap<>();
private static HashMap<String, Class<? extends QuestEvent>> eventTypes = new HashMap<>();
private static HashMap<String, Class<? extends Objective>> objectiveTypes = new HashMap<>();
private static HashMap<String, Class<? extends ConversationIO>> convIOTypes = new HashMap<>();
private static HashMap<String, Class<? extends Variable>> variableTypes = new HashMap<>();
private static HashMap<ConditionID, Condition> conditions = new HashMap<>();
private static HashMap<EventID, QuestEvent> events = new HashMap<>();
private static HashMap<ObjectiveID, Objective> objectives = new HashMap<>();
private static HashMap<String, ConversationData> conversations = new HashMap<>();
private static HashMap<VariableID, Variable> variables = new HashMap<>();
@Override
public void onEnable() {
instance = this;
// initialize debugger
new Debug();
// load configuration
new Config();
// try to connect to database
Debug.info("Connecting to MySQL database");
this.database = new MySQL(this, getConfig().getString("mysql.host"), getConfig().getString("mysql.port"),
getConfig().getString("mysql.base"), getConfig().getString("mysql.user"),
getConfig().getString("mysql.pass"));
// try to connect to MySQL
Connection con = database.getConnection();
if (con != null) {
Debug.broadcast("Using MySQL for storing data!");
isMySQLUsed = true;
// if it fails use SQLite
} else {
this.database = new SQLite(this, "database.db");
Debug.broadcast("Using SQLite for storing data!");
isMySQLUsed = false;
}
// create tables in the database
database.createTables(isMySQLUsed);
// create and start the saver object, which handles correct asynchronous
// saving to the database
saver = new Saver();
saver.start();
// load database backup
Utils.loadDatabaseFromBackup();
// update configuration if needed
new ConfigUpdater();
// if it's a first start of the plugin, debug option is not there
// add it so debug option is turned off after first start
if (getConfig().getString("debug", null) == null) {
getConfig().set("debug", "false");
saveConfig();
}
// instantiating of these important things
new JoinQuitListener();
// instantiate default conversation start listener
new CubeNPCListener();
// instantiate journal handler
new QuestItemHandler();
// initialize static events
new StaticEvents();
// initialize combat tagging
new CombatTagger();
// load colors for conversations
new ConversationColors();
// start timer for global locations
new GlobalLocations().runTaskTimer(this, 20, 20);
// start mob kill listener
new MobKillListener();
// start custom drop listener
new CustomDropListener();
// register commands
new QuestCommand();
new JournalCommand();
new BackpackCommand();
new CancelQuestCommand();
new CompassCommand();
new LangCommand();
// register conditions
registerConditions("health", HealthCondition.class);
registerConditions("permission", PermissionCondition.class);
registerConditions("experience", ExperienceCondition.class);
registerConditions("tag", TagCondition.class);
registerConditions("point", PointCondition.class);
registerConditions("and", ConjunctionCondition.class);
registerConditions("or", AlternativeCondition.class);
registerConditions("time", TimeCondition.class);
registerConditions("weather", WeatherCondition.class);
registerConditions("height", HeightCondition.class);
registerConditions("item", ItemCondition.class);
registerConditions("hand", HandCondition.class);
registerConditions("location", LocationCondition.class);
registerConditions("armor", ArmorCondition.class);
registerConditions("effect", EffectCondition.class);
registerConditions("rating", ArmorRatingCondition.class);
registerConditions("sneak", SneakCondition.class);
registerConditions("random", RandomCondition.class);
registerConditions("journal", JournalCondition.class);
registerConditions("testforblock", TestForBlockCondition.class);
registerConditions("empty", EmptySlotsCondition.class);
registerConditions("party", PartyCondition.class);
registerConditions("monsters", MonstersCondition.class);
registerConditions("objective", ObjectiveCondition.class);
registerConditions("check", CheckCondition.class);
registerConditions("chestitem", ChestItemCondition.class);
registerConditions("score", ScoreboardCondition.class);
registerConditions("riding", VehicleCondition.class);
registerConditions("world", WorldCondition.class);
registerConditions("gamemode", GameModeCondition.class);
registerConditions("achievement", AchievementCondition.class);
registerConditions("variable", VariableCondition.class);
registerConditions("fly", FlyingCondition.class);
// register events
registerEvents("message", MessageEvent.class);
registerEvents("objective", ObjectiveEvent.class);
registerEvents("command", CommandEvent.class);
registerEvents("tag", TagEvent.class);
registerEvents("journal", JournalEvent.class);
registerEvents("teleport", TeleportEvent.class);
registerEvents("explosion", ExplosionEvent.class);
registerEvents("lightning", LightningEvent.class);
registerEvents("point", PointEvent.class);
registerEvents("give", GiveEvent.class);
registerEvents("take", TakeEvent.class);
registerEvents("conversation", ConversationEvent.class);
registerEvents("kill", KillEvent.class);
registerEvents("effect", EffectEvent.class);
registerEvents("spawn", SpawnMobEvent.class);
registerEvents("time", TimeEvent.class);
registerEvents("weather", WeatherEvent.class);
registerEvents("folder", FolderEvent.class);
registerEvents("setblock", SetBlockEvent.class);
registerEvents("damage", DamageEvent.class);
registerEvents("party", PartyEvent.class);
registerEvents("clear", ClearEvent.class);
registerEvents("run", RunEvent.class);
registerEvents("givejournal", GiveJournalEvent.class);
registerEvents("sudo", SudoEvent.class);
registerEvents("chestgive", ChestGiveEvent.class);
registerEvents("chesttake", ChestTakeEvent.class);
registerEvents("chestclear", ChestClearEvent.class);
registerEvents("compass", CompassEvent.class);
registerEvents("cancel", CancelEvent.class);
registerEvents("score", ScoreboardEvent.class);
registerEvents("lever", LeverEvent.class);
registerEvents("door", DoorEvent.class);
registerEvents("if", IfElseEvent.class);
registerEvents("variable", VariableEvent.class);
registerEvents("title", TitleEvent.class);
registerEvents("language", LanguageEvent.class);
// register objectives
registerObjectives("location", LocationObjective.class);
registerObjectives("block", BlockObjective.class);
registerObjectives("mobkill", MobKillObjective.class);
registerObjectives("action", ActionObjective.class);
registerObjectives("die", DieObjective.class);
registerObjectives("craft", CraftingObjective.class);
registerObjectives("smelt", SmeltingObjective.class);
registerObjectives("tame", TameObjective.class);
registerObjectives("delay", DelayObjective.class);
registerObjectives("arrow", ArrowShootObjective.class);
registerObjectives("experience", ExperienceObjective.class);
registerObjectives("step", StepObjective.class);
registerObjectives("logout", LogoutObjective.class);
registerObjectives("password", PasswordObjective.class);
registerObjectives("fish", FishObjective.class);
registerObjectives("enchant", EnchantObjective.class);
registerObjectives("shear", ShearObjective.class);
registerObjectives("chestput", ChestPutObjective.class);
registerObjectives("potion", PotionObjective.class);
registerObjectives("vehicle", VehicleObjective.class);
registerObjectives("consume", ConsumeObjective.class);
registerObjectives("variable", VariableObjective.class);
registerObjectives("kill", KillPlayerObjective.class);
registerObjectives("breed", BreedObjective.class);
// register conversation IO types
registerConversationIO("simple", SimpleConvIO.class);
registerConversationIO("tellraw", TellrawConvIO.class);
registerConversationIO("chest", InventoryConvIO.class);
// register variable types
registerVariable("player", PlayerNameVariable.class);
registerVariable("npc", NpcNameVariable.class);
registerVariable("objective", ObjectivePropertyVariable.class);
registerVariable("point", PointVariable.class);
registerVariable("item", ItemAmountVariable.class);
registerVariable("version", VersionVariable.class);
registerVariable("location", LocationVariable.class);
// initialize compatibility with other plugins
compatibility = new Compatibility();
// schedule quest data loading on the first tick, so all other
// plugins can register their types
Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
public void run() {
// Load all events and conditions
loadData();
// load data for all online players
for (Player player : Bukkit.getOnlinePlayers()) {
String playerID = PlayerConverter.getID(player);
PlayerData playerData = new PlayerData(playerID);
playerDataMap.put(playerID, playerData);
playerData.startObjectives();
playerData.getJournal().update();
if (playerData.getConversation() != null)
new ConversationResumer(playerID, playerData.getConversation());
}
}
});
// block betonquestanswer logging (it's just a spam)
try {
Class.forName("org.apache.logging.log4j.core.Filter");
Logger coreLogger = (Logger) LogManager.getRootLogger();
coreLogger.addFilter(new AnswerFilter());
} catch (ClassNotFoundException | NoClassDefFoundError e) {
Debug.info("Could not disable /betonquestanswer logging");
}
// metrics
new Metrics(this);
// updater
updater = new Updater(this.getFile());
// done
Debug.broadcast("BetonQuest succesfully enabled!");
}
/**
* Loads events and conditions to the maps
*/
public void loadData() {
// save data of all objectives to the players
for (Objective objective : objectives.values()) {
objective.close();
}
// clear previously loaded data
events.clear();
conditions.clear();
conversations.clear();
objectives.clear();
variables.clear();
// load new data
for (ConfigPackage pack : Config.getPackages().values()) {
String packName = pack.getName();
Debug.info("Loading stuff in package " + packName);
FileConfiguration eConfig = Config.getPackages().get(packName).getEvents().getConfig();
for (String key : eConfig.getKeys(false)) {
if (key.contains(" ")) {
Debug.error("Event name cannot contain spaces: '" + key + "' (in " + packName + " package)");
continue;
}
EventID ID;
try {
ID = new EventID(pack, key);
} catch (ObjectNotFoundException e) {
Debug.error("Error while loading event '" + packName + "." + key + "': " + e.getMessage());
continue;
}
String type;
try {
type = ID.generateInstruction().getPart(0);
} catch (InstructionParseException e) {
Debug.error("Objective type not defined in '" + packName + "." + key + "'");
continue;
}
Class<? extends QuestEvent> eventClass = eventTypes.get(type);
if (eventClass == null) {
// if it's null then there is no such type registered, log an error
Debug.error("Event type " + type + " is not registered, check if it's"
+ " spelled correctly in '" + ID + "' event.");
continue;
}
try {
QuestEvent event = eventClass.getConstructor(Instruction.class).newInstance(ID.generateInstruction());
events.put(ID, event);
Debug.info(" Event '" + ID + "' loaded");
} catch (InvocationTargetException e) {
if (e.getCause() instanceof InstructionParseException) {
Debug.error("Error in '" + ID + "' event (" + type + "): " + e.getCause().getMessage());
} else {
e.printStackTrace();
Debug.error(ERROR);
}
} catch (Exception e) {
e.printStackTrace();
Debug.error(ERROR);
}
}
FileConfiguration cConfig = pack.getConditions().getConfig();
for (String key : cConfig.getKeys(false)) {
if (key.contains(" ")) {
Debug.error("Condition name cannot contain spaces: '" + key + "' (in " + packName + " package)");
continue;
}
ConditionID ID;
try {
ID = new ConditionID(pack, key);
} catch (ObjectNotFoundException e) {
Debug.error("Error while loading condition '" + packName + "." + key + "': " + e.getMessage());
continue;
}
String type;
try {
type = ID.generateInstruction().getPart(0);
} catch (InstructionParseException e1) {
Debug.error("Condition type not defined in '" + packName + "." + key + "'");
continue;
}
Class<? extends Condition> conditionClass = conditionTypes.get(type);
// if it's null then there is no such type registered, log an error
if (conditionClass == null) {
Debug.error("Condition type " + type + " is not registered,"
+ " check if it's spelled correctly in '" + ID + "' condition.");
continue;
}
try {
Condition condition = conditionClass.getConstructor(Instruction.class) .newInstance(ID.generateInstruction());
conditions.put(ID, condition);
Debug.info(" Condition '" + ID + "' loaded");
} catch (InvocationTargetException e) {
if (e.getCause() instanceof InstructionParseException) {
Debug.error("Error in '" + ID + "' condition (" + type + "): " + e.getCause().getMessage());
} else {
e.printStackTrace();
Debug.error(ERROR);
}
} catch (Exception e) {
e.printStackTrace();
Debug.error(ERROR);
}
}
FileConfiguration oConfig = pack.getObjectives().getConfig();
for (String key : oConfig.getKeys(false)) {
if (key.contains(" ")) {
Debug.error("Objective name cannot contain spaces: '" + key + "' (in " + packName + " package)");
continue;
}
ObjectiveID ID;
try {
ID = new ObjectiveID(pack, key);
} catch (ObjectNotFoundException e) {
Debug.error("Error while loading objective '" + packName + "." + key + "': " + e.getMessage());
continue;
}
String type;
try {
type = ID.generateInstruction().getPart(0);
} catch (InstructionParseException e) {
Debug.error("Objective type not defined in '" + packName + "." + key + "'");
continue;
}
Class<? extends Objective> objectiveClass = objectiveTypes.get(type);
// if it's null then there is no such type registered, log an
// error
if (objectiveClass == null) {
Debug.error("Objective type " + type + " is not registered, check if it's"
+ " spelled correctly in '" + ID + "' objective.");
continue;
}
try {
Objective objective = objectiveClass.getConstructor(Instruction.class)
.newInstance(ID.generateInstruction());
objectives.put(ID, objective);
Debug.info(" Objective '" + ID + "' loaded");
} catch (InvocationTargetException e) {
if (e.getCause() instanceof InstructionParseException) {
Debug.error("Error in '" + ID + "' objective (" + type + "): " + e.getCause().getMessage());
} else {
e.printStackTrace();
Debug.error(ERROR);
}
} catch (Exception e) {
e.printStackTrace();
Debug.error(ERROR);
}
}
for (String convName : pack.getConversationNames()) {
if (convName.contains(" ")) {
Debug.error("Conversation name cannot contain spaces: '" + convName + "' (in " + packName
+ " package)");
continue;
}
try {
conversations.put(pack.getName() + "." + convName, new ConversationData(pack, convName));
} catch (InstructionParseException e) {
Debug.error(
"Error in '" + packName + "." + convName + "' conversation: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
Debug.error(ERROR);
}
}
// check external pointers
ConversationData.postEnableCheck();
Debug.info("Everything in package " + packName + " loaded");
}
Debug.broadcast("There are " + conditions.size() + " conditions, " + events.size() + " events, "
+ objectives.size() + " objectives and " + conversations.size() + " conversations loaded from "
+ Config.getPackages().size() + " packages.");
// start those freshly loaded objectives for all players
for (PlayerData playerData : playerDataMap.values()) {
playerData.startObjectives();
}
}
/**
* Reloads the plugin.
*/
public void reload() {
// reload the configuration
Debug.info("Reloading configuration");
new Config();
// reload updater settings
BetonQuest.getInstance().getUpdater().reload();
// load new static events
new StaticEvents();
// stop current global locations listener
// and start new one with reloaded configs
Debug.info("Restarting global locations");
GlobalLocations.stop();
new GlobalLocations().runTaskTimer(instance, 0, 20);
new ConversationColors();
Compatibility.reload();
// load all events, conditions, objectives, conversations etc.
loadData();
// start objectives and update journals for every online player
for (Player player : Bukkit.getOnlinePlayers()) {
String playerID = PlayerConverter.getID(player);
Debug.info("Updating journal for player " + PlayerConverter.getName(playerID));
PlayerData playerData = instance.getPlayerData(playerID);
Journal journal = playerData.getJournal();
journal.update();
}
// initialize new debugger
new Debug();
}
@Override
public void onDisable() {
// suspend all conversations
for (Player player : Bukkit.getOnlinePlayers()) {
Conversation conv = Conversation.getConversation(PlayerConverter.getID(player));
if (conv != null)
conv.suspend();
player.closeInventory();
}
// cancel database saver
saver.end();
compatibility.disable();
// stop global location listener
GlobalLocations.stop();
database.closeConnection();
// cancel static events (they are registered outside of Bukkit so it
// won't happen automatically)
StaticEvents.stop();
// update if needed
updater.updateBugfixes();
// done
Debug.broadcast("BetonQuest succesfully disabled!");
}
/**
* Returns the plugin's instance
*
* @return the plugin's instance
*/
public static BetonQuest getInstance() {
return instance;
}
/**
* Returns the database instance
*
* @return Database instance
*/
public Database getDB() {
return database;
}
public Updater getUpdater() {
return updater;
}
/**
* Checks if MySQL is used or not
*
* @return if MySQL is used (false means that SQLite is being used)
*/
public boolean isMySQLUsed() {
return isMySQLUsed;
}
/**
* Stores the PlayerData in a map, so it can be retrieved using
* getPlayerData(String playerID)
*
* @param playerID
* ID of the player
* @param playerData
* PlayerData object to store
*/
public void putPlayerData(String playerID, PlayerData playerData) {
Debug.info("Inserting data for " + PlayerConverter.getName(playerID));
playerDataMap.put(playerID, playerData);
}
/**
* Retrieves PlayerData object for specified player. If the playerData
* does not exist but the player is online, it will create new playerData on
* the main thread and put it into the map.
*
* @param playerID
* ID of the player
* @return PlayerData object for the player
*/
public PlayerData getPlayerData(String playerID) {
PlayerData playerData = playerDataMap.get(playerID);
if (playerData == null && PlayerConverter.getPlayer(playerID) != null) {
playerData = new PlayerData(playerID);
putPlayerData(playerID, playerData);
}
return playerData;
}
/**
* Removes the database playerData from the map
*
* @param playerID
* ID of the player whose playerData is to be removed
*/
public void removePlayerData(String playerID) {
playerDataMap.remove(playerID);
}
/**
* Registers new condition classes by their names
*
* @param name
* name of the condition type
* @param conditionClass
* class object for the condition
*/
public void registerConditions(String name, Class<? extends Condition> conditionClass) {
Debug.info("Registering " + name + " condition type");
conditionTypes.put(name, conditionClass);
}
/**
* Registers new event classes by their names
*
* @param name
* name of the event type
* @param eventClass
* class object for the condition
*/
public void registerEvents(String name, Class<? extends QuestEvent> eventClass) {
Debug.info("Registering " + name + " event type");
eventTypes.put(name, eventClass);
}
/**
* Registers new objective classes by their names
*
* @param name
* name of the objective type
* @param objectiveClass
* class object for the objective
*/
public void registerObjectives(String name, Class<? extends Objective> objectiveClass) {
Debug.info("Registering " + name + " objective type");
objectiveTypes.put(name, objectiveClass);
}
/**
* Registers new conversation input/output class.
*
* @param name
* name of the IO type
* @param convIOClass
* class object to register
*/
public void registerConversationIO(String name, Class<? extends ConversationIO> convIOClass) {
Debug.info("Registering " + name + " conversation IO type");
convIOTypes.put(name, convIOClass);
}
/**
* Registers new variable type.
*
* @param name
* name of the variable type
* @param variable
* class object of this type
*/
public void registerVariable(String name, Class<? extends Variable> variable) {
Debug.info("Registering " + name + " variable type");
variableTypes.put(name, variable);
}
/**
* Checks if the condition described by conditionID is met
*
* @param conditionID
* ID of the condition to check, as defined in conditions.yml
* @param playerID
* ID of the player which should be checked
* @return if the condition is met
*/
public static boolean condition(String playerID, ConditionID conditionID) {
// null check
if (conditionID == null) {
Debug.info("Null condition ID!");
return false;
}
// get the condition
Condition condition = null;
for (Entry<ConditionID, Condition> e : conditions.entrySet()) {
if (e.getKey().equals(conditionID)) {
condition = e.getValue();
break;
}
}
if (condition == null) {
Debug.error("The condition " + conditionID + " is not defined!");
return false;
}
// check for null player
if (playerID == null && !condition.isStatic()) {
Debug.info("Cannot check non-static condition without a player, returning false");
return false;
}
// check for online player
if (playerID != null && PlayerConverter.getPlayer(playerID) == null && !condition.isPersistent()) {
Debug.info("Player was offline, condition is not persistent, returning false");
return false;
}
// and check if it's met or not
boolean outcome = false;
try {
outcome = condition.check(playerID);
} catch (QuestRuntimeException e) {
Debug.error("Error while checking '" + conditionID + "' condition: " + e.getMessage());
return false;
}
boolean isMet = (outcome && !conditionID.inverted()) || (!outcome && conditionID.inverted());
Debug.info((isMet ? "TRUE" : "FALSE") + ": " + (conditionID.inverted() ? "inverted" : "") + " condition "
+ conditionID + " for player " + PlayerConverter.getName(playerID));
return isMet;
}
/**
* Fires the event described by eventID
*
* @param eventID
* ID of the event to fire, as defined in events.yml
* @param playerID
* ID of the player who the event is firing for
*/
public static void event(String playerID, EventID eventID) {
// null check
if (eventID == null) {
Debug.info("Null event ID!");
return;
}
// get the event
QuestEvent event = null;
for (Entry<EventID, QuestEvent> e : events.entrySet()) {
if (e.getKey().equals(eventID)) {
event = e.getValue();
break;
}
}
if (event == null) {
Debug.error("Event " + eventID + " is not defined");
return;
}
// fire the event
if (playerID == null) {
Debug.info("Firing static event " + eventID);
} else {
Debug.info("Firing event " + eventID + " for " + PlayerConverter.getName(playerID));
}
try {
event.fire(playerID);
} catch (QuestRuntimeException e) {
Debug.error("Error while firing '" + eventID + "' event: " + e.getMessage());
}
}
/**
* Creates new objective for given player
*
* @param playerID
* ID of the player
* @param objectiveID
* ID of the objective
*/
public static void newObjective(String playerID, ObjectiveID objectiveID) {
// null check
if (playerID == null || objectiveID == null) {
Debug.info("Null arguments for the objective!");
return;
}
Objective objective = null;
for (Entry<ObjectiveID, Objective> e : objectives.entrySet()) {
if (e.getKey().equals(objectiveID)) {
objective = e.getValue();
break;
}
}
if (objective.containsPlayer(playerID)) {
Debug.info("Player " + PlayerConverter.getName(playerID) + " already has the " + objectiveID +
" objective");
return;
}
objective.newPlayer(playerID);
}
/**
* Resumes the existing objective for given player
*
* @param playerID
* ID of the player
* @param objectiveID
* ID of the objective
* @param instruction
* data instruction string
*/
public static void resumeObjective(String playerID, ObjectiveID objectiveID, String instruction) {
// null check
if (playerID == null || objectiveID == null || instruction == null) {
Debug.info("Null arguments for the objective!");
return;
}
Objective objective = null;
for (Entry<ObjectiveID, Objective> e : objectives.entrySet()) {
if (e.getKey().equals(objectiveID)) {
objective = e.getValue();
break;
}
}
if (objective == null) {
Debug.error("Objective " + objectiveID + " does not exist");
return;
}
if (objective.containsPlayer(playerID)) {
Debug.info(
"Player " + PlayerConverter.getName(playerID) + " already has the " + objectiveID + " objective!");
return;
}
objective.addPlayer(playerID, instruction);
}
/**
* Generates new instance of a Variable. If a similar one was already
* created, it will return it instead of creating a new one.
*
* @param pack
* package in which the variable is defined
* @param instruction
* instruction of the variable, including both % characters.
* @return the Variable instance
* @throws InstructionParseException
* when the variable parsing fails
*/
public static Variable createVariable(ConfigPackage pack, String instruction) throws InstructionParseException {
VariableID ID;
try {
ID = new VariableID(pack, instruction);
} catch (ObjectNotFoundException e) {
throw new InstructionParseException("Could not load variable: " + e.getMessage());
}
// no need to create duplicated variables
for (Entry<VariableID, Variable> e : variables.entrySet()) {
if (e.getKey().equals(ID)) {
return e.getValue();
}
}
String[] parts = instruction.replace("%", "").split("\\.");
if (parts.length < 1) {
throw new InstructionParseException("Not enough arguments in variable " + ID);
}
Class<? extends Variable> variableClass = variableTypes.get(parts[0]);
// if it's null then there is no such type registered, log an error
if (variableClass == null) {
throw new InstructionParseException("Variable type " + parts[0] + " is not registered");
}
try {
Variable variable = variableClass.getConstructor(Instruction.class).newInstance(new VariableInstruction(pack, null, instruction));
variables.put(ID, variable);
Debug.info("Variable " + ID + " loaded");
return variable;
} catch (InvocationTargetException e) {
if (e.getCause() instanceof InstructionParseException) {
throw new InstructionParseException("Error in " + ID + " variable: " + e.getCause().getMessage());
} else {
e.printStackTrace();
Debug.error(ERROR);
}
} catch (Exception e) {
e.printStackTrace();
Debug.error(ERROR);
}
return null;
}
/**
* Resolves variables in the supplied text and returns them as a list of
* instruction strings, including % characters. Variables are unique, so if
* the user uses the same variables multiple times, the list will contain
* only one occurence of this variable.
*
* @param text
* text from which the variables will be resolved
* @return the list of unique variable instructions
*/
public static ArrayList<String> resolveVariables(String text) {
ArrayList<String> variables = new ArrayList<>();
boolean inside = false;
char[] charArr = text.toCharArray();
StringBuilder variable = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
if (inside) {
if (charArr[i] == ' ') {
// it's not a variable if it contains a space
inside = false;
variable = new StringBuilder();
}
variable.append(charArr[i]);
if (charArr[i] == '%') {
// end of the variable
inside = false;
String finalVariable = variable.toString();
variable = new StringBuilder();
if (!variables.contains(finalVariable)) {
variables.add(finalVariable);
}
}
} else {
if (charArr[i] == '%') {
inside = true;
variable.append('%');
}
}
}
return variables;
}
/**
* Returns the list of objectives of this player
*
* @param playerID
* ID of the player
* @return list of this player's active objectives
*/
public ArrayList<Objective> getPlayerObjectives(String playerID) {
ArrayList<Objective> list = new ArrayList<>();
for (Objective objective : objectives.values()) {
if (objective.containsPlayer(playerID)) {
list.add(objective);
}
}
return list;
}
/**
* @param name
* package name, dot and name of the conversation
* @return ConversationData object for this conversation or null if it does
* not exist
*/
public ConversationData getConversation(String name) {
return conversations.get(name);
}
/**
* @param objectiveID
* package name, dot and ID of the objective
* @return Objective object or null if it does not exist
*/
public Objective getObjective(ObjectiveID objectiveID) {
for (Entry<ObjectiveID, Objective> e : objectives.entrySet()) {
if (e.getKey().equals(objectiveID)) {
return e.getValue();
}
}
return null;
}
/**
* Returns the instance of Saver
*
* @return the Saver
*/
public Saver getSaver() {
return saver;
}
/**
* @param name
* name of the conversation IO type
* @return the class object for this conversation IO type
*/
public Class<? extends ConversationIO> getConvIO(String name) {
return convIOTypes.get(name);
}
/**
* Resoles the variable for specified player. If the variable is not loaded
* yet it will load it on the main thread.
*
* @param packName
* name of the package
* @param name
* name of the variable (instruction, with % characters)
* @param playerID
* ID of the player
* @return the value of this variable for given player
*/
public String getVariableValue(String packName, String name, String playerID) {
try {
Variable var = createVariable(Config.getPackages().get(packName), name);
if (var == null)
return "could not resolve variable";
return var.getValue(playerID);
} catch (InstructionParseException e) {
return "could not resolve variable";
}
}
/**
* @param name the name of the event class, as previously registered
* @return the class of the event
*/
public Class<? extends QuestEvent> getEventClass(String name) {
return eventTypes.get(name);
}
/**
* @param name the name of the condition class, as previously registered
* @return the class of the event
*/
public Class<? extends Condition> getConditionClass(String name) {
return conditionTypes.get(name);
}
/**
* Renames the objective instance.
*
* @param name
* the current name
* @param rename
* the name it should have now
*/
public void renameObjective(ObjectiveID name, ObjectiveID rename) {
objectives.put(rename, objectives.remove(name));
}
}