Add optional MySQL implementation, part 1. See #312

This commit is contained in:
PikaMug 2020-11-14 21:30:46 -05:00
parent 6d5d3deff6
commit ce9b24fe3a
16 changed files with 1355 additions and 389 deletions

View File

@ -13,7 +13,6 @@
package me.blackvein.quests;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
@ -36,7 +35,6 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.conversations.Conversable;
@ -61,6 +59,7 @@ import me.blackvein.quests.events.quest.QuestTakeEvent;
import me.blackvein.quests.events.quester.QuesterPostStartQuestEvent;
import me.blackvein.quests.events.quester.QuesterPreOpenGUIEvent;
import me.blackvein.quests.events.quester.QuesterPreStartQuestEvent;
import me.blackvein.quests.storage.Storage;
import me.blackvein.quests.tasks.StageTimer;
import me.blackvein.quests.util.ConfigUtil;
import me.blackvein.quests.util.InventoryUtil;
@ -2833,14 +2832,13 @@ public class Quester implements Comparable<Quester> {
* @return true if successful
*/
public boolean saveData() {
final FileConfiguration data = getBaseData();
try {
data.save(new File(plugin.getDataFolder(), "data" + File.separator + id + ".yml"));
return true;
} catch (final IOException e) {
e.printStackTrace();
final Storage storage = plugin.getStorage();
storage.saveQuesterData(this);
} catch (final Exception e) {
return false;
}
return false;
return true;
}
/**
@ -3200,7 +3198,7 @@ public class Quester implements Comparable<Quester> {
}
/**
* Check whether data file for this Quester exists
* Get data file for this Quester
*
* @return file if exists, otherwise null
*/
@ -3221,386 +3219,8 @@ public class Quester implements Comparable<Quester> {
*
* @return true if successful
*/
@SuppressWarnings("deprecation")
public boolean loadData() {
final FileConfiguration data = new YamlConfiguration();
try {
final File dataFile = getDataFile();
if (dataFile != null) {
data.load(dataFile);
} else {
return false;
}
} catch (final IOException e) {
return false;
} catch (final InvalidConfigurationException e) {
return false;
}
hardClear();
if (data.contains("completedRedoableQuests")) {
final List<String> redoNames = data.getStringList("completedRedoableQuests");
final List<Long> redoTimes = data.getLongList("completedQuestTimes");
for (final String s : redoNames) {
for (final Quest q : plugin.getQuests()) {
if (q.getName().equalsIgnoreCase(s)) {
completedTimes.put(q.getName(), redoTimes.get(redoNames.indexOf(s)));
break;
}
}
}
}
if (data.contains("amountsCompletedQuests")) {
final List<String> list1 = data.getStringList("amountsCompletedQuests");
final List<Integer> list2 = data.getIntegerList("amountsCompleted");
for (int i = 0; i < list1.size(); i++) {
amountsCompleted.put(list1.get(i), list2.get(i));
}
}
questPoints = data.getInt("quest-points");
hasJournal = data.getBoolean("hasJournal");
if (data.isList("completed-Quests")) {
for (final String s : data.getStringList("completed-Quests")) {
for (final Quest q : plugin.getQuests()) {
if (q.getName().equalsIgnoreCase(s)) {
if (!completedQuests.contains(q.getName())) {
completedQuests.add(q.getName());
}
break;
}
}
}
} else {
completedQuests.clear();
}
if (data.isString("currentQuests") == false) {
final List<String> questNames = data.getStringList("currentQuests");
final List<Integer> questStages = data.getIntegerList("currentStages");
// These appear to differ sometimes? That seems bad.
final int maxSize = Math.min(questNames.size(), questStages.size());
for (int i = 0; i < maxSize; i++) {
if (plugin.getQuest(questNames.get(i)) != null) {
currentQuests.put(plugin.getQuest(questNames.get(i)), questStages.get(i));
}
}
final ConfigurationSection dataSec = data.getConfigurationSection("questData");
if (dataSec == null || dataSec.getKeys(false).isEmpty()) {
return false;
}
for (final String key : dataSec.getKeys(false)) {
final ConfigurationSection questSec = dataSec.getConfigurationSection(key);
final Quest quest = plugin.getQuest(key);
Stage stage;
if (quest == null || currentQuests.containsKey(quest) == false) {
continue;
}
stage = getCurrentStage(quest);
if (stage == null) {
quest.completeQuest(this);
plugin.getLogger().severe("[Quests] Invalid stage number for player: \"" + id + "\" on Quest \""
+ quest.getName() + "\". Quest ended.");
continue;
}
addEmptiesFor(quest, currentQuests.get(quest));
if (questSec == null)
continue;
if (questSec.contains("blocks-broken-names")) {
final List<String> names = questSec.getStringList("blocks-broken-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-broken-amounts");
final List<Short> durability = questSec.getShortList("blocks-broken-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (getQuestData(quest).blocksBroken.size() > 0) {
getQuestData(quest).blocksBroken.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-damaged-names")) {
final List<String> names = questSec.getStringList("blocks-damaged-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-damaged-amounts");
final List<Short> durability = questSec.getShortList("blocks-damaged-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (getQuestData(quest).blocksDamaged.size() > 0) {
getQuestData(quest).blocksDamaged.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-placed-names")) {
final List<String> names = questSec.getStringList("blocks-placed-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-placed-amounts");
final List<Short> durability = questSec.getShortList("blocks-placed-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (getQuestData(quest).blocksPlaced.size() > 0) {
getQuestData(quest).blocksPlaced.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-used-names")) {
final List<String> names = questSec.getStringList("blocks-used-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-used-amounts");
final List<Short> durability = questSec.getShortList("blocks-used-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (getQuestData(quest).blocksUsed.size() > 0) {
getQuestData(quest).blocksUsed.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-cut-names")) {
final List<String> names = questSec.getStringList("blocks-cut-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-cut-amounts");
final List<Short> durability = questSec.getShortList("blocks-cut-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (getQuestData(quest).blocksCut.size() > 0) {
getQuestData(quest).blocksCut.set(index, is);
}
index++;
}
}
if (questSec.contains("item-craft-amounts")) {
final List<Integer> craftAmounts = questSec.getIntegerList("item-craft-amounts");
for (int i = 0; i < craftAmounts.size(); i++) {
if (i < getCurrentStage(quest).itemsToCraft.size()) {
getQuestData(quest).itemsCrafted.put(getCurrentStage(quest).itemsToCraft
.get(i), craftAmounts.get(i));
}
}
}
if (questSec.contains("item-smelt-amounts")) {
final List<Integer> smeltAmounts = questSec.getIntegerList("item-smelt-amounts");
for (int i = 0; i < smeltAmounts.size(); i++) {
if (i < getCurrentStage(quest).itemsToSmelt.size()) {
getQuestData(quest).itemsSmelted.put(getCurrentStage(quest).itemsToSmelt
.get(i), smeltAmounts.get(i));
}
}
}
if (questSec.contains("item-enchant-amounts")) {
final List<Integer> enchantAmounts = questSec.getIntegerList("item-enchant-amounts");
for (int i = 0; i < enchantAmounts.size(); i++) {
if (i < getCurrentStage(quest).itemsToEnchant.size()) {
getQuestData(quest).itemsEnchanted.put(getCurrentStage(quest).itemsToEnchant
.get(i), enchantAmounts.get(i));
}
}
}
if (questSec.contains("item-brew-amounts")) {
final List<Integer> brewAmounts = questSec.getIntegerList("item-brew-amounts");
for (int i = 0; i < brewAmounts.size(); i++) {
if (i < getCurrentStage(quest).itemsToBrew.size()) {
getQuestData(quest).itemsBrewed.put(getCurrentStage(quest).itemsToBrew
.get(i), brewAmounts.get(i));
}
}
}
if (questSec.contains("item-consume-amounts")) {
final List<Integer> consumeAmounts = questSec.getIntegerList("item-consume-amounts");
for (int i = 0; i < consumeAmounts.size(); i++) {
if (i < getCurrentStage(quest).itemsToConsume.size()) {
getQuestData(quest).itemsConsumed.put(getCurrentStage(quest).itemsToConsume
.get(i), consumeAmounts.get(i));
}
}
}
if (questSec.contains("cows-milked")) {
getQuestData(quest).setCowsMilked(questSec.getInt("cows-milked"));
}
if (questSec.contains("fish-caught")) {
getQuestData(quest).setFishCaught(questSec.getInt("fish-caught"));
}
if (questSec.contains("players-killed")) {
getQuestData(quest).setPlayersKilled(questSec.getInt("players-killed"));
}
if (questSec.contains("mobs-killed")) {
final LinkedList<EntityType> mobs = new LinkedList<EntityType>();
final List<Integer> amounts = questSec.getIntegerList("mobs-killed-amounts");
for (final String s : questSec.getStringList("mobs-killed")) {
final EntityType mob = MiscUtil.getProperMobType(s);
if (mob != null) {
mobs.add(mob);
}
getQuestData(quest).mobsKilled.clear();
getQuestData(quest).mobNumKilled.clear();
for (final EntityType e : mobs) {
getQuestData(quest).mobsKilled.add(e);
getQuestData(quest).mobNumKilled.add(amounts.get(mobs.indexOf(e)));
}
if (questSec.contains("mob-kill-locations")) {
final LinkedList<Location> locations = new LinkedList<Location>();
final List<Integer> radii = questSec.getIntegerList("mob-kill-location-radii");
for (final String loc : questSec.getStringList("mob-kill-locations")) {
if (ConfigUtil.getLocation(loc) != null) {
locations.add(ConfigUtil.getLocation(loc));
}
}
getQuestData(quest).locationsToKillWithin = locations;
getQuestData(quest).radiiToKillWithin.clear();
for (final int i : radii) {
getQuestData(quest).radiiToKillWithin.add(i);
}
}
}
}
if (questSec.contains("item-delivery-amounts")) {
final List<Integer> deliveryAmounts = questSec.getIntegerList("item-delivery-amounts");
int index = 0;
for (final int amt : deliveryAmounts) {
final ItemStack is = getCurrentStage(quest).itemsToDeliver.get(index);
final ItemStack temp = new ItemStack(is.getType(), amt, is.getDurability());
try {
temp.addEnchantments(is.getEnchantments());
} catch (final Exception e) {
plugin.getLogger().warning("Unable to add enchantment(s) " + is.getEnchantments().toString()
+ " to delivery item " + is.getType().name() + " x " + amt + " for quest "
+ quest.getName());
}
temp.setItemMeta(is.getItemMeta());
if (getQuestData(quest).itemsDelivered.size() > 0) {
getQuestData(quest).itemsDelivered.set(index, temp);
}
index++;
}
}
if (questSec.contains("citizen-ids-to-talk-to")) {
final List<Integer> ids = questSec.getIntegerList("citizen-ids-to-talk-to");
final List<Boolean> has = questSec.getBooleanList("has-talked-to");
for (final int i : ids) {
getQuestData(quest).citizensInteracted.put(i, has.get(ids.indexOf(i)));
}
}
if (questSec.contains("citizen-ids-killed")) {
final List<Integer> ids = questSec.getIntegerList("citizen-ids-killed");
final List<Integer> num = questSec.getIntegerList("citizen-amounts-killed");
getQuestData(quest).citizensKilled.clear();
getQuestData(quest).citizenNumKilled.clear();
for (final int i : ids) {
getQuestData(quest).citizensKilled.add(i);
getQuestData(quest).citizenNumKilled.add(num.get(ids.indexOf(i)));
}
}
if (questSec.contains("locations-to-reach")) {
final LinkedList<Location> locations = new LinkedList<Location>();
final List<Boolean> has = questSec.getBooleanList("has-reached-location");
while (has.size() < locations.size()) {
// TODO - Find proper cause of Github issues #646 and #825
plugin.getLogger().info("Added missing has-reached-location data for Quester " + id);
has.add(false);
}
final List<Integer> radii = questSec.getIntegerList("radii-to-reach-within");
for (final String loc : questSec.getStringList("locations-to-reach")) {
if (ConfigUtil.getLocation(loc) != null) {
locations.add(ConfigUtil.getLocation(loc));
}
}
getQuestData(quest).locationsReached = locations;
getQuestData(quest).hasReached.clear();
getQuestData(quest).radiiToReachWithin.clear();
for (final boolean b : has) {
getQuestData(quest).hasReached.add(b);
}
for (final int i : radii) {
getQuestData(quest).radiiToReachWithin.add(i);
}
}
if (questSec.contains("mobs-to-tame")) {
final List<String> mobs = questSec.getStringList("mobs-to-tame");
final List<Integer> amounts = questSec.getIntegerList("mob-tame-amounts");
for (final String mob : mobs) {
getQuestData(quest).mobsTamed.put(EntityType.valueOf(mob.toUpperCase()), amounts
.get(mobs.indexOf(mob)));
}
}
if (questSec.contains("sheep-to-shear")) {
final List<String> colors = questSec.getStringList("sheep-to-shear");
final List<Integer> amounts = questSec.getIntegerList("sheep-sheared");
for (final String color : colors) {
getQuestData(quest).sheepSheared.put(MiscUtil.getProperDyeColor(color), amounts.get(colors
.indexOf(color)));
}
}
if (questSec.contains("passwords")) {
final List<String> passwords = questSec.getStringList("passwords");
final List<Boolean> said = questSec.getBooleanList("passwords-said");
for (int i = 0; i < passwords.size(); i++) {
getQuestData(quest).passwordsSaid.put(passwords.get(i), said.get(i));
}
}
if (questSec.contains("custom-objectives")) {
final List<String> customObj = questSec.getStringList("custom-objectives");
final List<Integer> customObjCount = questSec.getIntegerList("custom-objective-counts");
for (int i = 0; i < customObj.size(); i++) {
getQuestData(quest).customObjectiveCounts.put(customObj.get(i), customObjCount.get(i));
}
}
if (questSec.contains("stage-delay")) {
getQuestData(quest).setDelayTimeLeft(questSec.getLong("stage-delay"));
}
if (getCurrentStage(quest).chatActions.isEmpty() == false) {
for (final String chatTrig : getCurrentStage(quest).chatActions.keySet()) {
getQuestData(quest).actionFired.put(chatTrig, false);
}
}
if (questSec.contains("chat-triggers")) {
final List<String> chatTriggers = questSec.getStringList("chat-triggers");
for (final String s : chatTriggers) {
getQuestData(quest).actionFired.put(s, true);
}
}
if (getCurrentStage(quest).commandActions.isEmpty() == false) {
for (final String commandTrig : getCurrentStage(quest).commandActions.keySet()) {
getQuestData(quest).actionFired.put(commandTrig, false);
}
}
if (questSec.contains("command-triggers")) {
final List<String> commandTriggers = questSec.getStringList("command-triggers");
for (final String s : commandTriggers) {
getQuestData(quest).actionFired.put(s, true);
}
}
}
}
return true;
return plugin.getStorage().loadQuesterData(id) != null;
}
/**

View File

@ -95,6 +95,8 @@ import me.blackvein.quests.listeners.ItemListener;
import me.blackvein.quests.listeners.NpcListener;
import me.blackvein.quests.listeners.PartiesListener;
import me.blackvein.quests.listeners.PlayerListener;
import me.blackvein.quests.storage.Storage;
import me.blackvein.quests.storage.StorageFactory;
import me.blackvein.quests.tasks.NpcEffectThread;
import me.blackvein.quests.tasks.PlayerMoveThread;
import me.blackvein.quests.util.ConfigUtil;
@ -136,6 +138,7 @@ public class Quests extends JavaPlugin implements ConversationAbandonedListener
private PartiesListener partiesListener;
private DenizenTrigger trigger;
private LocaleQuery localeQuery;
private Storage storage;
@Override
public void onEnable() {
@ -186,6 +189,8 @@ public class Quests extends JavaPlugin implements ConversationAbandonedListener
// 7 - Save config with any new options
getConfig().options().copyDefaults(true);
saveConfig();
final StorageFactory storageFactory = new StorageFactory(this);
storage = storageFactory.getInstance();
// 8 - Setup commands
getCommand("quests").setExecutor(cmdExecutor);
@ -418,6 +423,10 @@ public class Quests extends JavaPlugin implements ConversationAbandonedListener
public LocaleQuery getLocaleQuery() {
return localeQuery;
}
public Storage getStorage() {
return storage;
}
@Override
public void conversationAbandoned(final ConversationAbandonedEvent abandonedEvent) {

View File

@ -1380,7 +1380,7 @@ public class CmdExecutor implements CommandExecutor {
}
final UUID id = target.getUniqueId();
final ConcurrentSkipListSet<Quester> temp = (ConcurrentSkipListSet<Quester>) plugin.getOfflineQuesters();
for(final Iterator<Quester> itr = temp.iterator(); itr.hasNext();) {
for (final Iterator<Quester> itr = temp.iterator(); itr.hasNext();) {
if (itr.next().getUUID().equals(id)) {
itr.remove();
}

View File

@ -0,0 +1,121 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import me.blackvein.quests.Quester;
import me.blackvein.quests.Quests;
import me.blackvein.quests.storage.implementation.StorageImplementation;
public class Storage {
private final Quests plugin;
private final StorageImplementation implementation;
public Storage(final Quests plugin, final StorageImplementation implementation) {
this.plugin = plugin;
this.implementation = implementation;
}
public StorageImplementation getImplementation() {
return implementation;
}
public Collection<StorageImplementation> getImplementations() {
return Collections.singleton(implementation);
}
private <T> CompletableFuture<T> makeFuture(final Callable<T> supplier) {
return CompletableFuture.supplyAsync(() -> {
try {
return supplier.call();
} catch (final Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
});
}
private CompletableFuture<Void> makeFuture(final Runnable runnable) {
return CompletableFuture.runAsync(() -> {
try {
runnable.run();
} catch (final Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new CompletionException(e);
}
});
}
public String getName() {
return implementation.getImplementationName();
}
public void init() {
try {
implementation.init();
} catch (final Exception e) {
plugin.getLogger().severe("Failed to initialize storage implementation");
e.printStackTrace();
}
}
public void close() {
try {
implementation.close();
} catch (final Exception e) {
plugin.getLogger().severe("Failed to close storage implementation");
e.printStackTrace();
}
}
public CompletableFuture<Quester> loadQuesterData(final UUID uniqueId) {
return makeFuture(() -> {
final Quester quester = implementation.loadQuesterData(uniqueId);
return quester;
});
}
public CompletableFuture<Void> saveQuesterData(final Quester quester) {
return makeFuture(() -> {
try {
implementation.saveQuesterData(quester);
} catch (final Exception e) {
e.printStackTrace();
}
});
}
public CompletableFuture<Void> deletePlayerData(final UUID uniqueId) {
return makeFuture(() -> {
try {
implementation.deleteQuesterData(uniqueId);
} catch (final Exception e) {
e.printStackTrace();
}
});
}
public CompletableFuture<String> getQuesterLastKnownName(final UUID uniqueId) {
return makeFuture(() -> implementation.getQuesterLastKnownName(uniqueId));
}
}

View File

@ -0,0 +1,84 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage;
import java.io.File;
import java.util.Collections;
import java.util.Set;
import org.bukkit.configuration.file.FileConfiguration;
import com.google.common.collect.ImmutableSet;
import me.blackvein.quests.Quests;
import me.blackvein.quests.storage.implementation.StorageImplementation;
import me.blackvein.quests.storage.implementation.custom.CustomStorageProviders;
import me.blackvein.quests.storage.implementation.file.SeparatedYamlStorage;
import me.blackvein.quests.storage.implementation.sql.SqlStorage;
import me.blackvein.quests.storage.implementation.sql.connection.hikari.MySqlConnectionFactory;
import me.blackvein.quests.storage.misc.StorageCredentials;
public class StorageFactory {
private final Quests plugin;
public StorageFactory(final Quests plugin) {
this.plugin = plugin;
}
public Set<StorageType> getRequiredTypes() {
return ImmutableSet.of(StorageType.parse(plugin.getConfig().getString("storage-method", "yaml"), StorageType.YAML));
}
public Storage getInstance() {
Storage storage;
final StorageType type = StorageType.parse(plugin.getConfig().getString("storage-method", "yaml"), StorageType.YAML);
plugin.getLogger().info("Loading storage implementation: " + type.name());
storage = new Storage(plugin, createNewImplementation(type));
storage.init();
return storage;
}
private StorageImplementation createNewImplementation(final StorageType method) {
switch (method) {
case CUSTOM:
return CustomStorageProviders.getProvider().provide(plugin);
case MYSQL:
return new SqlStorage(
plugin,
new MySqlConnectionFactory(getDatabaseValues(plugin.getConfig())),
plugin.getConfig().getString("storage-data.table_prefix")
);
case YAML:
return new SeparatedYamlStorage(plugin, plugin.getDataFolder() + File.separator + "data");
default:
throw new RuntimeException("Unknown method: " + method);
}
}
private StorageCredentials getDatabaseValues(final FileConfiguration fc) {
final int maxPoolSize = fc.getInt("storage-data.pool-settings.max-pool-size", fc.getInt("storage-data.pool-size", 10));
final int minIdle = fc.getInt("storage-data.pool-settings.min-idle", maxPoolSize);
final int maxLifetime = fc.getInt("storage-data.pool-settings.max-lifetime", 1800000);
final int connectionTimeout = fc.getInt("storage-data.pool-settings.connection-timeout", 5000);
return new StorageCredentials(
fc.getString("storage-data.address", null),
fc.getString("storage-data.database", null),
fc.getString("storage-data.username", null),
fc.getString("storage-data.password", null),
maxPoolSize, minIdle, maxLifetime, connectionTimeout, Collections.singletonMap("true", "utf8")
);
}
}

View File

@ -0,0 +1,57 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage;
import java.util.List;
import com.google.common.collect.ImmutableList;
public enum StorageType {
// Local file
YAML("YAML", "yaml", "yml"),
// Remote database
MYSQL("MySQL", "mysql"),
// Custom
CUSTOM("Custom", "custom");
private final String name;
private final List<String> identifiers;
StorageType(final String name, final String... identifiers) {
this.name = name;
this.identifiers = ImmutableList.copyOf(identifiers);
}
public static StorageType parse(final String name, final StorageType def) {
for (final StorageType t : values()) {
for (final String id : t.getIdentifiers()) {
if (id.equalsIgnoreCase(name)) {
return t;
}
}
}
return def;
}
public String getName() {
return name;
}
public List<String> getIdentifiers() {
return identifiers;
}
}

View File

@ -0,0 +1,36 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation;
import java.util.UUID;
import me.blackvein.quests.Quester;
import me.blackvein.quests.Quests;
public interface StorageImplementation {
Quests getPlugin();
String getImplementationName();
void init() throws Exception;
void close();
Quester loadQuesterData(UUID uniqueId) throws Exception;
void saveQuesterData(Quester quester) throws Exception;
void deleteQuesterData(UUID uniqueId) throws Exception;
String getQuesterLastKnownName(UUID uniqueId) throws Exception;
}

View File

@ -0,0 +1,20 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.custom;
import me.blackvein.quests.Quests;
import me.blackvein.quests.storage.implementation.StorageImplementation;
public interface CustomStorageProvider {
StorageImplementation provide(Quests plugin);
}

View File

@ -0,0 +1,31 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.custom;
public final class CustomStorageProviders {
private CustomStorageProviders() {}
private static CustomStorageProvider provider = null;
public static void register(final CustomStorageProvider provider) {
CustomStorageProviders.provider = provider;
}
public static CustomStorageProvider getProvider() {
if (provider == null) {
throw new IllegalStateException("Provider not found.");
}
return provider;
}
}

View File

@ -0,0 +1,498 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.file;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import me.blackvein.quests.Quest;
import me.blackvein.quests.Quester;
import me.blackvein.quests.Quests;
import me.blackvein.quests.Stage;
import me.blackvein.quests.storage.implementation.StorageImplementation;
import me.blackvein.quests.util.ConfigUtil;
import me.blackvein.quests.util.MiscUtil;
public class SeparatedYamlStorage implements StorageImplementation {
private final Quests plugin;
private final String directoryPath;
public SeparatedYamlStorage(final Quests plugin, final String directoryPath) {
this.plugin = plugin;
this.directoryPath = directoryPath;
}
@Override
public Quests getPlugin() {
return plugin;
}
@Override
public String getImplementationName() {
return "YAML";
}
@Override
public void init() throws Exception {
// TODO Auto-generated method stub
}
@Override
public void close() {
// TODO Auto-generated method stub
}
@SuppressWarnings("deprecation")
@Override
public Quester loadQuesterData(final UUID uniqueId) throws Exception {
final FileConfiguration data = new YamlConfiguration();
Quester quester = plugin.getQuester(uniqueId);
if (quester != null) {
quester.hardClear();
} else {
quester = new Quester(plugin, uniqueId);
}
try {
final File dataFile = quester.getDataFile();
if (dataFile != null) {
data.load(dataFile);
} else {
return null;
}
} catch (final IOException e) {
e.printStackTrace();
return null;
} catch (final InvalidConfigurationException e) {
e.printStackTrace();
return null;
}
if (data.contains("completedRedoableQuests")) {
final List<String> redoNames = data.getStringList("completedRedoableQuests");
final List<Long> redoTimes = data.getLongList("completedQuestTimes");
for (final String s : redoNames) {
for (final Quest q : plugin.getQuests()) {
if (q.getName().equalsIgnoreCase(s)) {
final Map<String, Long> completedTimes = quester.getCompletedTimes();
completedTimes.put(q.getName(), redoTimes.get(redoNames.indexOf(s)));
quester.setCompletedTimes(completedTimes);
break;
}
}
}
}
if (data.contains("amountsCompletedQuests")) {
final List<String> list1 = data.getStringList("amountsCompletedQuests");
final List<Integer> list2 = data.getIntegerList("amountsCompleted");
for (int i = 0; i < list1.size(); i++) {
final Map<String, Integer> amountsCompleted = quester.getAmountsCompleted();
amountsCompleted.put(list1.get(i), list2.get(i));
quester.setAmountsCompleted(amountsCompleted);
}
}
int questPoints = quester.getQuestPoints();
questPoints = data.getInt("quest-points");
quester.setQuestPoints(questPoints);
quester.hasJournal = data.getBoolean("hasJournal");
if (data.isList("completed-Quests")) {
for (final String s : data.getStringList("completed-Quests")) {
for (final Quest q : plugin.getQuests()) {
if (q.getName().equalsIgnoreCase(s)) {
if (!quester.getCompletedQuests().contains(q.getName())) {
final LinkedList<String> completedQuests = quester.getCompletedQuests();
completedQuests.add(q.getName());
quester.setCompletedQuests(completedQuests);
}
break;
}
}
}
} else {
quester.setCompletedQuests(new LinkedList<String>());
}
if (data.isString("currentQuests") == false) {
final List<String> questNames = data.getStringList("currentQuests");
final List<Integer> questStages = data.getIntegerList("currentStages");
// These appear to differ sometimes? That seems bad.
final int maxSize = Math.min(questNames.size(), questStages.size());
for (int i = 0; i < maxSize; i++) {
if (plugin.getQuest(questNames.get(i)) != null) {
final ConcurrentHashMap<Quest, Integer> currentQuests = quester.getCurrentQuests();
currentQuests.put(plugin.getQuest(questNames.get(i)), questStages.get(i));
quester.setCurrentQuests(currentQuests);
}
}
final ConfigurationSection dataSec = data.getConfigurationSection("questData");
if (dataSec == null || dataSec.getKeys(false).isEmpty()) {
return null;
}
for (final String key : dataSec.getKeys(false)) {
final ConfigurationSection questSec = dataSec.getConfigurationSection(key);
final Quest quest = plugin.getQuest(key);
Stage stage;
if (quest == null || quester.getCurrentQuests().containsKey(quest) == false) {
continue;
}
stage = quester.getCurrentStage(quest);
if (stage == null) {
quest.completeQuest(quester);
plugin.getLogger().severe("[Quests] Invalid stage number for player: \"" + uniqueId + "\" on Quest \""
+ quest.getName() + "\". Quest ended.");
continue;
}
quester.addEmptiesFor(quest, quester.getCurrentQuests().get(quest));
if (questSec == null)
continue;
if (questSec.contains("blocks-broken-names")) {
final List<String> names = questSec.getStringList("blocks-broken-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-broken-amounts");
final List<Short> durability = questSec.getShortList("blocks-broken-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (quester.getQuestData(quest).blocksBroken.size() > 0) {
quester.getQuestData(quest).blocksBroken.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-damaged-names")) {
final List<String> names = questSec.getStringList("blocks-damaged-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-damaged-amounts");
final List<Short> durability = questSec.getShortList("blocks-damaged-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (quester.getQuestData(quest).blocksDamaged.size() > 0) {
quester.getQuestData(quest).blocksDamaged.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-placed-names")) {
final List<String> names = questSec.getStringList("blocks-placed-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-placed-amounts");
final List<Short> durability = questSec.getShortList("blocks-placed-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (quester.getQuestData(quest).blocksPlaced.size() > 0) {
quester.getQuestData(quest).blocksPlaced.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-used-names")) {
final List<String> names = questSec.getStringList("blocks-used-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-used-amounts");
final List<Short> durability = questSec.getShortList("blocks-used-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (quester.getQuestData(quest).blocksUsed.size() > 0) {
quester.getQuestData(quest).blocksUsed.set(index, is);
}
index++;
}
}
if (questSec.contains("blocks-cut-names")) {
final List<String> names = questSec.getStringList("blocks-cut-names");
final List<Integer> amounts = questSec.getIntegerList("blocks-cut-amounts");
final List<Short> durability = questSec.getShortList("blocks-cut-durability");
int index = 0;
for (final String s : names) {
ItemStack is;
try {
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), durability.get(index));
} catch (final IndexOutOfBoundsException e) {
// Legacy
is = new ItemStack(Material.matchMaterial(s), amounts.get(index), (short) 0);
}
if (quester.getQuestData(quest).blocksCut.size() > 0) {
quester.getQuestData(quest).blocksCut.set(index, is);
}
index++;
}
}
if (questSec.contains("item-craft-amounts")) {
final List<Integer> craftAmounts = questSec.getIntegerList("item-craft-amounts");
for (int i = 0; i < craftAmounts.size(); i++) {
if (i < quester.getCurrentStage(quest).getItemsToCraft().size()) {
quester.getQuestData(quest).itemsCrafted.put(quester.getCurrentStage(quest)
.getItemsToCraft().get(i), craftAmounts.get(i));
}
}
}
if (questSec.contains("item-smelt-amounts")) {
final List<Integer> smeltAmounts = questSec.getIntegerList("item-smelt-amounts");
for (int i = 0; i < smeltAmounts.size(); i++) {
if (i < quester.getCurrentStage(quest).getItemsToSmelt().size()) {
quester.getQuestData(quest).itemsSmelted.put(quester.getCurrentStage(quest)
.getItemsToSmelt().get(i), smeltAmounts.get(i));
}
}
}
if (questSec.contains("item-enchant-amounts")) {
final List<Integer> enchantAmounts = questSec.getIntegerList("item-enchant-amounts");
for (int i = 0; i < enchantAmounts.size(); i++) {
if (i < quester.getCurrentStage(quest).getItemsToEnchant().size()) {
quester.getQuestData(quest).itemsEnchanted.put(quester.getCurrentStage(quest)
.getItemsToEnchant().get(i), enchantAmounts.get(i));
}
}
}
if (questSec.contains("item-brew-amounts")) {
final List<Integer> brewAmounts = questSec.getIntegerList("item-brew-amounts");
for (int i = 0; i < brewAmounts.size(); i++) {
if (i < quester.getCurrentStage(quest).getItemsToBrew().size()) {
quester.getQuestData(quest).itemsBrewed.put(quester.getCurrentStage(quest)
.getItemsToBrew().get(i), brewAmounts.get(i));
}
}
}
if (questSec.contains("item-consume-amounts")) {
final List<Integer> consumeAmounts = questSec.getIntegerList("item-consume-amounts");
for (int i = 0; i < consumeAmounts.size(); i++) {
if (i < quester.getCurrentStage(quest).getItemsToConsume().size()) {
quester.getQuestData(quest).itemsConsumed.put(quester.getCurrentStage(quest)
.getItemsToConsume().get(i), consumeAmounts.get(i));
}
}
}
if (questSec.contains("cows-milked")) {
quester.getQuestData(quest).setCowsMilked(questSec.getInt("cows-milked"));
}
if (questSec.contains("fish-caught")) {
quester.getQuestData(quest).setFishCaught(questSec.getInt("fish-caught"));
}
if (questSec.contains("players-killed")) {
quester.getQuestData(quest).setPlayersKilled(questSec.getInt("players-killed"));
}
if (questSec.contains("mobs-killed")) {
final LinkedList<EntityType> mobs = new LinkedList<EntityType>();
final List<Integer> amounts = questSec.getIntegerList("mobs-killed-amounts");
for (final String s : questSec.getStringList("mobs-killed")) {
final EntityType mob = MiscUtil.getProperMobType(s);
if (mob != null) {
mobs.add(mob);
}
quester.getQuestData(quest).mobsKilled.clear();
quester.getQuestData(quest).mobNumKilled.clear();
for (final EntityType e : mobs) {
quester.getQuestData(quest).mobsKilled.add(e);
quester.getQuestData(quest).mobNumKilled.add(amounts.get(mobs.indexOf(e)));
}
if (questSec.contains("mob-kill-locations")) {
final LinkedList<Location> locations = new LinkedList<Location>();
final List<Integer> radii = questSec.getIntegerList("mob-kill-location-radii");
for (final String loc : questSec.getStringList("mob-kill-locations")) {
if (ConfigUtil.getLocation(loc) != null) {
locations.add(ConfigUtil.getLocation(loc));
}
}
quester.getQuestData(quest).locationsToKillWithin = locations;
quester.getQuestData(quest).radiiToKillWithin.clear();
for (final int i : radii) {
quester.getQuestData(quest).radiiToKillWithin.add(i);
}
}
}
}
if (questSec.contains("item-delivery-amounts")) {
final List<Integer> deliveryAmounts = questSec.getIntegerList("item-delivery-amounts");
int index = 0;
for (final int amt : deliveryAmounts) {
final ItemStack is = quester.getCurrentStage(quest).getItemsToDeliver().get(index);
final ItemStack temp = new ItemStack(is.getType(), amt, is.getDurability());
try {
temp.addEnchantments(is.getEnchantments());
} catch (final Exception e) {
plugin.getLogger().warning("Unable to add enchantment(s) " + is.getEnchantments().toString()
+ " to delivery item " + is.getType().name() + " x " + amt + " for quest "
+ quest.getName());
}
temp.setItemMeta(is.getItemMeta());
if (quester.getQuestData(quest).itemsDelivered.size() > 0) {
quester.getQuestData(quest).itemsDelivered.set(index, temp);
}
index++;
}
}
if (questSec.contains("citizen-ids-to-talk-to")) {
final List<Integer> ids = questSec.getIntegerList("citizen-ids-to-talk-to");
final List<Boolean> has = questSec.getBooleanList("has-talked-to");
for (final int i : ids) {
quester.getQuestData(quest).citizensInteracted.put(i, has.get(ids.indexOf(i)));
}
}
if (questSec.contains("citizen-ids-killed")) {
final List<Integer> ids = questSec.getIntegerList("citizen-ids-killed");
final List<Integer> num = questSec.getIntegerList("citizen-amounts-killed");
quester.getQuestData(quest).citizensKilled.clear();
quester.getQuestData(quest).citizenNumKilled.clear();
for (final int i : ids) {
quester.getQuestData(quest).citizensKilled.add(i);
quester.getQuestData(quest).citizenNumKilled.add(num.get(ids.indexOf(i)));
}
}
if (questSec.contains("locations-to-reach")) {
final LinkedList<Location> locations = new LinkedList<Location>();
final List<Boolean> has = questSec.getBooleanList("has-reached-location");
while (has.size() < locations.size()) {
// TODO - Find proper cause of Github issues #646 and #825
plugin.getLogger().info("Added missing has-reached-location data for Quester " + uniqueId);
has.add(false);
}
final List<Integer> radii = questSec.getIntegerList("radii-to-reach-within");
for (final String loc : questSec.getStringList("locations-to-reach")) {
if (ConfigUtil.getLocation(loc) != null) {
locations.add(ConfigUtil.getLocation(loc));
}
}
quester.getQuestData(quest).locationsReached = locations;
quester.getQuestData(quest).hasReached.clear();
quester.getQuestData(quest).radiiToReachWithin.clear();
for (final boolean b : has) {
quester.getQuestData(quest).hasReached.add(b);
}
for (final int i : radii) {
quester.getQuestData(quest).radiiToReachWithin.add(i);
}
}
if (questSec.contains("mobs-to-tame")) {
final List<String> mobs = questSec.getStringList("mobs-to-tame");
final List<Integer> amounts = questSec.getIntegerList("mob-tame-amounts");
for (final String mob : mobs) {
quester.getQuestData(quest).mobsTamed.put(EntityType.valueOf(mob.toUpperCase()), amounts
.get(mobs.indexOf(mob)));
}
}
if (questSec.contains("sheep-to-shear")) {
final List<String> colors = questSec.getStringList("sheep-to-shear");
final List<Integer> amounts = questSec.getIntegerList("sheep-sheared");
for (final String color : colors) {
quester.getQuestData(quest).sheepSheared.put(MiscUtil.getProperDyeColor(color), amounts.get(colors
.indexOf(color)));
}
}
if (questSec.contains("passwords")) {
final List<String> passwords = questSec.getStringList("passwords");
final List<Boolean> said = questSec.getBooleanList("passwords-said");
for (int i = 0; i < passwords.size(); i++) {
quester.getQuestData(quest).passwordsSaid.put(passwords.get(i), said.get(i));
}
}
if (questSec.contains("custom-objectives")) {
final List<String> customObj = questSec.getStringList("custom-objectives");
final List<Integer> customObjCount = questSec.getIntegerList("custom-objective-counts");
for (int i = 0; i < customObj.size(); i++) {
quester.getQuestData(quest).customObjectiveCounts.put(customObj.get(i), customObjCount.get(i));
}
}
if (questSec.contains("stage-delay")) {
quester.getQuestData(quest).setDelayTimeLeft(questSec.getLong("stage-delay"));
}
if (quester.getCurrentStage(quest).getChatActions().isEmpty() == false) {
for (final String chatTrig : quester.getCurrentStage(quest).getChatActions().keySet()) {
quester.getQuestData(quest).actionFired.put(chatTrig, false);
}
}
if (questSec.contains("chat-triggers")) {
final List<String> chatTriggers = questSec.getStringList("chat-triggers");
for (final String s : chatTriggers) {
quester.getQuestData(quest).actionFired.put(s, true);
}
}
if (quester.getCurrentStage(quest).getCommandActions().isEmpty() == false) {
for (final String commandTrig : quester.getCurrentStage(quest).getCommandActions().keySet()) {
quester.getQuestData(quest).actionFired.put(commandTrig, false);
}
}
if (questSec.contains("command-triggers")) {
final List<String> commandTriggers = questSec.getStringList("command-triggers");
for (final String s : commandTriggers) {
quester.getQuestData(quest).actionFired.put(s, true);
}
}
}
}
return quester;
}
@Override
public void saveQuesterData(final Quester quester) throws Exception {
final FileConfiguration data = quester.getBaseData();
try {
data.save(new File(directoryPath + File.separator + quester.getUUID() + ".yml"));
} catch (final IOException e) {
e.printStackTrace();
}
}
@Override
public void deleteQuesterData(final UUID uniqueId) throws Exception {
final File f = new File(directoryPath + File.separator + uniqueId + ".yml");
f.delete();
}
@Override
public String getQuesterLastKnownName(final UUID uniqueId) throws Exception {
final FileConfiguration data = new YamlConfiguration();
Quester quester = plugin.getQuester(uniqueId);
if (quester != null) {
quester.hardClear();
} else {
quester = new Quester(plugin, uniqueId);
}
return data.getString("lastKnownName");
}
}

View File

@ -0,0 +1,149 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.sql;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.UUID;
import java.util.function.Function;
import me.blackvein.quests.Quester;
import me.blackvein.quests.Quests;
import me.blackvein.quests.storage.implementation.StorageImplementation;
import me.blackvein.quests.storage.implementation.sql.connection.ConnectionFactory;
public class SqlStorage implements StorageImplementation {
private static final String PLAYER_SELECT = "SELECT id, hasjournal, FROM '{prefix}players' WHERE uuid=?";
private static final String PLAYER_SELECT_USERNAME_BY_UUID = "SELECT username FROM '{prefix}players' WHERE uuid=? LIMIT 1";
private static final String PLAYER_UPDATE_USERNAME_FOR_UUID = "UPDATE '{prefix}players' SET username=? WHERE uuid=?";
private static final String PLAYER_INSERT = "INSERT INTO '{prefix}players' (uuid, username, hasjournal) VALUES(?, ?, ?) ON DUPLICATE KEY UPDATE";
private static final String PLAYER_DELETE = "DELETE FROM '{prefix}players' WHERE uuid=?";
private final Quests plugin;
private final ConnectionFactory connectionFactory;
private final Function<String, String> statementProcessor;
public SqlStorage(final Quests plugin, final ConnectionFactory connectionFactory, final String tablePrefix) {
this.plugin = plugin;
this.connectionFactory = connectionFactory;
this.statementProcessor = connectionFactory.getStatementProcessor().compose(s -> s.replace("{prefix}", tablePrefix));
}
@Override
public Quests getPlugin() {
return plugin;
}
@Override
public String getImplementationName() {
return connectionFactory.getImplementationName();
}
public ConnectionFactory getConnectionFactory() {
return connectionFactory;
}
public Function<String, String> getStatementProcessor() {
return statementProcessor;
}
@Override
public void init() throws Exception {
connectionFactory.init(plugin);
}
@Override
public void close() {
try {
connectionFactory.close();
} catch (final Exception e) {
this.plugin.getLogger().severe("Problem occurred while closing SQL storage");
e.printStackTrace();
}
}
@Override
public Quester loadQuesterData(final UUID uniqueId) throws Exception {
final Quester quester = new Quester(plugin, uniqueId);
try (Connection c = connectionFactory.getConnection()) {
if (uniqueId != null) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT))) {
ps.setString(1, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
final boolean hasJournal = rs.getBoolean("hasjournal");
quester.hasJournal = hasJournal;
// TODO the rest
}
}
}
}
}
return quester;
}
@Override
public void saveQuesterData(final Quester quester) throws Exception {
final UUID uniqueId = quester.getUUID();
final String username = quester.getPlayer().getName();
final String oldUsername = getQuesterLastKnownName(uniqueId);
try (Connection c = connectionFactory.getConnection()) {
if (oldUsername != null && !username.equals(oldUsername)) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
}
} else {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
ps.setString(1, uniqueId.toString());
ps.setString(2, username);
ps.setBoolean(3, quester.hasJournal);
ps.execute();
}
}
}
if (!username.equals(oldUsername)) {
}
}
@Override
public void deleteQuesterData(final UUID uniqueId) throws Exception {
try (Connection c = connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE))) {
ps.setString(1, uniqueId.toString());
ps.execute();
}
}
}
@Override
public String getQuesterLastKnownName(final UUID uniqueId) throws Exception {
try (Connection c = connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_USERNAME_BY_UUID))) {
ps.setString(1, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString("username");
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,37 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.sql.connection;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import me.blackvein.quests.Quests;
public interface ConnectionFactory {
String getImplementationName();
void init(Quests plugin);
void close() throws Exception;
default Map<String, String> getMeta() {
return Collections.emptyMap();
}
Function<String, String> getStatementProcessor();
Connection getConnection() throws SQLException;
}

View File

@ -0,0 +1,141 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.sql.connection.hikari;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.bukkit.plugin.java.JavaPlugin;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import me.blackvein.quests.Quests;
import me.blackvein.quests.storage.implementation.sql.connection.ConnectionFactory;
import me.blackvein.quests.storage.misc.StorageCredentials;
public abstract class HikariConnectionFactory implements ConnectionFactory {
private final StorageCredentials configuration;
private HikariDataSource hikari;
public HikariConnectionFactory(final StorageCredentials configuration) {
this.configuration = configuration;
}
/**
* Gets the default port used by the database
*
* @return the default port
*/
protected abstract String defaultPort();
/**
* Configures the {@link HikariConfig} with the relevant database properties.
*
* <p>Each driver does this slightly differently.</p>
*
* @param config the hikari config
* @param address the database address
* @param port the database port
* @param databaseName the database name
* @param username the database username
* @param password the database password
*/
protected abstract void configureDatabase(HikariConfig config, String address, String port, String databaseName,
String username, String password);
/**
* Allows the connection factory instance to override certain properties before they are set.
*
* @param properties the current properties
*/
protected void overrideProperties(final Map<String, String> properties) {
// https://github.com/brettwooldridge/HikariCP/wiki/Rapid-Recovery
properties.putIfAbsent("socketTimeout", String.valueOf(TimeUnit.SECONDS.toMillis(30)));
}
/**
* Sets the given connection properties onto the config.
*
* @param config the hikari config
* @param properties the properties
*/
protected void setProperties(final HikariConfig config, final Map<String, String> properties) {
for (final Map.Entry<String, String> property : properties.entrySet()) {
config.addDataSourceProperty(property.getKey(), property.getValue());
}
}
@Override
public void init(final Quests plugin) {
final HikariConfig config = new HikariConfig();
config.setPoolName("quests-hikari");
final String[] addressSplit = configuration.getAddress().split(":");
final String address = addressSplit[0];
final String port = addressSplit.length > 1 ? addressSplit[1] : defaultPort();
configureDatabase(config, address, port, configuration.getDatabase(), configuration.getUsername(),
configuration.getPassword());
final Map<String, String> properties = new HashMap<>(this.configuration.getProperties());
overrideProperties(properties);
setProperties(config, properties);
config.setMaximumPoolSize(this.configuration.getMaxPoolSize());
config.setMinimumIdle(this.configuration.getMinIdleConnections());
config.setMaxLifetime(this.configuration.getMaxLifetime());
config.setConnectionTimeout(this.configuration.getConnectionTimeout());
hikari = new HikariDataSource(config);
}
@Override
public void close() {
if (hikari != null) {
hikari.close();
}
}
@Override
public Connection getConnection() throws SQLException {
if (hikari == null) {
throw new SQLException("Unable to get a connection from the pool because hikari is null");
}
final Connection connection = hikari.getConnection();
if (connection == null) {
throw new SQLException("Unable to get a connection from the pool because getConnection returned null");
}
return connection;
}
public static String identifyClassLoader(final ClassLoader classLoader) throws ReflectiveOperationException {
final Class<?> pluginClassLoaderClass = Class.forName("org.bukkit.plugin.java.PluginClassLoader");
if (pluginClassLoaderClass.isInstance(classLoader)) {
final Method getPluginMethod = pluginClassLoaderClass.getDeclaredMethod("getPlugin");
getPluginMethod.setAccessible(true);
final JavaPlugin plugin = (JavaPlugin) getPluginMethod.invoke(classLoader);
return plugin.getName();
}
return null;
}
}

View File

@ -0,0 +1,72 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.implementation.sql.connection.hikari;
import java.util.Map;
import java.util.function.Function;
import com.zaxxer.hikari.HikariConfig;
import me.blackvein.quests.storage.misc.StorageCredentials;
public class MySqlConnectionFactory extends HikariConnectionFactory {
public MySqlConnectionFactory(final StorageCredentials configuration) {
super(configuration);
}
@Override
public String getImplementationName() {
return "MySQL";
}
@Override
protected String defaultPort() {
return "3306";
}
@Override
protected void configureDatabase(final HikariConfig config, final String address, final String port,
final String databaseName, final String username, final String password) {
config.setDriverClassName("com.mysql.cj.jdbc.NonRegisteringDriver");
config.setJdbcUrl("jdbc:mysql://" + address + ":" + port + "/" + databaseName);
config.setUsername(username);
config.setPassword(password);
}
@Override
protected void overrideProperties(final Map<String, String> properties) {
// https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
properties.putIfAbsent("cachePrepStmts", "true");
properties.putIfAbsent("prepStmtCacheSize", "250");
properties.putIfAbsent("prepStmtCacheSqlLimit", "2048");
properties.putIfAbsent("useServerPrepStmts", "true");
properties.putIfAbsent("useLocalSessionState", "true");
properties.putIfAbsent("rewriteBatchedStatements", "true");
properties.putIfAbsent("cacheResultSetMetadata", "true");
properties.putIfAbsent("cacheServerConfiguration", "true");
properties.putIfAbsent("elideSetAutoCommits", "true");
properties.putIfAbsent("maintainTimeStats", "false");
properties.putIfAbsent("alwaysSendSetIsolation", "false");
properties.putIfAbsent("cacheCallableStmts", "true");
// https://stackoverflow.com/a/54256150
properties.putIfAbsent("serverTimezone", "UTC");
super.overrideProperties(properties);
}
@Override
public Function<String, String> getStatementProcessor() {
return s -> s.replace("'", "`");
}
}

View File

@ -0,0 +1,79 @@
/*******************************************************************************************************
* Continued by PikaMug (formerly HappyPikachu) with permission from _Blackvein_. All rights reserved.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************************************/
package me.blackvein.quests.storage.misc;
import java.util.Map;
import java.util.Objects;
public class StorageCredentials {
private final String address;
private final String database;
private final String username;
private final String password;
private final int maxPoolSize;
private final int minIdleConnections;
private final int maxLifetime;
private final int connectionTimeout;
private final Map<String, String> properties;
public StorageCredentials(final String address, final String database, final String username, final String password,
final int maxPoolSize, final int minIdleConnections, final int maxLifetime, final int connectionTimeout,
final Map<String, String> properties) {
this.address = address;
this.database = database;
this.username = username;
this.password = password;
this.maxPoolSize = maxPoolSize;
this.minIdleConnections = minIdleConnections;
this.maxLifetime = maxLifetime;
this.connectionTimeout = connectionTimeout;
this.properties = properties;
}
public String getAddress() {
return Objects.requireNonNull(address, "address");
}
public String getDatabase() {
return Objects.requireNonNull(database, "database");
}
public String getUsername() {
return Objects.requireNonNull(username, "username");
}
public String getPassword() {
return Objects.requireNonNull(password, "password");
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public int getMinIdleConnections() {
return minIdleConnections;
}
public int getMaxLifetime() {
return maxLifetime;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public Map<String, String> getProperties() {
return properties;
}
}

View File

@ -19,6 +19,18 @@ npc-effects:
show-requirements: true
show-titles: true
strict-player-movement: 0
storage-data:
address: localhost
database: minecraft
username: root
password: ''
table_prefix: 'quests_'
pool-settings:
max-pool-size: 10
min-idle: 10
max-lifetime: 1800000
connection-timeout: 5000
storage-method: yaml
top-limit: 150
translate-names: true
translate-subcommands: false