diff --git a/core/pom.xml b/core/pom.xml index 1c5f6f79..7811a1d1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 @@ -89,10 +89,10 @@ maven-central https://oss.sonatype.org/content/groups/public - - teamvk-repo - https://raw.githubusercontent.com/TeamVK/maven-repository/master/release/ - + + teamvk-repo + https://raw.githubusercontent.com/TeamVK/maven-repository/master/release/ + lumine https://mvn.lumine.io/repository/maven-public/ @@ -123,7 +123,7 @@ org.spigotmc spigot - 1.18-R0.1-SNAPSHOT + 1.18.2-R0.1-SNAPSHOT provided @@ -136,7 +136,7 @@ org.mcmonkey sentinel - 2.4.0-SNAPSHOT + 2.5.0-SNAPSHOT provided @@ -210,6 +210,12 @@ 4.12.0 provided + + io.lumine + Mythic-Dist + 5.0.1 + provided + com.github.promcteam proskillapi @@ -234,6 +240,12 @@ 18.37.1 provided + + org.mariadb.jdbc + mariadb-java-client + 2.7.5 + provided + diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 3105fcab..a6c0a5d7 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -35,7 +35,6 @@ import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.bossbar.BQBossBarImplementation; -import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.commands.Commands; import fr.skytasul.quests.commands.CommandsManager; import fr.skytasul.quests.editors.Editor; @@ -51,7 +50,6 @@ import fr.skytasul.quests.players.PlayersManagerDB; import fr.skytasul.quests.players.PlayersManagerYAML; import fr.skytasul.quests.scoreboards.ScoreboardManager; -import fr.skytasul.quests.structure.NPCStarter; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.structure.QuestsManager; import fr.skytasul.quests.structure.pools.QuestPoolsManager; @@ -84,11 +82,10 @@ public class BeautyQuests extends JavaPlugin { private YamlConfiguration data; private File dataFile; - public static File saveFolder; + private File saveFolder; /* --------- Datas --------- */ - private Map npcs = new HashMap<>(); private ScoreboardManager scoreboards; private QuestsManager quests; private QuestPoolsManager pools; @@ -122,6 +119,8 @@ public void onLoad(){ @Override public void onEnable(){ try { + logger.info("------------ BeautyQuests ------------"); + dependencies.testCompatibilities(); Bukkit.getPluginManager().registerEvents(dependencies, this); @@ -137,7 +136,7 @@ public void onEnable(){ try { dependencies.initializeCompatibilities(); }catch (Exception ex) { - logger.severe("Error when initializing compatibilities. Consider restarting.", ex); + logger.severe("An error occurred while initializing compatibilities. Consider restarting.", ex); } if (QuestsAPI.getNPCsManager() == null) { @@ -161,7 +160,11 @@ public void run() { if (!lastVersion.equals(pluginVersion)) { // maybe change in data structure : update of all quest files DebugUtils.logMessage("Migrating from " + lastVersion + " to " + pluginVersion); - for (Quest qu : quests) qu.saveToFile(); + int updated = 0; + for (Quest qu : quests) { + if (qu.saveToFile()) updated++; + } + if (updated > 0) logger.info("Updated " + updated + " quests during migration."); saveAllConfig(false); } }catch (Throwable e) { @@ -169,7 +172,6 @@ public void run() { } } }.runTaskLater(this, QuestsAPI.getNPCsManager().getTimeToWaitForNPCs()); - // Start of non-essential systems if (loggerHandler != null) loggerHandler.launchFlushTimer(); @@ -249,7 +251,7 @@ public void run() { } } }; - logger.info("Periodic saves task started (" + cycle + " ticks). Task ID: " + saveTask.runTaskTimer(this, cycle, cycle).getTaskId()); + logger.info("Periodic saves task started (" + cycle + " ticks). Task ID: " + saveTask.runTaskTimerAsynchronously(this, cycle, cycle).getTaskId()); } } @@ -294,7 +296,7 @@ private void launchMetrics(String pluginVersion) { private void launchUpdateChecker(String pluginVersion) throws ReflectiveOperationException { DebugUtils.logMessage("Starting Spigot updater"); if (pluginVersion.contains("_")) { - Matcher matcher = Pattern.compile("_BUILD(.+)").matcher(pluginVersion); + Matcher matcher = Pattern.compile("_BUILD(\\d+)").matcher(pluginVersion); if (matcher.find()) { String build = matcher.group(1); UpdateChecker.init(instance, "https://ci.codemc.io/job/SkytAsul/job/BeautyQuests/lastSuccessfulBuild/buildNumber") @@ -330,13 +332,16 @@ private void loadConfigParameters(boolean init) throws LoadingException { ConfigurationSection dbConfig = config.getConfig().getConfigurationSection("database"); if (dbConfig.getBoolean("enabled")) { - db = new Database(dbConfig); - if (db.openConnection()) { + try { + db = new Database(dbConfig); + db.testConnection(); logger.info("Connection to database etablished."); - }else { - db.closeConnection(); - db = null; - throw new LoadingException("Connection to database has failed."); + }catch (Exception ex) { + if (db != null) { + db.closeConnection(); + db = null; + } + throw new LoadingException("Connection to database has failed.", ex); } } @@ -486,7 +491,7 @@ private void loadAllDatas() throws Throwable { public void saveAllConfig(boolean unload) throws Exception { if (unload) { - quests.unloadQuests(); + if (quests != null) quests.unloadQuests(); QuestsAPI.getQuestsHandlers().forEach(handler -> { try { @@ -498,6 +503,7 @@ public void saveAllConfig(boolean unload) throws Exception { } if (loaded) { + long time = System.currentTimeMillis(); data.set("lastID", quests.getLastID()); data.set("version", getDescription().getVersion()); @@ -508,18 +514,18 @@ public void saveAllConfig(boolean unload) throws Exception { logger.severe("Error when saving player datas.", ex); } data.save(dataFile); + DebugUtils.logMessage("Saved datas (" + (((double) System.currentTimeMillis() - time) / 1000D) + "s)!"); } if (unload){ + QuestsAPI.getNPCsManager().unload(); resetDatas(); } } private void resetDatas(){ - npcs.values().forEach(NPCStarter::removeHolograms); quests = null; pools = null; - npcs.clear(); if (db != null) db.closeConnection(); //HandlerList.unregisterAll(this); loaded = false; @@ -616,10 +622,6 @@ public void run() { public QuestsConfiguration getConfiguration() { return config; } - - public Map getNPCs() { - return npcs; - } public FileConfiguration getDataFile(){ return data; diff --git a/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java b/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java index b4a6ae6d..53d8bcd0 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java @@ -17,6 +17,7 @@ import com.google.common.collect.Sets; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.structure.QuestDescription; @@ -155,6 +156,7 @@ void init() { }else if (config.isString("item")) { item = XMaterial.matchXMaterial(config.getString("item")).orElse(XMaterial.BOOK).parseItem(); }else item = XMaterial.BOOK.parseItem(); + item = ItemUtils.clearVisibleAttributes(item); if (config.contains("pageItem")) pageItem = XMaterial.matchXMaterial(config.getString("pageItem")).orElse(XMaterial.ARROW); if (pageItem == null) pageItem = XMaterial.ARROW; startParticleDistance = config.getInt("startParticleDistance"); diff --git a/core/src/main/java/fr/skytasul/quests/QuestsListener.java b/core/src/main/java/fr/skytasul/quests/QuestsListener.java index 7e419423..804b6896 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsListener.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsListener.java @@ -38,9 +38,9 @@ import fr.skytasul.quests.players.PlayersManager; import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; import fr.skytasul.quests.players.events.PlayerAccountLeaveEvent; -import fr.skytasul.quests.structure.NPCStarter; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; @@ -57,67 +57,65 @@ public void onNPCClick(BQNPCClickEvent e) { if (Inventories.isInSystem(p)) return; - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - if (starter != null) { - PlayerAccount acc = PlayersManager.getPlayerAccount(p); - if (acc == null) return; - - Set quests = starter.getQuests(); - quests = quests.stream().filter(qu -> !qu.hasStarted(acc) && (qu.isRepeatable() ? true : !qu.hasFinished(acc))).collect(Collectors.toSet()); - if (quests.isEmpty() && starter.getPools().isEmpty()) return; - - List launcheable = new ArrayList<>(); - List requirements = new ArrayList<>(); - List timer = new ArrayList<>(); - for (Quest qu : quests) { - try { - if (!qu.testRequirements(p, acc, false)) { - requirements.add(qu); - }else if (!qu.testTimer(acc, false)) { - timer.add(qu); - }else launcheable.add(qu); - }catch (Exception ex) { - BeautyQuests.logger.severe("An exception occured when checking requirements on the quest " + qu.getID() + " for player " + p.getName(), ex); - } + PlayerAccount acc = PlayersManager.getPlayerAccount(p); + if (acc == null) return; + + Set quests = npc.getQuests(); + quests = quests.stream().filter(qu -> !qu.hasStarted(acc) && (qu.isRepeatable() ? true : !qu.hasFinished(acc))).collect(Collectors.toSet()); + if (quests.isEmpty() && npc.getPools().isEmpty()) return; + + List launcheable = new ArrayList<>(); + List requirements = new ArrayList<>(); + List timer = new ArrayList<>(); + for (Quest qu : quests) { + try { + if (!qu.testRequirements(p, acc, false)) { + requirements.add(qu); + }else if (!qu.testTimer(acc, false)) { + timer.add(qu); + }else launcheable.add(qu); + }catch (Exception ex) { + BeautyQuests.logger.severe("An exception occured when checking requirements on the quest " + qu.getID() + " for player " + p.getName(), ex); } - - Set startablePools = starter.getPools().stream().filter(pool -> { - try { - return pool.canGive(p, acc); - }catch (Exception ex) { - BeautyQuests.logger.severe("An exception occured when checking requirements on the pool " + pool.getID() + " for player " + p.getName(), ex); - return false; - } - }).collect(Collectors.toSet()); - - e.setCancelled(true); - if (!launcheable.isEmpty()) { - for (Quest quest : launcheable) { - if (quest.isInDialog(p)) { - quest.clickNPC(p); - return; - } - } - ChooseQuestGUI gui = new ChooseQuestGUI(launcheable, (quest) -> { - if (quest == null) return; + } + + Set startablePools = npc.getPools().stream().filter(pool -> { + try { + return pool.canGive(p, acc); + }catch (Exception ex) { + BeautyQuests.logger.severe("An exception occured when checking requirements on the pool " + pool.getID() + " for player " + p.getName(), ex); + return false; + } + }).collect(Collectors.toSet()); + + e.setCancelled(true); + if (!launcheable.isEmpty()) { + for (Quest quest : launcheable) { + if (quest.isInDialog(p)) { quest.clickNPC(p); - }); - gui.setValidate(__ -> { - new PlayerListGUI(acc).create(p); - }, ItemUtils.item(XMaterial.BOOKSHELF, Lang.questMenu.toString(), QuestOption.formatDescription(Lang.questMenuLore.toString()))); - gui.create(p); - }else if (!startablePools.isEmpty()) { - startablePools.iterator().next().give(p); - }else { - if (!timer.isEmpty()) { - timer.get(0).testTimer(acc, true); - }else if (!requirements.isEmpty()) { - requirements.get(0).testRequirements(p, acc, true); - }else { - Utils.sendMessage(p, starter.getPools().iterator().next().give(p)); + return; } - e.setCancelled(false); } + ChooseQuestGUI gui = new ChooseQuestGUI(launcheable, (quest) -> { + if (quest == null) return; + quest.clickNPC(p); + }); + gui.setValidate(__ -> { + new PlayerListGUI(acc).create(p); + }, ItemUtils.item(XMaterial.BOOKSHELF, Lang.questMenu.toString(), QuestOption.formatDescription(Lang.questMenuLore.toString()))); + gui.create(p); + }else if (!startablePools.isEmpty()) { + QuestPool pool = startablePools.iterator().next(); + DebugUtils.logMessage("NPC " + npc.getId() + ": " + startablePools.size() + " pools, result: " + pool.give(p)); + }else { + if (!timer.isEmpty()) { + timer.get(0).testTimer(acc, true); + }else if (!requirements.isEmpty()) { + requirements.get(0).testRequirements(p, acc, true); + }else { + Utils.sendMessage(p, npc.getPools().iterator().next().give(p)); + } + e.setCancelled(false); } } diff --git a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java index de21a31e..5bb5579c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java +++ b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java @@ -1,8 +1,6 @@ package fr.skytasul.quests.api; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -11,14 +9,12 @@ import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.bossbar.BQBossBarManager; import fr.skytasul.quests.api.comparison.ItemComparison; import fr.skytasul.quests.api.mobs.MobFactory; -import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.api.npcs.BQNPCsManager; import fr.skytasul.quests.api.objects.QuestObjectsRegistry; import fr.skytasul.quests.api.options.QuestOptionCreator; @@ -28,16 +24,12 @@ import fr.skytasul.quests.api.rewards.RewardCreator; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageType; -import fr.skytasul.quests.players.PlayerAccount; -import fr.skytasul.quests.players.PlayersManager; -import fr.skytasul.quests.structure.NPCStarter; -import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.structure.QuestsManager; import fr.skytasul.quests.structure.pools.QuestPoolsManager; import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.Lang; -public class QuestsAPI { +public final class QuestsAPI { private static final QuestObjectsRegistry requirements = new QuestObjectsRegistry<>(Lang.INVENTORY_REQUIREMENTS.toString()); private static final QuestObjectsRegistry rewards = new QuestObjectsRegistry<>(Lang.INVENTORY_REWARDS.toString()); @@ -50,11 +42,11 @@ public class QuestsAPI { private static final Set handlers = new HashSet<>(); + private QuestsAPI() {} + /** * Register new stage type into the plugin - * @param type StageType object - * @param item ItemStack shown in stages GUI when choosing stage type - * @param runnables Instance of special runnables + * @param creator StageType instance */ public static void registerStage(StageType creator) { stages.add(creator); @@ -116,6 +108,7 @@ public static void setHologramsManager(AbstractHolograms newHologramsManager) Validate.notNull(newHologramsManager); if (hologramsManager != null) BeautyQuests.logger.warning(newHologramsManager.getClass().getSimpleName() + " will replace " + hologramsManager.getClass().getSimpleName() + " as the new holograms manager."); hologramsManager = newHologramsManager; + DebugUtils.logMessage("Holograms manager has been registered: " + newHologramsManager.getClass().getName()); } public static boolean hasBossBarManager() { @@ -130,6 +123,7 @@ public static void setBossBarManager(BQBossBarManager newBossBarManager) { Validate.notNull(newBossBarManager); if (bossBarManager != null) BeautyQuests.logger.warning(newBossBarManager.getClass().getSimpleName() + " will replace " + hologramsManager.getClass().getSimpleName() + " as the new boss bar manager."); bossBarManager = newBossBarManager; + DebugUtils.logMessage("Bossbars manager has been registered: " + newBossBarManager.getClass().getName()); } public static void registerQuestsHandler(QuestsHandler handler) { @@ -155,24 +149,6 @@ public static void propagateQuestsHandlers(Consumer consumer) { } }); } - - public static List getQuestsAssigneds(BQNPC npc) { - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - return starter == null ? Collections.emptyList() : new ArrayList<>(starter.getQuests()); - } - - public static boolean isQuestStarter(BQNPC npc) { - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - return starter != null && !starter.getQuests().isEmpty(); - } - - public static boolean hasQuestStarted(Player p, BQNPC npc) { - PlayerAccount acc = PlayersManager.getPlayerAccount(p); - for (Quest qu : getQuestsAssigneds(npc)){ - if (qu.hasStarted(acc)) return true; - } - return false; - } public static QuestsManager getQuests() { return BeautyQuests.getInstance().getQuestsManager(); diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java b/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java index 83efbfa6..8f32bf2c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java +++ b/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java @@ -25,7 +25,7 @@ /** * This class implements {@link Listener} to permit the implementation to have at least one {@link EventHandler}. - * This event method will be used to fire {@link #callEvent(Object, Entity, Player)}. + * This event method will be used to fire the {@link #callEvent(Event, Object, Entity, Player)}. * * @param object which should represents a mob type from whatever plugin */ diff --git a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java index 074bbc2b..0bc34aed 100644 --- a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java +++ b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java @@ -1,9 +1,62 @@ package fr.skytasul.quests.api.npcs; +import java.util.*; +import java.util.Map.Entry; +import java.util.function.BiPredicate; + +import org.apache.commons.lang.StringUtils; import org.bukkit.Location; import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.QuestsConfiguration; +import fr.skytasul.quests.api.AbstractHolograms; +import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.options.OptionHologramLaunch; +import fr.skytasul.quests.options.OptionHologramLaunchNo; +import fr.skytasul.quests.options.OptionHologramText; +import fr.skytasul.quests.options.OptionStarterNPC; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayersManager; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.DebugUtils; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; -public interface BQNPC { +public abstract class BQNPC { + + private Set quests = new TreeSet<>(); + private Set pools = new TreeSet<>(); + + private List> hiddenTickets = new ArrayList<>(); + private Map> startable = new HashMap<>(); + + private BukkitTask launcheableTask; + + /* Holograms */ + private BukkitTask hologramsTask; + private boolean hologramsRemoved = true; + private Hologram hologramText = new Hologram(false, QuestsAPI.hasHologramsManager() && !QuestsConfiguration.isTextHologramDisabled(), Lang.HologramText.toString()); + private Hologram hologramLaunch = new Hologram(false, QuestsAPI.hasHologramsManager() && QuestsAPI.getHologramsManager().supportItems(), QuestsConfiguration.getHoloLaunchItem()); + private Hologram hologramLaunchNo = new Hologram(false, QuestsAPI.hasHologramsManager() && QuestsAPI.getHologramsManager().supportItems() && QuestsAPI.getHologramsManager().supportPerPlayerVisibility(), QuestsConfiguration.getHoloLaunchNoItem()); + private Hologram hologramPool = new Hologram(false, QuestsAPI.hasHologramsManager() && QuestsAPI.getHologramsManager().supportPerPlayerVisibility(), Lang.PoolHologramText.toString()) { + @Override + public double getYAdd() { + return hologramText.canAppear && hologramText.visible ? 0.55 : 0; + } + }; + private final boolean holograms; + + protected BQNPC() { + holograms = hologramText.enabled || hologramLaunch.enabled || hologramLaunchNo.enabled || hologramPool.enabled; + launcheableTask = startLauncheableTasks(); + } public abstract int getId(); @@ -26,4 +79,319 @@ public interface BQNPC { */ public abstract boolean setNavigationPaused(boolean paused); + private BukkitTask startLauncheableTasks() { + return new BukkitRunnable() { + private int timer = 0; + + @Override + public void run() { + if (!isSpawned()) return; + if (!(getEntity() instanceof LivingEntity)) return; + LivingEntity en = (LivingEntity) getEntity(); + + if (timer-- == 0) { + timer = QuestsConfiguration.getRequirementUpdateTime(); + return; + } + + for (Quest quest : quests) quest.getLauncheable().clear(); + + Set playersInRadius = new HashSet<>(); + Location lc = en.getLocation(); + for (Player p : lc.getWorld().getPlayers()) { + PlayerAccount acc = PlayersManager.getPlayerAccount(p); + if (acc == null) continue; + if (lc.distanceSquared(p.getLocation()) > QuestsConfiguration.getStartParticleDistanceSquared()) continue; + playersInRadius.add(p); + try { + for (Quest quest : quests) { + if (quest.isLauncheable(p, acc, false)) { + quest.getLauncheable().add(p); + break; + } + } + }catch (NullPointerException ex) {} + } + for (Quest quest : quests) quest.updateLauncheable(en); + + if (hologramPool.canAppear) { + for (Player p : playersInRadius) { + boolean visible = false; + for (QuestPool pool : pools) { + if (pool.canGive(p, PlayersManager.getPlayerAccount(p))) { + visible = true; + break; + } + } + hologramPool.setVisible(p, visible); + } + } + if (hologramLaunch.canAppear || hologramLaunchNo.canAppear) { + List launcheable = new ArrayList<>(); + List unlauncheable = new ArrayList<>(); + for (Iterator iterator = playersInRadius.iterator(); iterator.hasNext();) { + Player player = iterator.next(); + if (hiddenTickets.stream().anyMatch(entry -> entry.getKey() == player)) { + iterator.remove(); + continue; + } + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + boolean launchYes = false; + boolean launchNo = false; + for (Quest qu : quests) { + if (!qu.hasStarted(acc)) { + boolean pLauncheable = qu.getLauncheable().contains(player); + if (hologramLaunch.enabled && pLauncheable) { + launchYes = true; + break; // launcheable take priority over not launcheable + }else if (hologramLaunchNo.enabled && !pLauncheable) { + launchNo = true; + } + } + } + if (launchYes) { + launcheable.add(player); + iterator.remove(); + }else if (launchNo) { + unlauncheable.add(player); + iterator.remove(); + } + } + hologramLaunch.setVisible(launcheable); + hologramLaunchNo.setVisible(unlauncheable); + } + + } + }.runTaskTimer(BeautyQuests.getInstance(), 20L, 20L); + } + + private BukkitTask startHologramsTask() { + return new BukkitRunnable() { + @Override + public void run() { + LivingEntity en = null; // check if NPC is spawned and living + if (isSpawned() && getEntity() instanceof LivingEntity) + en = (LivingEntity) getEntity(); + if (en == null) { + if (!hologramsRemoved) removeHolograms(); // if the NPC is not living and holograms have not been already removed before + return; + } + hologramsRemoved = false; + + if (hologramText.canAppear && hologramText.visible) hologramText.refresh(en); + if (hologramLaunch.canAppear) hologramLaunch.refresh(en); + if (hologramLaunchNo.canAppear) hologramLaunchNo.refresh(en); + if (hologramPool.canAppear && hologramPool.visible) hologramPool.refresh(en); + } + }.runTaskTimer(BeautyQuests.getInstance(), 20L, 1L); + } + + public Set getQuests() { + return quests; + } + + public Hologram getHologramText() { + return hologramText; + } + + public Hologram getHologramLaunch() { + return hologramLaunch; + } + + public Hologram getHologramLaunchNo() { + return hologramLaunchNo; + } + + public void addQuest(Quest quest) { + if (!quests.add(quest)) return; + if (hologramText.enabled && quest.hasOption(OptionHologramText.class)) hologramText.setText(quest.getOption(OptionHologramText.class).getValue()); + if (hologramLaunch.enabled && quest.hasOption(OptionHologramLaunch.class)) hologramLaunch.setItem(quest.getOption(OptionHologramLaunch.class).getValue()); + if (hologramLaunchNo.enabled && quest.hasOption(OptionHologramLaunchNo.class)) hologramLaunchNo.setItem(quest.getOption(OptionHologramLaunchNo.class).getValue()); + hologramText.visible = true; + addStartablePredicate((p, acc) -> quest.isLauncheable(p, acc, false), quest); + updatedObjects(); + } + + public boolean removeQuest(Quest quest) { + boolean b = quests.remove(quest); + removeStartablePredicate(quest); + updatedObjects(); + if (quests.isEmpty()) { + hologramText.visible = false; + hologramText.delete(); + } + return b; + } + + public boolean hasQuestStarted(Player p) { + PlayerAccount acc = PlayersManager.getPlayerAccount(p); + return quests.stream().anyMatch(quest -> quest.hasStarted(acc)); + } + + public Set getPools() { + return pools; + } + + public void addPool(QuestPool pool) { + if (!pools.add(pool)) return; + if (hologramPool.enabled && (pool.getHologram() != null)) hologramPool.setText(pool.getHologram()); + hologramPool.visible = true; + addStartablePredicate(pool::canGive, pool); + updatedObjects(); + } + + public boolean removePool(QuestPool pool) { + boolean b = pools.remove(pool); + removeStartablePredicate(pool); + updatedObjects(); + if (pools.isEmpty()) { + hologramPool.visible = false; + hologramPool.delete(); + } + return b; + } + + public void addStartablePredicate(BiPredicate predicate, Object holder) { + startable.put(holder, predicate); + } + + public void removeStartablePredicate(Object holder) { + startable.remove(holder); + } + + public void hideForPlayer(Player p, Object holder) { + hiddenTickets.add(new AbstractMap.SimpleEntry<>(p, holder)); + } + + public void removeHiddenForPlayer(Player p, Object holder) { + for (Iterator> iterator = hiddenTickets.iterator(); iterator.hasNext();) { + Entry entry = iterator.next(); + if (entry.getKey() == p && entry.getValue() == holder) { + iterator.remove(); + return; + } + } + } + + public boolean canGiveSomething(Player p) { + PlayerAccount acc = PlayersManager.getPlayerAccount(p); + return startable.values().stream().anyMatch(predicate -> predicate.test(p, acc)); + } + + private void removeHolograms() { + hologramText.delete(); + hologramLaunch.delete(); + hologramLaunchNo.delete(); + hologramPool.delete(); + hologramsRemoved = true; + if (hologramsTask != null) { + hologramsTask.cancel(); + hologramsTask = null; + } + } + + private boolean isEmpty() { + return quests.isEmpty() && pools.isEmpty(); + } + + private void updatedObjects() { + if (isEmpty()) { + removeHolograms(); + }else if (holograms && hologramsTask == null) { + hologramsTask = startHologramsTask(); + } + } + + public void unload() { + removeHolograms(); + if (launcheableTask != null) { + launcheableTask.cancel(); + launcheableTask = null; + } + } + + public void delete(String cause) { + DebugUtils.logMessage("Removing NPC Starter " + getId()); + for (Quest qu : quests) { + BeautyQuests.logger.warning("Starter NPC #" + getId() + " has been removed from quest " + qu.getID() + ". Reason: " + cause); + qu.removeOption(OptionStarterNPC.class); + } + quests = null; + for (QuestPool pool : pools) { + BeautyQuests.logger.warning("NPC #" + getId() + " has been removed from pool " + pool.getID() + ". Reason: " + cause); + pool.unloadStarter(); + } + unload(); + } + + public class Hologram { + final boolean enabled; + boolean visible; + boolean canAppear; + AbstractHolograms.BQHologram hologram; + + String text; + ItemStack item; + + public Hologram(boolean visible, boolean enabled, String text) { + this.visible = visible; + this.enabled = enabled; + setText(text); + } + + public Hologram(boolean visible, boolean enabled, ItemStack item) { + this.visible = visible; + this.enabled = enabled; + setItem(item); + } + + public void refresh(LivingEntity en) { + Location lc = Utils.upLocationForEntity(en, getYAdd()); + if (hologram == null) { + create(lc); + }else hologram.teleport(lc); + } + + public double getYAdd() { + return item == null ? 0 : 1; + } + + public void setVisible(List players) { + if (hologram != null) hologram.setPlayersVisible(players); + } + + public void setVisible(Player p, boolean visibility) { + if (hologram != null) hologram.setPlayerVisibility(p, visibility); + } + + public void setText(String text) { + if (Objects.equals(text, this.text)) return; + this.text = text; + canAppear = enabled && !StringUtils.isEmpty(text) && !"none".equals(text); + delete(); // delete to regenerate with new text + } + + public void setItem(ItemStack item) { + if (Objects.equals(item, this.item)) return; + this.item = item; + canAppear = enabled && item != null; + if (canAppear && QuestsConfiguration.isCustomHologramNameShown() && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) + this.text = item.getItemMeta().getDisplayName(); + delete(); // delete to regenerate with new item + } + + public void create(Location lc) { + if (hologram != null) return; + hologram = QuestsAPI.getHologramsManager().createHologram(lc, visible); + if (text != null) hologram.appendTextLine(text); + if (item != null) hologram.appendItem(item); + } + + public void delete() { + if (hologram == null) return; + hologram.delete(); + hologram = null; + } + } + } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPCsManager.java b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPCsManager.java index 9a179dea..2acc2167 100644 --- a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPCsManager.java +++ b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPCsManager.java @@ -12,14 +12,12 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Listener; -import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration.ClickType; import fr.skytasul.quests.api.events.BQNPCClickEvent; -import fr.skytasul.quests.structure.NPCStarter; public abstract class BQNPCsManager implements Listener { - private final Map cache = new HashMap<>(); + private final Map npcs = new HashMap<>(); public abstract int getTimeToWaitForNPCs(); @@ -34,7 +32,7 @@ public final BQNPC createNPC(Location location, EntityType type, String name, St }catch (Exception ex) { ex.printStackTrace(); } - cache.put(npc.getId(), npc); + npcs.put(npc.getId(), npc); return npc; } @@ -43,17 +41,16 @@ public final BQNPC createNPC(Location location, EntityType type, String name, St protected abstract BQNPC create(Location location, EntityType type, String name); public final BQNPC getById(int id) { - return cache.computeIfAbsent(id, this::fetchNPC); + return npcs.computeIfAbsent(id, this::fetchNPC); } protected abstract BQNPC fetchNPC(int id); protected final void removeEvent(int id) { - BQNPC npc = cache.get(id); + BQNPC npc = npcs.get(id); if (npc == null) return; - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - if (starter != null) starter.delete("NPC #" + id + " removed"); - cache.remove(id); + npc.delete("NPC #" + id + " removed"); + npcs.remove(id); } protected final void clickEvent(Cancellable event, int npcID, Player p, ClickType click) { @@ -63,4 +60,9 @@ protected final void clickEvent(Cancellable event, int npcID, Player p, ClickTyp if (event != null) event.setCancelled(newEvent.isCancelled()); } + public void unload() { + npcs.values().forEach(BQNPC::unload); + npcs.clear(); + } + } diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java index ac8ae715..762a98a5 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java @@ -45,6 +45,10 @@ public String getName() { return getCreator().id; } + public String debugName() { + return getClass().getSimpleName() + (quest == null ? ", unknown quest" : (", quest " + quest.getID())); + } + @Override public abstract QuestObject clone(); @@ -98,7 +102,7 @@ public static > T deseria String id = (String) map.get("id"); if (id != null) creator = registry.getByID(id); - if (creator == null) { + if (creator == null && map.containsKey("class")) { String className = (String) map.get("class"); try { creator = registry.getByClass(Class.forName(className)); diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java index 7f040331..fe632c69 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java @@ -21,8 +21,8 @@ public class QuestObjectCreator { * @param item ItemStack shown in {@link QuestObjectGUI} * @param newObjectSupplier lambda returning an instance of this Object ({@link T}::new) */ - public QuestObjectCreator(String id, Class clazz, ItemStack is, Supplier newObjectSupplier) { - this(id, clazz, is, newObjectSupplier, true); + public QuestObjectCreator(String id, Class clazz, ItemStack item, Supplier newObjectSupplier) { + this(id, clazz, item, newObjectSupplier, true); } /** @@ -33,10 +33,10 @@ public QuestObjectCreator(String id, Class clazz, ItemStack is, Sup * @param multiple can the object be present multiple times * @param allowedLocations if present, specifies where the object can be used. If no location specified, the */ - public QuestObjectCreator(String id, Class clazz, ItemStack is, Supplier newObjectSupplier, boolean multiple, QuestObjectLocation... allowedLocations) { + public QuestObjectCreator(String id, Class clazz, ItemStack item, Supplier newObjectSupplier, boolean multiple, QuestObjectLocation... allowedLocations) { this.id = id; this.clazz = clazz; - this.item = is; + this.item = item; this.newObjectSupplier = newObjectSupplier; this.multiple = multiple; this.allowedLocations = allowedLocations; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java index 57682164..cfc4965a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java @@ -114,7 +114,7 @@ public boolean event(PlayerAccount acc, Player p, Object object, int amount) { if (objectApplies(entry.getValue().getKey(), object)) { Map playerAmounts = getPlayerRemainings(acc); if (playerAmounts == null) { - BeautyQuests.logger.warning(p.getName() + " oesdoes not have object datas for stage " + debugName() + ". This is a bug!"); + BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + debugName() + ". This is a bug!"); return true; } if (playerAmounts.containsKey(id)) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java index ddc0f0c0..48450708 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java @@ -6,6 +6,7 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; @@ -36,24 +37,26 @@ protected void event(Player p, EntityType type) { PlayerAccount acc = PlayersManager.getPlayerAccount(p); if (branch.hasStageLaunched(acc, this) && canUpdate(p)) { if (entity == null || type.equals(entity)) { - int amount = getPlayerAmount(acc); - if (amount <= 1) { + Integer playerAmount = getPlayerAmount(acc); + if (playerAmount == null) { + BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + debugName() + ". This is a bug!"); + }else if (playerAmount.intValue() <= 1) { finishStage(p); }else { - updateObjective(acc, p, "amount", --amount); + updateObjective(acc, p, "amount", playerAmount.intValue() - 1); } } } } - protected int getPlayerAmount(PlayerAccount acc) { + protected Integer getPlayerAmount(PlayerAccount acc) { return getData(acc, "amount"); } @Override protected void initPlayerDatas(PlayerAccount acc, Map datas) { - datas.put("amount", amount); super.initPlayerDatas(acc, datas); + datas.put("amount", amount); } @Override @@ -63,7 +66,10 @@ protected void serialize(Map map) { } protected String getMobsLeft(PlayerAccount acc) { - return Utils.getStringFromNameAndAmount(entity == null ? Lang.EntityTypeAny.toString() : MinecraftNames.getEntityName(entity), QuestsConfiguration.getItemAmountColor(), getPlayerAmount(acc), amount, false); + Integer playerAmount = getPlayerAmount(acc); + if (playerAmount == null) return "§cerror: no datas"; + + return Utils.getStringFromNameAndAmount(entity == null ? Lang.EntityTypeAny.toString() : MinecraftNames.getEntityName(entity), QuestsConfiguration.getItemAmountColor(), playerAmount, amount, false); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 87a79380..980b609a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -24,7 +24,6 @@ import fr.skytasul.quests.players.PlayersManager; import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; import fr.skytasul.quests.players.events.PlayerAccountLeaveEvent; -import fr.skytasul.quests.structure.BranchesManager; import fr.skytasul.quests.structure.QuestBranch; import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Utils; @@ -147,7 +146,6 @@ public String debugName() { /** * Called internally when a player finish stage's objectives * @param p Player who finish the stage - * @see BranchesManager#next(Player) */ protected final void finishStage(Player p) { branch.finishStage(p, this); @@ -156,7 +154,7 @@ protected final void finishStage(Player p) { /** * Called internally to test if a player has the stage started * @param p Player to test - * @see BranchesManager#hasStageLaunched(PlayerAccount, AbstractStage) + * @see QuestBranch#hasStageLaunched(PlayerAccount, AbstractStage) */ protected final boolean hasStarted(Player p){ return branch.hasStageLaunched(PlayersManager.getPlayerAccount(p), this); diff --git a/core/src/main/java/fr/skytasul/quests/commands/Cmd.java b/core/src/main/java/fr/skytasul/quests/commands/Cmd.java index 544ee672..20e3d267 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Cmd.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Cmd.java @@ -8,6 +8,7 @@ import org.bukkit.entity.Player; +import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.structure.Quest; @Retention(RUNTIME) @@ -40,7 +41,9 @@ *
  • NPCSID : list of all NPCs IDs *
  • xxx|yyy|zzz : available values, separated by a pipe (|) * - * In the case of PLAYERS QUESTSID and NPCSID, they will be directly replaced by an instance of {@link Player}/{@link Quest}/{@link NPC} when command executing (no need for String parsing) + * In the case of PLAYERS QUESTSID and NPCSID, they will be directly replaced by + * an instance of {@link Player} / {@link Quest} / {@link BQNPC} when command executing + * (no need for String parsing) * @return String array of possibles arguments */ public String[] args() default {}; diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java index 4c5c7942..cebde670 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Commands.java @@ -7,6 +7,7 @@ import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Optional; @@ -69,8 +70,8 @@ public void edit(CommandContext cmd){ Lang.CHOOSE_NPC_STARTER.send(cmd.player); new SelectNPC(cmd.player, () -> {}, npc -> { if (npc == null) return; - if (QuestsAPI.isQuestStarter(npc)){ - Inventories.create(cmd.player, new ChooseQuestGUI(QuestsAPI.getQuestsAssigneds(npc), quest -> { + if (!npc.getQuests().isEmpty()) { + Inventories.create(cmd.player, new ChooseQuestGUI(npc.getQuests(), quest -> { if (quest == null) return; Inventories.create(cmd.player, new StagesGUI(null)).edit(quest); })); @@ -96,8 +97,8 @@ public void remove(CommandContext cmd){ Lang.CHOOSE_NPC_STARTER.send(cmd.sender); new SelectNPC(cmd.player, () -> {}, npc -> { if (npc == null) return; - if (QuestsAPI.isQuestStarter(npc)){ - Inventories.create(cmd.player, new ChooseQuestGUI(QuestsAPI.getQuestsAssigneds(npc), quest -> { + if (!npc.getQuests().isEmpty()) { + Inventories.create(cmd.player, new ChooseQuestGUI(npc.getQuests(), quest -> { if (quest == null) return; remove(cmd.sender, quest); })); @@ -617,25 +618,32 @@ public void downloadTranslations(CommandContext cmd) { @Cmd (permission = "manage") public void migrateDatas(CommandContext cmd) { if (!(PlayersManager.manager instanceof PlayersManagerYAML)) { - cmd.sender.sendMessage("§cYou can't migrate YAML datas to a the DB system if you're already using the DB system."); + cmd.sender.sendMessage("§cYou can't migrate YAML datas to a DB system if you are already using the DB system."); return; } - Database db = new Database(BeautyQuests.getInstance().getConfig().getConfigurationSection("database")); - cmd.sender.sendMessage("§aConnecting to the database."); - if (db.openConnection()) { - cmd.sender.sendMessage("§aConnection to database etablished."); - }else { - cmd.sender.sendMessage("§cConnection to database has failed. Aborting."); - db.closeConnection(); - db = null; - return; - } - try { - cmd.sender.sendMessage(PlayersManagerDB.migrate(db, (PlayersManagerYAML) PlayersManager.manager)); - }catch (Exception e) { - e.printStackTrace(); - cmd.sender.sendMessage("§cAn exception occured during migration. Process aborted."); - } + Utils.runAsync(() -> { + cmd.sender.sendMessage("§aConnecting to the database."); + Database db = null; + try { + db = new Database(BeautyQuests.getInstance().getConfig().getConfigurationSection("database")); + db.testConnection(); + cmd.sender.sendMessage("§aConnection to database etablished."); + final Database fdb = db; + Utils.runSync(() -> { + cmd.sender.sendMessage("§aStarting migration..."); + try { + cmd.sender.sendMessage(PlayersManagerDB.migrate(fdb, (PlayersManagerYAML) PlayersManager.manager)); + }catch (Exception ex) { + cmd.sender.sendMessage("§cAn exception occured during migration. Process aborted. " + ex.getMessage()); + ex.printStackTrace(); + } + }); + }catch (SQLException ex) { + cmd.sender.sendMessage("§cConnection to database has failed. Aborting. " + ex.getMessage()); + BeautyQuests.logger.severe("An error occurred while connecting to the database for datas migration.", ex); + if (db != null) db.closeConnection(); + } + }); } @Cmd(permission = "help") diff --git a/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java b/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java index 9c8e9d8f..9ad0e189 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java +++ b/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java @@ -11,7 +11,6 @@ import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.KnowledgeBookMeta; @@ -130,13 +129,19 @@ public static ItemStack nameAndLore(ItemStack is, String name, List lore public static ItemStack clearVisibleAttributes(ItemStack is) { ItemMeta im = is.getItemMeta(); + + // remove name and lore im.setDisplayName(null); + im.setLore(null); + + // add flags to hide various descriptions, + // depending on the item type/attributes/other things if (im.hasEnchants()) im.addItemFlags(ItemFlag.HIDE_ENCHANTS); - if (im.isUnbreakable()) im.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); - if (NMS.getMCVersion() <= 12 || im.hasAttributeModifiers() || im instanceof Damageable) im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); + if (NMS.getMCVersion() >= 11 && im.isUnbreakable()) im.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); + if (is.getType().getMaxDurability() != 0 || (NMS.getMCVersion() > 12 && im.hasAttributeModifiers())) im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); if (im instanceof BookMeta || im instanceof PotionMeta || im instanceof EnchantmentStorageMeta || (NMS.getMCVersion() >= 12 && im instanceof KnowledgeBookMeta)) im.addItemFlags(ItemFlag.HIDE_POTION_EFFECTS); if (im instanceof LeatherArmorMeta) im.addItemFlags(ItemFlag.HIDE_DYE); - im.setLore(null); + is.setItemMeta(im); return is; } diff --git a/core/src/main/java/fr/skytasul/quests/gui/blocks/BlocksGUI.java b/core/src/main/java/fr/skytasul/quests/gui/blocks/BlocksGUI.java index b8fe2c6c..b7060d93 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/blocks/BlocksGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/blocks/BlocksGUI.java @@ -36,6 +36,7 @@ public CustomInventory openLastInv(Player p) { return this; } + @Override public Inventory open(Player p) { inv = Bukkit.createInventory(null, 9, Lang.INVENTORY_BLOCKSLIST.toString()); @@ -45,6 +46,7 @@ public Inventory open(Player p) { return inv = p.openInventory(inv).getTopInventory(); } + @Override public boolean onClick(Player p, Inventory inv, ItemStack is, int slot, ClickType click) { if (slot == 8){ Inventories.closeAndExit(p); @@ -56,12 +58,11 @@ public boolean onClick(Player p, Inventory inv, ItemStack is, int slot, ClickTyp inv.setItem(slot, none); return true; } - SelectBlockGUI sm = Inventories.create(p, new SelectBlockGUI()); - sm.run = (type, amount) -> { + new SelectBlockGUI(true, (type, amount) -> { Inventories.put(p, openLastInv(p), inv); setItem(inv, slot, type.getMaterial(), type.getAsString(), amount); blocks.put(slot, new AbstractMap.SimpleEntry<>(type, amount)); - }; + }).create(p); return true; } diff --git a/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java b/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java index 41705a54..943ce1c3 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java @@ -34,7 +34,8 @@ public class SelectBlockGUI implements CustomInventory{ private ItemStack done = item(XMaterial.DIAMOND, Lang.done.toString()); - public BiConsumer run; + private boolean allowAmount; + private BiConsumer run; public Inventory inv; @@ -43,6 +44,11 @@ public class SelectBlockGUI implements CustomInventory{ private String tag = null; private int amount = 1; + public SelectBlockGUI(boolean allowAmount, BiConsumer run) { + this.allowAmount = allowAmount; + this.run = run; + } + public String name() { return Lang.INVENTORY_BLOCK.toString(); } @@ -56,7 +62,7 @@ public CustomInventory openLastInv(Player p) { public Inventory open(Player p){ inv = Bukkit.createInventory(null, 9, name()); - inv.setItem(1, item(XMaterial.REDSTONE, Lang.Amount.format(amount))); + if (allowAmount) inv.setItem(1, item(XMaterial.REDSTONE, Lang.Amount.format(amount))); if (NMS.getMCVersion() >= 13) inv.setItem(DATA_SLOT, item(XMaterial.COMMAND_BLOCK, Lang.blockData.toString(), Lang.NotSet.toString())); if (NMS.getMCVersion() >= 13) inv.setItem(TAG_SLOT, item(XMaterial.FILLED_MAP, Lang.blockTag.toString(), QuestOption.formatDescription(Lang.blockTagLore.toString()), "", Lang.NotSet.toString())); inv.setItem(8, done.clone()); @@ -149,7 +155,8 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli case TAG_SLOT: Lang.BLOCK_TAGS.send(p, String.join(", ", NMS.getNMS().getAvailableBlockTags())); new TextEditor<>(p, () -> openLastInv(p), obj -> { - if (Bukkit.getTag("blocks", NamespacedKey.fromString((String) obj), Material.class) == null) { + NamespacedKey key = NamespacedKey.fromString((String) obj); + if (key == null || Bukkit.getTag("blocks", key, Material.class) == null) { Lang.INVALID_BLOCK_TAG.send(p, obj); }else { tag = (String) obj; diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java index 92373f77..ce31255f 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java @@ -76,7 +76,7 @@ public Inventory open(Player p){ invName = invName + " #" + edited.getID(); if (NMS.getMCVersion() <= 8 && invName.length() > 32) invName = Lang.INVENTORY_DETAILS.toString(); // 32 characters limit in 1.8 } - inv = Bukkit.createInventory(null, (int) Math.ceil(QuestOptionCreator.creators.values().stream().mapToInt(creator -> creator.slot).max().getAsInt() / 9D) * 9, invName); + inv = Bukkit.createInventory(null, (int) Math.ceil((QuestOptionCreator.creators.values().stream().mapToInt(creator -> creator.slot).max().getAsInt() + 1) / 9D) * 9, invName); for (QuestOptionCreator creator : QuestOptionCreator.creators.values()) { QuestOption option; @@ -320,6 +320,7 @@ public UpdatableItem(int slot) { public static void initialize(){ DebugUtils.logMessage("Initlializing default quest options."); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("pool", 9, OptionQuestPool.class, OptionQuestPool::new, null)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("name", 10, OptionName.class, OptionName::new, null)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("description", 12, OptionDescription.class, OptionDescription::new, null)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("customItem", 13, OptionQuestItem.class, OptionQuestItem::new, QuestsConfiguration.getItemMaterial(), "customMaterial")); @@ -338,7 +339,7 @@ public static void initialize(){ QuestsAPI.registerQuestOption(new QuestOptionCreator<>("auto", 29, OptionAutoQuest.class, OptionAutoQuest::new, false)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("repeatable", 30, OptionRepeatable.class, OptionRepeatable::new, false, "multiple")); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("timer", 31, OptionTimer.class, OptionTimer::new, QuestsConfiguration.getTimeBetween())); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("pool", 34, OptionQuestPool.class, OptionQuestPool::new, null)); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("endSound", 34, OptionEndSound.class, OptionEndSound::new, QuestsConfiguration.getFinishSound())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("firework", 35, OptionFirework.class, OptionFirework::new, QuestsConfiguration.getDefaultFirework())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("requirements", 36, OptionRequirements.class, OptionRequirements::new, new ArrayList<>())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("startRewards", 38, OptionStartRewards.class, OptionStartRewards::new, new ArrayList<>(), "startRewardsList")); diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/ChooseQuestGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/ChooseQuestGUI.java index 17e37ce6..4de2c05f 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/ChooseQuestGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/ChooseQuestGUI.java @@ -1,7 +1,7 @@ package fr.skytasul.quests.gui.quests; +import java.util.Collection; import java.util.Comparator; -import java.util.List; import java.util.function.Consumer; import org.apache.commons.lang.Validate; @@ -28,7 +28,7 @@ public CustomInventory openLastInv(Player p) { return this; } - public ChooseQuestGUI(List quests, Consumer run){ + public ChooseQuestGUI(Collection quests, Consumer run) { super(Lang.INVENTORY_CHOOSE.toString(), DyeColor.MAGENTA, quests); Validate.notNull(run, "Runnable cannot be null"); super.objects.sort(Comparator.naturalOrder()); diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionEndSound.java b/core/src/main/java/fr/skytasul/quests/options/OptionEndSound.java new file mode 100644 index 00000000..28b3b453 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/options/OptionEndSound.java @@ -0,0 +1,31 @@ +package fr.skytasul.quests.options; + +import org.bukkit.entity.Player; + +import fr.skytasul.quests.api.options.QuestOptionString; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.XMaterial; + +public class OptionEndSound extends QuestOptionString { + + @Override + public void sendIndication(Player p) { + Lang.WRITE_END_SOUND.send(p); + } + + @Override + public XMaterial getItemMaterial() { + return XMaterial.JUKEBOX; + } + + @Override + public String getItemName() { + return Lang.endSound.toString(); + } + + @Override + public String getItemDescription() { + return Lang.endSoundLore.toString(); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index 5f331bd1..3c1acdfa 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -1,5 +1,6 @@ package fr.skytasul.quests.players; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -28,7 +29,6 @@ import fr.skytasul.quests.structure.pools.QuestPool; import fr.skytasul.quests.utils.CustomizedObjectTypeAdapter; import fr.skytasul.quests.utils.Database; -import fr.skytasul.quests.utils.Database.BQStatement; import fr.skytasul.quests.utils.DebugUtils; public class PlayersManagerDB extends PlayersManager { @@ -40,33 +40,33 @@ public class PlayersManagerDB extends PlayersManager { private Database db; /* Accounts statements */ - private BQStatement getAccounts; - private BQStatement insertAccount; - private BQStatement deleteAccount; + private String getAccounts; + private String insertAccount; + private String deleteAccount; /* Quest datas statements */ - private BQStatement insertQuestData; - private BQStatement removeQuestData; - private BQStatement getQuestsData; - private BQStatement getQuestAccountData; - - private BQStatement removeExistingQuestDatas; - - private BQStatement updateFinished; - private BQStatement updateTimer; - private BQStatement updateBranch; - private BQStatement updateStage; - private BQStatement[] updateDatas = new BQStatement[5]; - private BQStatement updateFlow; + private String insertQuestData; + private String removeQuestData; + private String getQuestsData; + private String getQuestAccountData; + + private String removeExistingQuestDatas; + + private String updateFinished; + private String updateTimer; + private String updateBranch; + private String updateStage; + private String[] updateDatas = new String[5]; + private String updateFlow; /* Pool datas statements */ - private BQStatement insertPoolData; - private BQStatement removePoolData; - private BQStatement getPoolData; - private BQStatement getPoolAccountData; + private String insertPoolData; + private String removePoolData; + private String getPoolData; + private String getPoolAccountData; - private BQStatement updatePoolLastGive; - private BQStatement updatePoolCompletedQuests; + private String updatePoolLastGive; + private String updatePoolCompletedQuests; public PlayersManagerDB(Database db) { this.db = db; @@ -76,25 +76,27 @@ public PlayersManagerDB(Database db) { } private synchronized void retrievePlayerDatas(PlayerAccount acc) { - try { - PreparedStatement statement = getQuestsData.getStatement(); - statement.setInt(1, acc.index); - ResultSet result = statement.executeQuery(); - while (result.next()) { - int questID = result.getInt("quest_id"); - acc.questDatas.put(questID, new PlayerQuestDatasDB(acc, questID, result)); + try (Connection connection = db.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(getQuestsData)) { + statement.setInt(1, acc.index); + ResultSet result = statement.executeQuery(); + while (result.next()) { + int questID = result.getInt("quest_id"); + acc.questDatas.put(questID, new PlayerQuestDatasDB(acc, questID, result)); + } + result.close(); } - result.close(); - statement = getPoolData.getStatement(); - statement.setInt(1, acc.index); - result = statement.executeQuery(); - while (result.next()) { - int poolID = result.getInt("pool_id"); - String completedQuests = result.getString("completed_quests"); - if (StringUtils.isEmpty(completedQuests)) completedQuests = null; - acc.poolDatas.put(poolID, new PlayerPoolDatasDB(acc, poolID, result.getLong("last_give"), completedQuests == null ? new HashSet<>() : Arrays.stream(completedQuests.split(";")).map(Integer::parseInt).collect(Collectors.toSet()))); + try (PreparedStatement statement = connection.prepareStatement(getPoolData)) { + statement.setInt(1, acc.index); + ResultSet result = statement.executeQuery(); + while (result.next()) { + int poolID = result.getInt("pool_id"); + String completedQuests = result.getString("completed_quests"); + if (StringUtils.isEmpty(completedQuests)) completedQuests = null; + acc.poolDatas.put(poolID, new PlayerPoolDatasDB(acc, poolID, result.getLong("last_give"), completedQuests == null ? new HashSet<>() : Arrays.stream(completedQuests.split(";")).map(Integer::parseInt).collect(Collectors.toSet()))); + } + result.close(); } - result.close(); }catch (SQLException e) { e.printStackTrace(); } @@ -102,41 +104,42 @@ private synchronized void retrievePlayerDatas(PlayerAccount acc) { @Override protected synchronized Entry load(Player player, long joinTimestamp) { - try { + try (Connection connection = db.getConnection()) { String uuid = player.getUniqueId().toString(); - PreparedStatement statement = getAccounts.getStatement(); - statement.setString(1, uuid); - ResultSet result = statement.executeQuery(); - while (result.next()) { - AbstractAccount abs = createAccountFromIdentifier(result.getString("identifier")); - if (abs.isCurrent()) { - PlayerAccount account = new PlayerAccount(abs, result.getInt("id")); - result.close(); - try { - // in order to ensure that, if the player was previously connected to another server, - // its datas have been fully pushed to database, we wait for 0,4 seconds - long timeout = 400 - (System.currentTimeMillis() - joinTimestamp); - if (timeout > 0) wait(timeout); - }catch (InterruptedException e) { - e.printStackTrace(); - Thread.currentThread().interrupt(); + try (PreparedStatement statement = connection.prepareStatement(getAccounts)) { + statement.setString(1, uuid); + ResultSet result = statement.executeQuery(); + while (result.next()) { + AbstractAccount abs = createAccountFromIdentifier(result.getString("identifier")); + if (abs.isCurrent()) { + PlayerAccount account = new PlayerAccount(abs, result.getInt("id")); + result.close(); + try { + // in order to ensure that, if the player was previously connected to another server, + // its datas have been fully pushed to database, we wait for 0,4 seconds + long timeout = 400 - (System.currentTimeMillis() - joinTimestamp); + if (timeout > 0) wait(timeout); + }catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + retrievePlayerDatas(account); + return new AbstractMap.SimpleEntry<>(account, false); } - retrievePlayerDatas(account); - return new AbstractMap.SimpleEntry<>(account, false); } + result.close(); + } + try (PreparedStatement statement = connection.prepareStatement(insertAccount, PreparedStatement.RETURN_GENERATED_KEYS)) { + AbstractAccount absacc = super.createAbstractAccount(player); + statement.setString(1, absacc.getIdentifier()); + statement.setString(2, uuid); + statement.executeUpdate(); + ResultSet result = statement.getGeneratedKeys(); + if (!result.next()) throw new SQLException("The plugin has not been able to create a player account."); + int index = result.getInt(1); // some drivers don't return a ResultSet with correct column names + result.close(); + return new AbstractMap.SimpleEntry<>(new PlayerAccount(absacc, index), true); } - result.close(); - - AbstractAccount absacc = super.createAbstractAccount(player); - statement = insertAccount.getStatement(); - statement.setString(1, absacc.getIdentifier()); - statement.setString(2, uuid); - statement.executeUpdate(); - result = statement.getGeneratedKeys(); - if (!result.next()) throw new SQLException("The plugin has not been able to create a player account."); - int index = result.getInt(1); // some drivers don't return a ResultSet with correct column names - result.close(); - return new AbstractMap.SimpleEntry<>(new PlayerAccount(absacc, index), true); }catch (SQLException e) { e.printStackTrace(); } @@ -145,8 +148,8 @@ protected synchronized Entry load(Player player, long jo @Override protected synchronized void removeAccount(PlayerAccount acc) { - try { - PreparedStatement statement = deleteAccount.getStatement(); + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(deleteAccount)) { statement.setInt(1, acc.index); statement.executeUpdate(); }catch (SQLException ex) { @@ -167,9 +170,9 @@ public PlayerQuestDatas createPlayerQuestDatas(PlayerAccount acc, Quest quest) { @Override public synchronized void playerQuestDataRemoved(PlayerAccount acc, int id, PlayerQuestDatas datas) { - try { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(removeQuestData)) { ((PlayerQuestDatasDB) datas).stop(); - PreparedStatement statement = removeQuestData.getStatement(); statement.setInt(1, acc.index); statement.setInt(2, id); statement.executeUpdate(); @@ -185,8 +188,8 @@ public PlayerPoolDatas createPlayerPoolDatas(PlayerAccount acc, QuestPool pool) @Override public synchronized void playerPoolDataRemoved(PlayerAccount acc, int id, PlayerPoolDatas datas) { - try { - PreparedStatement statement = removePoolData.getStatement(); + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(removePoolData)) { statement.setInt(1, acc.index); statement.setInt(2, id); statement.executeUpdate(); @@ -198,12 +201,12 @@ public synchronized void playerPoolDataRemoved(PlayerAccount acc, int id, Player @Override public synchronized int removeQuestDatas(Quest quest) { int amount = 0; - try { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(removeExistingQuestDatas)) { for (PlayerAccount acc : PlayersManager.cachedAccounts.values()) { PlayerQuestDatasDB datas = (PlayerQuestDatasDB) acc.removeQuestDatasSilently(quest.getID()); if (datas != null) datas.stop(); } - PreparedStatement statement = removeExistingQuestDatas.getStatement(); statement.setInt(1, quest.getID()); amount += statement.executeUpdate(); }catch (SQLException e) { @@ -214,8 +217,8 @@ public synchronized int removeQuestDatas(Quest quest) { } public synchronized boolean hasAccounts(Player p) { - try { - PreparedStatement statement = getAccounts.getStatement(); + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(getAccounts)) { statement.setString(1, p.getUniqueId().toString()); ResultSet result = statement.executeQuery(); boolean has = result.next(); @@ -232,16 +235,16 @@ public void load() { try { createTables(); - getAccounts = db.new BQStatement("SELECT * FROM " + ACCOUNTS_TABLE + " WHERE `player_uuid` = ?"); - insertAccount = db.new BQStatement("INSERT INTO " + ACCOUNTS_TABLE + " (`identifier`, `player_uuid`) VALUES (?, ?)", true); - deleteAccount = db.new BQStatement("DELETE FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?"); + getAccounts = "SELECT * FROM " + ACCOUNTS_TABLE + " WHERE `player_uuid` = ?"; + insertAccount = "INSERT INTO " + ACCOUNTS_TABLE + " (`identifier`, `player_uuid`) VALUES (?, ?)"; + deleteAccount = "DELETE FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?"; - insertQuestData = db.new BQStatement("INSERT INTO " + QUESTS_DATAS_TABLE + " (`account_id`, `quest_id`) VALUES (?, ?)"); - removeQuestData = db.new BQStatement("DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"); - getQuestsData = db.new BQStatement("SELECT * FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ?"); - getQuestAccountData = db.new BQStatement("SELECT 1 FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"); + insertQuestData = "INSERT INTO " + QUESTS_DATAS_TABLE + " (`account_id`, `quest_id`) VALUES (?, ?)"; + removeQuestData = "DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"; + getQuestsData = "SELECT * FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ?"; + getQuestAccountData = "SELECT 1 FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"; - removeExistingQuestDatas = db.new BQStatement("DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `quest_id` = ?"); + removeExistingQuestDatas = "DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `quest_id` = ?"; updateFinished = prepareDatasStatement("finished"); updateTimer = prepareDatasStatement("timer"); @@ -252,20 +255,20 @@ public void load() { } updateFlow = prepareDatasStatement("quest_flow"); - insertPoolData = db.new BQStatement("INSERT INTO " + POOLS_DATAS_TABLE + " (`account_id`, `pool_id`) VALUES (?, ?)"); - removePoolData = db.new BQStatement("DELETE FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"); - getPoolData = db.new BQStatement("SELECT * FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ?"); - getPoolAccountData = db.new BQStatement("SELECT 1 FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"); + insertPoolData = "INSERT INTO " + POOLS_DATAS_TABLE + " (`account_id`, `pool_id`) VALUES (?, ?)"; + removePoolData = "DELETE FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"; + getPoolData = "SELECT * FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ?"; + getPoolAccountData = "SELECT 1 FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"; - updatePoolLastGive = db.new BQStatement("UPDATE " + POOLS_DATAS_TABLE + " SET `last_give` = ? WHERE `account_id` = ? AND `pool_id` = ?"); - updatePoolCompletedQuests = db.new BQStatement("UPDATE " + POOLS_DATAS_TABLE + " SET `completed_quests` = ? WHERE `account_id` = ? AND `pool_id` = ?"); + updatePoolLastGive = "UPDATE " + POOLS_DATAS_TABLE + " SET `last_give` = ? WHERE `account_id` = ? AND `pool_id` = ?"; + updatePoolCompletedQuests = "UPDATE " + POOLS_DATAS_TABLE + " SET `completed_quests` = ? WHERE `account_id` = ? AND `pool_id` = ?"; }catch (SQLException e) { e.printStackTrace(); } } - private BQStatement prepareDatasStatement(String column) throws SQLException { - return db.new BQStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `" + column + "` = ? WHERE `account_id` = ? AND `quest_id` = ?"); + private String prepareDatasStatement(String column) throws SQLException { + return "UPDATE " + QUESTS_DATAS_TABLE + " SET `" + column + "` = ? WHERE `account_id` = ? AND `quest_id` = ?"; } @Override @@ -274,7 +277,7 @@ public void save() { } private void createTables() throws SQLException { - try (Statement statement = db.getConnection().createStatement()) { + try (Connection connection = db.getConnection(); Statement statement = connection.createStatement()) { statement.execute("CREATE TABLE IF NOT EXISTS " + ACCOUNTS_TABLE + " (" + " `id` int NOT NULL AUTO_INCREMENT ," + " `identifier` text NOT NULL ," @@ -307,7 +310,7 @@ private void createTables() throws SQLException { + ")"); List columns = new ArrayList<>(14); - try (ResultSet set = db.getConnection().getMetaData().getColumns(db.getDatabase(), null, QUESTS_DATAS_TABLE, null)) { + try (ResultSet set = connection.getMetaData().getColumns(db.getDatabase(), null, QUESTS_DATAS_TABLE, null)) { while (set.next()) { columns.add(set.getString("COLUMN_NAME").toLowerCase()); } @@ -323,64 +326,72 @@ private void createTables() throws SQLException { } public static synchronized String migrate(Database db, PlayersManagerYAML yaml) throws SQLException { - ResultSet result = db.getConnection().getMetaData().getTables(null, null, "%", null); - while (result.next()) { - String tableName = result.getString(3); - if (tableName.equals("player_accounts") || tableName.equals("player_quests")) { - result.close(); - return "§cTable \"" + tableName + "\" already exists. Please drop it before migration."; + try (Connection connection = db.getConnection()) { + ResultSet result = connection.getMetaData().getTables(null, null, "%", null); + while (result.next()) { + String tableName = result.getString(3); + if (tableName.equals("player_accounts") || tableName.equals("player_quests")) { + result.close(); + return "§cTable \"" + tableName + "\" already exists. Please drop it before migration."; + } } - } - result.close(); - - PlayersManagerDB manager = new PlayersManagerDB(db); - manager.createTables(); - - PreparedStatement insertAccount = db.prepareStatement("INSERT INTO " + manager.ACCOUNTS_TABLE + " (`id`, `identifier`, `player_uuid`) VALUES (?, ?, ?)"); - PreparedStatement insertQuestData = - db.prepareStatement("INSERT INTO " + manager.QUESTS_DATAS_TABLE + " (`account_id`, `quest_id`, `finished`, `timer`, `current_branch`, `current_stage`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - PreparedStatement insertPoolData = - db.prepareStatement("INSERT INTO " + manager.POOLS_DATAS_TABLE + " (`account_id`, `pool_id`, `last_give`, `completed_quests`) VALUES (?, ?, ?, ?)"); - - int amount = 0, failed = 0; - yaml.loadAllAccounts(); - for (PlayerAccount acc : yaml.loadedAccounts.values()) { - try { - insertAccount.setInt(1, acc.index); - insertAccount.setString(2, acc.abstractAcc.getIdentifier()); - insertAccount.setString(3, acc.getOfflinePlayer().getUniqueId().toString()); - insertAccount.executeUpdate(); - - for (Entry entry : acc.questDatas.entrySet()) { - insertQuestData.setInt(1, acc.index); - insertQuestData.setInt(2, entry.getKey()); - insertQuestData.setInt(3, entry.getValue().getTimesFinished()); - insertQuestData.setLong(4, entry.getValue().getTimer()); - insertQuestData.setInt(5, entry.getValue().getBranch()); - insertQuestData.setInt(6, entry.getValue().getStage()); - for (int i = 0; i < 5; i++) { - Map stageDatas = entry.getValue().getStageDatas(i); - insertQuestData.setString(7 + i, stageDatas == null ? null : CustomizedObjectTypeAdapter.GSON.toJson(stageDatas)); + result.close(); + + PlayersManagerDB manager = new PlayersManagerDB(db); + manager.createTables(); + + PreparedStatement insertAccount = + connection.prepareStatement("INSERT INTO " + manager.ACCOUNTS_TABLE + " (`id`, `identifier`, `player_uuid`) VALUES (?, ?, ?)"); + PreparedStatement insertQuestData = + connection.prepareStatement("INSERT INTO " + manager.QUESTS_DATAS_TABLE + + " (`account_id`, `quest_id`, `finished`, `timer`, `current_branch`, `current_stage`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PreparedStatement insertPoolData = + connection.prepareStatement("INSERT INTO " + manager.POOLS_DATAS_TABLE + " (`account_id`, `pool_id`, `last_give`, `completed_quests`) VALUES (?, ?, ?, ?)"); + + int amount = 0, failed = 0; + yaml.loadAllAccounts(); + for (PlayerAccount acc : yaml.loadedAccounts.values()) { + try { + insertAccount.setInt(1, acc.index); + insertAccount.setString(2, acc.abstractAcc.getIdentifier()); + insertAccount.setString(3, acc.getOfflinePlayer().getUniqueId().toString()); + insertAccount.executeUpdate(); + + for (Entry entry : acc.questDatas.entrySet()) { + insertQuestData.setInt(1, acc.index); + insertQuestData.setInt(2, entry.getKey()); + insertQuestData.setInt(3, entry.getValue().getTimesFinished()); + insertQuestData.setLong(4, entry.getValue().getTimer()); + insertQuestData.setInt(5, entry.getValue().getBranch()); + insertQuestData.setInt(6, entry.getValue().getStage()); + for (int i = 0; i < 5; i++) { + Map stageDatas = entry.getValue().getStageDatas(i); + insertQuestData.setString(7 + i, stageDatas == null ? null : CustomizedObjectTypeAdapter.GSON.toJson(stageDatas)); + } + insertQuestData.executeUpdate(); } - insertQuestData.executeUpdate(); - } - - for (Entry entry : acc.poolDatas.entrySet()) { - insertPoolData.setInt(1, acc.index); - insertPoolData.setInt(2, entry.getKey()); - insertPoolData.setLong(3, entry.getValue().getLastGive()); - insertPoolData.setString(4, getCompletedQuestsString(entry.getValue().getCompletedQuests())); - insertPoolData.executeUpdate(); + + for (Entry entry : acc.poolDatas.entrySet()) { + insertPoolData.setInt(1, acc.index); + insertPoolData.setInt(2, entry.getKey()); + insertPoolData.setLong(3, entry.getValue().getLastGive()); + insertPoolData.setString(4, getCompletedQuestsString(entry.getValue().getCompletedQuests())); + insertPoolData.executeUpdate(); + } + + amount++; + }catch (Exception ex) { + BeautyQuests.logger.severe("Failed to migrate datas for account " + acc.debugName(), ex); + failed++; } - - amount++; - }catch (Exception ex) { - BeautyQuests.logger.severe("Failed to migrate datas for account " + acc.debugName(), ex); - failed++; } + + insertAccount.close(); + insertQuestData.close(); + insertPoolData.close(); + + return "§aMigration succeed! " + amount + " accounts migrated, " + failed + " accounts failed to migrate.\n§oDatabase saving system is §lnot§r§a§o enabled. You need to reboot the server with the line \"database.enabled\" set to true."; } - - return "§aMigration succeed! " + amount + " accounts migrated, " + failed + " accounts failed to migrate.\n§oDatabase saving system is §lnot§r§a§o enabled. You need to reboot the server with the line \"database.enabled\" set to true."; } @Override @@ -403,7 +414,7 @@ public class PlayerQuestDatasDB extends PlayerQuestDatas { private static final int DATA_FLUSHING_TIME = 10; - private Map> cachedDatas = new HashMap<>(5); + private Map> cachedDatas = new HashMap<>(5); private Lock datasLock = new ReentrantLock(); private boolean disabled = false; @@ -468,8 +479,8 @@ public void resetQuestFlow() { super.resetQuestFlow(); setDataStatement(updateFlow, null, true); } - - private void setDataStatement(BQStatement dataStatement, Object data, boolean allowNull) { + + private void setDataStatement(String dataStatement, Object data, boolean allowNull) { if (disabled) return; try { datasLock.lock(); @@ -483,43 +494,40 @@ private void setDataStatement(BQStatement dataStatement, Object data, boolean al @Override public void run() { if (disabled) return; + Entry entry = null; + datasLock.lock(); try { - Entry entry = null; - datasLock.lock(); - try { - if (!disabled) { // in case disabled while acquiring lock - entry = cachedDatas.remove(dataStatement); - } - }finally { - datasLock.unlock(); + if (!disabled) { // in case disabled while acquiring lock + entry = cachedDatas.remove(dataStatement); } - if (entry != null) { - synchronized (dataStatement) { - synchronized (getQuestAccountData) { - PreparedStatement statement = getQuestAccountData.getStatement(); - statement.setInt(1, acc.index); - statement.setInt(2, questID); - if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update - synchronized (insertQuestData) { - PreparedStatement insertStatement = insertQuestData.getStatement(); - insertStatement.setInt(1, acc.index); - insertStatement.setInt(2, questID); - insertStatement.executeUpdate(); - } + }finally { + datasLock.unlock(); + } + if (entry != null) { + try (Connection connection = db.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(getQuestAccountData)) { + statement.setInt(1, acc.index); + statement.setInt(2, questID); + if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update + try (PreparedStatement insertStatement = connection.prepareStatement(insertQuestData)) { + insertStatement.setInt(1, acc.index); + insertStatement.setInt(2, questID); + insertStatement.executeUpdate(); } } - PreparedStatement statement = dataStatement.getStatement(); + } + try (PreparedStatement statement = connection.prepareStatement(dataStatement)) { statement.setObject(1, entry.getValue()); statement.setInt(2, acc.index); statement.setInt(3, questID); statement.executeUpdate(); + if (entry.getValue() == null && !allowNull) { + BeautyQuests.logger.warning("Setting an illegal NULL value in statement \"" + dataStatement + "\" for account " + acc.index + " and quest " + questID); + } } - if (entry.getValue() == null && !allowNull) { - BeautyQuests.logger.warning("Setting an illegal NULL value in statement \"" + dataStatement.getStatementCommand() + "\" for account " + acc.index + " and quest " + questID); - } + }catch (SQLException e) { + e.printStackTrace(); } - }catch (SQLException e) { - e.printStackTrace(); } } }; @@ -580,30 +588,27 @@ public void updatedCompletedQuests() { updateData(updatePoolCompletedQuests, getCompletedQuestsString(getCompletedQuests())); } - private void updateData(BQStatement dataStatement, Object data) { - synchronized (dataStatement) { - try { - synchronized (getPoolAccountData) { - PreparedStatement statement = getPoolAccountData.getStatement(); - statement.setInt(1, acc.index); - statement.setInt(2, poolID); - if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update - synchronized (insertPoolData) { - PreparedStatement insertStatement = insertPoolData.getStatement(); - insertStatement.setInt(1, acc.index); - insertStatement.setInt(2, poolID); - insertStatement.executeUpdate(); - } + private void updateData(String dataStatement, Object data) { + try (Connection connection = db.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(getPoolAccountData)) { + statement.setInt(1, acc.index); + statement.setInt(2, poolID); + if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update + try (PreparedStatement insertStatement = connection.prepareStatement(insertPoolData)) { + insertStatement.setInt(1, acc.index); + insertStatement.setInt(2, poolID); + insertStatement.executeUpdate(); } } - PreparedStatement statement = dataStatement.getStatement(); + } + try (PreparedStatement statement = connection.prepareStatement(dataStatement)) { statement.setObject(1, data); statement.setInt(2, acc.index); statement.setInt(3, poolID); statement.executeUpdate(); - }catch (SQLException e) { - e.printStackTrace(); } + }catch (SQLException e) { + e.printStackTrace(); } } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java index e1729bab..185c6fec 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java @@ -270,7 +270,7 @@ public void load() { if (identifiersIndex.size() >= ACCOUNTS_THRESHOLD) { BeautyQuests.logger.warning( - "⚠ WARNING - " + identifiersIndex.size() + " are registered on this server." + "⚠ WARNING - " + identifiersIndex.size() + " players are registered on this server." + " It is recommended to switch to a SQL database setup in order to keep proper performances and scalability." + " In order to do that, setup your database credentials in config.yml (without enabling it) and run the command" + " /quests migrateDatas. Then follow steps on screen."); @@ -278,12 +278,16 @@ public void load() { } @Override - public void save() { + public synchronized void save() { DebugUtils.logMessage("Saving " + loadedAccounts.size() + " loaded accounts and " + identifiersIndex.size() + " identifiers."); BeautyQuests.getInstance().getDataFile().set("players", identifiersIndex); - for (PlayerAccount acc : loadedAccounts.values()) { + // as the save can take a few seconds and MAY be done asynchronously, + // it is possible that the "loadedAccounts" map is being edited concurrently. + // therefore, we create a new list to avoid this issue. + ArrayList accountToSave = new ArrayList<>(loadedAccounts.values()); + for (PlayerAccount acc : accountToSave) { try { savePlayerFile(acc); }catch (Exception e) { diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java index 58ac1b88..5d40f131 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java @@ -48,11 +48,15 @@ public boolean test(Player p){ String request = hook.onRequest(p, params); if (comparison.isNumberOperation()) { BigDecimal dec1 = new BigDecimal(value); - BigDecimal dec2 = new BigDecimal(request); - int signum = dec2.subtract(dec1).signum(); - if (signum == 0) return comparison.isEqualOperation(); - if (signum == 1) return comparison == ComparisonMethod.GREATER || comparison == ComparisonMethod.GREATER_OR_EQUAL; - if (signum == -1) return comparison == ComparisonMethod.LESS || comparison == ComparisonMethod.LESS_OR_EQUAL; + try { + BigDecimal dec2 = new BigDecimal(request); + int signum = dec2.subtract(dec1).signum(); + if (signum == 0) return comparison.isEqualOperation(); + if (signum == 1) return comparison == ComparisonMethod.GREATER || comparison == ComparisonMethod.GREATER_OR_EQUAL; + if (signum == -1) return comparison == ComparisonMethod.LESS || comparison == ComparisonMethod.LESS_OR_EQUAL; + }catch (NumberFormatException e) { + BeautyQuests.logger.severe("Cannot parse placeholder " + rawPlaceholder + " for player " + p.getName() + ": got " + request + ", which is not a number. (" + debugName() + ")"); + } return false; } if (comparison == ComparisonMethod.DIFFERENT) return !value.equals(request); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java b/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java index c2ef8039..e9051e05 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java @@ -12,7 +12,6 @@ import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.comparison.ItemComparisonMap; -import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.ItemsGUI; @@ -107,17 +106,18 @@ public void start(PlayerAccount acc) { protected void initDialogRunner() { super.initDialogRunner(); + getNPC().addStartablePredicate((p, acc) -> { + return canUpdate(p, false) && checkItems(p, false); + }, this); + dialogRunner.addTest(p -> { - if (branch.isRegularStage(this)) return true; - - boolean canUpdate = canUpdate(p, true); - if (!canUpdate || !checkItems(p, false)) { // if player do not have items - for (AbstractStage stage : branch.getEndingStages().keySet()) { - if (stage instanceof StageBringBack) { // if other ending stage is bring back - StageBringBack other = (StageBringBack) stage; - if (other.getNPC() == getNPC() && other.checkItems(p, false)) return false; // if same NPC and can start: don't cancel event and stop there - } - } + if (getNPC().canGiveSomething(p)) { + // if the NPC can offer something else to the player + // AND the stage cannot complete right now, + // the click will not be handled by this stage + // to let the plugin handle the NPC event (and give + // another quest/complete something else to the player). + if (!canUpdate(p, true) || !checkItems(p, false)) return false; } return true; }); @@ -126,6 +126,12 @@ protected void initDialogRunner() { dialogRunner.addEndAction(this::removeItems); } + @Override + public void unload() { + super.unload(); + if (getNPC() != null) getNPC().removeStartablePredicate(this); + } + @Override public void serialize(Map map) { super.serialize(map); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageBucket.java b/core/src/main/java/fr/skytasul/quests/stages/StageBucket.java index f4643323..a3871d00 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageBucket.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageBucket.java @@ -89,8 +89,12 @@ public static StageBucket deserialize(Map map, QuestBranch branc return new StageBucket(branch, BucketType.valueOf((String) map.get("bucket")), (int) map.get("amount")); } - public static enum BucketType { - WATER(Lang.BucketWater, XMaterial.WATER_BUCKET), LAVA(Lang.BucketLava, XMaterial.LAVA_BUCKET), MILK(Lang.BucketMilk, XMaterial.MILK_BUCKET); + public enum BucketType { + WATER(Lang.BucketWater, XMaterial.WATER_BUCKET), + LAVA(Lang.BucketLava, XMaterial.LAVA_BUCKET), + MILK(Lang.BucketMilk, XMaterial.MILK_BUCKET), + SNOW(Lang.BucketSnow, XMaterial.POWDER_SNOW_BUCKET) + ; private Lang name; private XMaterial type; @@ -109,9 +113,9 @@ public XMaterial getMaterial() { } public static BucketType fromMaterial(XMaterial type) { - if (type == XMaterial.WATER_BUCKET) return WATER; - if (type == XMaterial.LAVA_BUCKET) return LAVA; - if (type == XMaterial.MILK_BUCKET) return MILK; + for (BucketType bucket : values()) { + if (bucket.type == type) return bucket; + } throw new IllegalArgumentException(type.name() + " does not correspond to any bucket type"); } } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index 16f352bf..a5664844 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -16,26 +16,24 @@ import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; -import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.WaitBlockClick; -import fr.skytasul.quests.editors.checkers.MaterialParser; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.blocks.SelectBlockGUI; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.structure.QuestBranch; import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.Utils; -import fr.skytasul.quests.utils.XBlock; import fr.skytasul.quests.utils.XMaterial; +import fr.skytasul.quests.utils.types.BQBlock; public class StageInteract extends AbstractStage { private boolean left; private Location lc; - private XMaterial material; + private BQBlock block; public StageInteract(QuestBranch branch, boolean leftClick, Location location) { super(branch); @@ -43,18 +41,18 @@ public StageInteract(QuestBranch branch, boolean leftClick, Location location) { this.lc = location.getBlock().getLocation(); } - public StageInteract(QuestBranch branch, boolean leftClick, XMaterial material) { + public StageInteract(QuestBranch branch, boolean leftClick, BQBlock block) { super(branch); this.left = leftClick; - this.material = material; + this.block = block; } public Location getLocation(){ return lc; } - public XMaterial getMaterial() { - return material; + public BQBlock getBlockType() { + return block; } public boolean needLeftClick(){ @@ -69,7 +67,7 @@ public void onInteract(PlayerInteractEvent e){ }else if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (lc != null) { if (!e.getClickedBlock().getLocation().equals(lc)) return; - }else if (!XBlock.isType(e.getClickedBlock(), material)) return; + }else if (!block.applies(e.getClickedBlock())) return; Player p = e.getPlayer(); if (hasStarted(p) && canUpdate(p)) { @@ -78,28 +76,38 @@ public void onInteract(PlayerInteractEvent e){ } } + @Override protected String descriptionLine(PlayerAccount acc, Source source){ - return lc == null ? Lang.SCOREBOARD_INTERACT_MATERIAL.format(MinecraftNames.getMaterialName(material)) : Lang.SCOREBOARD_INTERACT.format(lc.getBlockX() + " " + lc.getBlockY() + " " + lc.getBlockZ()); + return lc == null ? Lang.SCOREBOARD_INTERACT_MATERIAL.format(block.getName()) : Lang.SCOREBOARD_INTERACT.format(lc.getBlockX() + " " + lc.getBlockY() + " " + lc.getBlockZ()); } + @Override protected void serialize(Map map){ map.put("leftClick", left); if (lc == null) { - map.put("material", material.name()); + map.put("block", block.getAsString()); }else map.put("location", lc.serialize()); } public static StageInteract deserialize(Map map, QuestBranch branch) { if (map.containsKey("location")) { return new StageInteract(branch, (boolean) map.get("leftClick"), Location.deserialize((Map) map.get("location"))); - }else return new StageInteract(branch, (boolean) map.get("leftClick"), XMaterial.valueOf((String) map.get("material"))); + }else { + BQBlock block; + if (map.containsKey("material")) { + block = new BQBlock.BQBlockMaterial(XMaterial.valueOf((String) map.get("material"))); + }else { + block = BQBlock.fromString((String) map.get("block")); + } + return new StageInteract(branch, (boolean) map.get("leftClick"), block); + } } public static class Creator extends StageCreation { private boolean leftClick = false; private Location location; - private XMaterial material; + private BQBlock block; public Creator(Line line, boolean ending) { super(line, ending); @@ -128,18 +136,17 @@ public void setLocation(Location location) { this.location = location; } - public void setMaterial(XMaterial material) { - if (this.material == null) { + public void setMaterial(BQBlock block) { + if (this.block == null) { line.setItem(7, ItemUtils.item(XMaterial.STICK, Lang.blockMaterial.toString()), (p, item) -> { - Lang.BLOCK_NAME.send(p); - new TextEditor<>(p, () -> reopenGUI(p, false), newMaterial -> { - setMaterial(newMaterial); - reopenGUI(p, false); - }, new MaterialParser(false, true)).enter(); + new SelectBlockGUI(false, (newBlock, __) -> { + setMaterial(newBlock); + reopenGUI(p, true); + }).create(p); }); } - line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(material.name()))); - this.material = material; + line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(block.getName()))); + this.block = block; } @Override @@ -153,11 +160,10 @@ public void start(Player p) { reopenGUI(p, true); }, ItemUtils.item(XMaterial.STICK, Lang.blockLocation.toString())).enter(); }, () -> { - Lang.BLOCK_NAME.send(p); - new TextEditor<>(p, cancel, material -> { - setMaterial(material); + new SelectBlockGUI(false, (newBlock, __) -> { + setMaterial(newBlock); reopenGUI(p, true); - }, new MaterialParser(false, true)).enter(); + }).create(p); }).create(p); } @@ -166,7 +172,7 @@ public void edit(StageInteract stage) { super.edit(stage); if (stage.lc != null) { setLocation(stage.getLocation()); - }else setMaterial(stage.material); + }else setMaterial(stage.getBlockType()); setLeftClick(stage.needLeftClick()); } @@ -174,7 +180,7 @@ public void edit(StageInteract stage) { public StageInteract finishStage(QuestBranch branch) { if (location != null) { return new StageInteract(branch, leftClick, location); - }else return new StageInteract(branch, leftClick, material); + }else return new StageInteract(branch, leftClick, block); } private class ChooseActionGUI implements CustomInventory { diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java index 3b46120a..4e23187f 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java @@ -30,7 +30,6 @@ import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.gui.npc.SelectGUI; import fr.skytasul.quests.players.PlayerAccount; -import fr.skytasul.quests.structure.NPCStarter; import fr.skytasul.quests.structure.QuestBranch; import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Lang; @@ -178,6 +177,8 @@ protected Object[] descriptionFormat(PlayerAccount acc, Source source) { } protected void initDialogRunner() { + if (dialogRunner != null) throw new IllegalStateException("Dialog runner already initialized"); + dialogRunner = new DialogRunner(dialog, npc); dialogRunner.addTest(super::hasStarted); dialogRunner.addTestCancelling(p -> canUpdate(p, true)); @@ -212,20 +213,17 @@ public void joins(PlayerAccount acc, Player p) { private void cachePlayer(Player p) { cached.add(p); - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - if (starter != null) starter.hideForPlayer(p, this); + if (npc != null) npc.hideForPlayer(p, this); } private void uncachePlayer(Player p) { cached.remove(p); - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - if (starter != null) starter.removeHiddenForPlayer(p, this); + if (npc != null) npc.removeHiddenForPlayer(p, this); } private void uncacheAll() { if (QuestsConfiguration.handleGPS() && !hide) cached.forEach(GPS::stopCompass); - NPCStarter starter = BeautyQuests.getInstance().getNPCs().get(npc); - if (starter != null) cached.forEach(p -> starter.removeHiddenForPlayer(p, this)); + if (npc != null) cached.forEach(p -> npc.removeHiddenForPlayer(p, this)); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/structure/NPCStarter.java b/core/src/main/java/fr/skytasul/quests/structure/NPCStarter.java deleted file mode 100644 index 9babb4b5..00000000 --- a/core/src/main/java/fr/skytasul/quests/structure/NPCStarter.java +++ /dev/null @@ -1,342 +0,0 @@ -package fr.skytasul.quests.structure; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeSet; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; -import org.bukkit.Location; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; - -import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.QuestsConfiguration; -import fr.skytasul.quests.api.AbstractHolograms; -import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.npcs.BQNPC; -import fr.skytasul.quests.options.OptionHologramLaunch; -import fr.skytasul.quests.options.OptionHologramLaunchNo; -import fr.skytasul.quests.options.OptionHologramText; -import fr.skytasul.quests.options.OptionStarterNPC; -import fr.skytasul.quests.players.PlayerAccount; -import fr.skytasul.quests.players.PlayersManager; -import fr.skytasul.quests.structure.pools.QuestPool; -import fr.skytasul.quests.utils.DebugUtils; -import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; - -public class NPCStarter { - - private BQNPC npc; - private Set quests = new TreeSet<>(); - private Set pools = new TreeSet<>(); - - private BukkitTask launcheableTask; - - /* Holograms */ - private BukkitTask hologramsTask; - private boolean hologramsRemoved = true; - private Hologram hologramText = new Hologram(false, QuestsAPI.hasHologramsManager() && !QuestsConfiguration.isTextHologramDisabled(), Lang.HologramText.toString()); - private Hologram hologramLaunch = new Hologram(false, QuestsAPI.hasHologramsManager() && QuestsAPI.getHologramsManager().supportItems(), QuestsConfiguration.getHoloLaunchItem()); - private Hologram hologramLaunchNo = new Hologram(false, QuestsAPI.hasHologramsManager() && QuestsAPI.getHologramsManager().supportItems() && QuestsAPI.getHologramsManager().supportPerPlayerVisibility(), QuestsConfiguration.getHoloLaunchNoItem()); - private Hologram hologramPool = new Hologram(false, QuestsAPI.hasHologramsManager() && QuestsAPI.getHologramsManager().supportPerPlayerVisibility(), Lang.PoolHologramText.toString()) { - @Override - public double getYAdd() { - return hologramText.visible ? 0.3 : 0; - } - }; - - private List> hiddenTickets = new ArrayList<>(); - - public NPCStarter(BQNPC npc) { - Validate.notNull(npc, "NPC cannot be null"); - this.npc = npc; - - launcheableTask = new BukkitRunnable() { - private boolean holograms = hologramLaunch.enabled || hologramLaunchNo.enabled || hologramPool.enabled; - private int timer = 0; - @Override - public void run() { - if (!npc.isSpawned()) return; - - if (timer-- == 0) { - timer = QuestsConfiguration.getRequirementUpdateTime(); - return; - } - - LivingEntity en; - try { - en = (LivingEntity) npc.getEntity(); - }catch (ClassCastException ex) {return;} - - for (Quest quest : quests) quest.launcheable.clear(); - - Set playersInRadius = new HashSet<>(); - Location lc = en.getLocation(); - for (Player p : lc.getWorld().getPlayers()) { - PlayerAccount acc = PlayersManager.getPlayerAccount(p); - if (acc == null) continue; - if (lc.distanceSquared(p.getLocation()) > QuestsConfiguration.getStartParticleDistanceSquared()) continue; - playersInRadius.add(p); - try{ - for (Quest quest : quests) { - if (quest.isLauncheable(p, acc, false)) { - quest.launcheable.add(p); - break; - } - } - }catch (NullPointerException ex) {} - } - for (Quest quest : quests) quest.updateLauncheable(en); - - if (!holograms && !quests.isEmpty()) return; - List launcheable = new ArrayList<>(); - List unlauncheable = new ArrayList<>(); - for (Iterator iterator = playersInRadius.iterator(); iterator.hasNext();) { - Player player = iterator.next(); - if (hiddenTickets.stream().anyMatch(entry -> entry.getKey() == player)) { - iterator.remove(); - continue; - } - PlayerAccount acc = PlayersManager.getPlayerAccount(player); - boolean launchYes = false; - boolean launchNo = false; - for (Quest qu : quests) { - if (!qu.hasStarted(acc)) { - boolean pLauncheable = qu.launcheable.contains(player); - if (hologramLaunch.enabled && pLauncheable) { - launchYes = true; - break; // launcheable take priority over not launcheable - }else if (hologramLaunchNo.enabled && !pLauncheable) { - launchNo = true; - } - } - } - if (launchYes) { - launcheable.add(player); - iterator.remove(); - }else if (launchNo) { - unlauncheable.add(player); - iterator.remove(); - } - } - hologramLaunch.setVisible(launcheable); - hologramLaunchNo.setVisible(unlauncheable); - for (Player p : playersInRadius) { - boolean visible = false; - for (QuestPool pool : pools) { - if (pool.canGive(p, PlayersManager.getPlayerAccount(p))) { - visible = true; - break; - } - } - hologramPool.setVisible(p, visible); - } - } - }.runTaskTimer(BeautyQuests.getInstance(), 20L, 20L); - - - if (!hologramText.enabled && !hologramLaunch.enabled && !hologramLaunchNo.enabled && !hologramPool.enabled) return; // no hologram: no need to launch the update task - hologramsTask = new BukkitRunnable() { - @Override - public void run(){ - LivingEntity en = null; // check if NPC is spawned and living - if (npc.isSpawned()){ - try { - en = (LivingEntity) npc.getEntity(); - }catch (ClassCastException ex) {} - } - if (en == null){ - if (!hologramsRemoved) removeHolograms(); // if the NPC is not living and holograms have not been already removed before - return; - } - hologramsRemoved = false; - - if (hologramText.canAppear && hologramText.visible) hologramText.refresh(en); - if (hologramLaunch.canAppear) hologramLaunch.refresh(en); - if (hologramLaunchNo.canAppear) hologramLaunchNo.refresh(en); - if (hologramPool.canAppear && hologramPool.visible) hologramPool.refresh(en); - } - }.runTaskTimer(BeautyQuests.getInstance(), 20L, 1L); - } - - public BQNPC getNPC() { - return npc; - } - - public Set getQuests() { - return quests; - } - - public Hologram getHologramText(){ - return hologramText; - } - - public Hologram getHologramLaunch(){ - return hologramLaunch; - } - - public Hologram getHologramLaunchNo(){ - return hologramLaunchNo; - } - - public void addQuest(Quest quest) { - if (!quests.add(quest)) return; - if (hologramText.enabled && quest.hasOption(OptionHologramText.class)) hologramText.setText(quest.getOption(OptionHologramText.class).getValue()); - if (hologramLaunch.enabled && quest.hasOption(OptionHologramLaunch.class)) hologramLaunch.setItem(quest.getOption(OptionHologramLaunch.class).getValue()); - if (hologramLaunchNo.enabled && quest.hasOption(OptionHologramLaunchNo.class)) hologramLaunchNo.setItem(quest.getOption(OptionHologramLaunchNo.class).getValue()); - hologramText.visible = true; - } - - public boolean removeQuest(Quest quest) { - boolean b = quests.remove(quest); - if (isEmpty()) { - delete("Quest remove"); - }else if (quests.isEmpty()) { - hologramText.visible = false; - hologramText.delete(); - } - return b; - } - - public Set getPools() { - return pools; - } - - public void addPool(QuestPool pool) { - if (!pools.add(pool)) return; - if (hologramPool.enabled && (pool.getHologram() != null)) hologramPool.setText(pool.getHologram()); - hologramPool.visible = true; - } - - public boolean removePool(QuestPool pool) { - boolean b = pools.remove(pool); - if (isEmpty()) { - delete("Pool remove"); - }else if (pools.isEmpty()) { - hologramPool.visible = false; - hologramPool.delete(); - } - return b; - } - - public void hideForPlayer(Player p, Object holder) { - hiddenTickets.add(new AbstractMap.SimpleEntry<>(p, holder)); - } - - public void removeHiddenForPlayer(Player p, Object holder) { - for (Iterator> iterator = hiddenTickets.iterator(); iterator.hasNext();) { - Entry entry = iterator.next(); - if (entry.getKey() == p && entry.getValue() == holder) { - iterator.remove(); - return; - } - } - } - - public void removeHolograms(){ - hologramText.delete(); - hologramLaunch.delete(); - hologramLaunchNo.delete(); - hologramPool.delete(); - hologramsRemoved = true; - } - - public boolean isEmpty() { - return quests.isEmpty() && pools.isEmpty(); - } - - public void delete(String cause) { - DebugUtils.logMessage("Removing NPC Starter " + npc.getId()); - for (Quest qu : quests) { - BeautyQuests.logger.warning("Starter NPC #" + npc.getId() + " has been removed from quest " + qu.getID() + ". Reason: " + cause); - qu.removeOption(OptionStarterNPC.class); - } - quests = null; - for (QuestPool pool : pools) { - BeautyQuests.logger.warning("NPC #" + npc.getId() + " has been removed from pool " + pool.getID() + ". Reason: " + cause); - pool.unloadStarter(); - } - BeautyQuests.getInstance().getNPCs().remove(npc); - launcheableTask.cancel(); - if (hologramsTask != null) hologramsTask.cancel(); - removeHolograms(); - } - - public class Hologram { - boolean visible; - boolean enabled; - boolean canAppear; - AbstractHolograms.BQHologram hologram; - - String text; - ItemStack item; - - public Hologram(boolean visible, boolean enabled, String text){ - this.visible = visible; - this.enabled = enabled; - setText(text); - } - - public Hologram(boolean visible, boolean enabled, ItemStack item){ - this.visible = visible; - this.enabled = enabled; - setItem(item); - } - - public void refresh(LivingEntity en){ - Location lc = Utils.upLocationForEntity(en, getYAdd()); - if (hologram == null){ - create(lc); - }else hologram.teleport(lc); - } - - public double getYAdd() { - return item == null ? 0 : 1; - } - - public void setVisible(List players){ - if (hologram != null) hologram.setPlayersVisible(players); - } - - public void setVisible(Player p, boolean visibility) { - if (hologram != null) hologram.setPlayerVisibility(p, visibility); - } - - public void setText(String text){ - this.text = text; - canAppear = enabled && !StringUtils.isEmpty(text) && !"none".equals(text); - if (!canAppear) delete(); - } - - public void setItem(ItemStack item) { - this.item = item; - canAppear = enabled && item != null; - if (canAppear && QuestsConfiguration.isCustomHologramNameShown() && item.hasItemMeta() && item.getItemMeta().hasDisplayName()) this.text = item.getItemMeta().getDisplayName(); - if (!canAppear) delete(); - } - - public void create(Location lc){ - if (hologram != null) return; - hologram = QuestsAPI.getHologramsManager().createHologram(lc, visible); - if (text != null) hologram.appendTextLine(text); - if (item != null) hologram.appendItem(item); - } - - public void delete(){ - if (hologram == null) return; - hologram.delete(); - hologram = null; - } - } - -} diff --git a/core/src/main/java/fr/skytasul/quests/structure/Quest.java b/core/src/main/java/fr/skytasul/quests/structure/Quest.java index 57d1854f..985d826e 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/Quest.java +++ b/core/src/main/java/fr/skytasul/quests/structure/Quest.java @@ -1,6 +1,8 @@ package fr.skytasul.quests.structure; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; @@ -50,11 +52,11 @@ public class Quest implements Comparable, OptionSet { public boolean asyncEnd = false; public List asyncStart = null; - List launcheable = new ArrayList<>(); + private List launcheable = new ArrayList<>(); private List particles = new ArrayList<>(); public Quest(int id) { - this(id, new File(BeautyQuests.saveFolder, id + ".yml")); + this(id, new File(BeautyQuests.getInstance().getQuestsManager().getSaveFolder(), id + ".yml")); } public Quest(int id, File file) { @@ -67,7 +69,11 @@ public void load() { QuestsAPI.propagateQuestsHandlers(handler -> handler.questLoaded(this)); } - void updateLauncheable(LivingEntity en) { + public List getLauncheable() { + return launcheable; + } + + public void updateLauncheable(LivingEntity en) { if (QuestsConfiguration.showStartParticles()) { if (launcheable.isEmpty()) return; particles.clear(); @@ -228,13 +234,13 @@ public boolean isLauncheable(Player p, PlayerAccount acc, boolean sendMessage) { public boolean testRequirements(Player p, PlayerAccount acc, boolean sendMessage){ if (!p.hasPermission("beautyquests.start")) return false; - if (QuestsConfiguration.getMaxLaunchedQuests() != 0 && !getOptionValueOrDef(OptionBypassLimit.class)) { + if (QuestsConfiguration.getMaxLaunchedQuests() != 0 && Boolean.FALSE.equals(getOptionValueOrDef(OptionBypassLimit.class))) { if (QuestsAPI.getQuests().getStartedSize(acc) >= QuestsConfiguration.getMaxLaunchedQuests()) { if (sendMessage) Lang.QUESTS_MAX_LAUNCHED.send(p, QuestsConfiguration.getMaxLaunchedQuests()); return false; } } - sendMessage = sendMessage && (!hasOption(OptionStarterNPC.class) || (QuestsConfiguration.isRequirementReasonSentOnMultipleQuests() || QuestsAPI.getQuestsAssigneds(getOption(OptionStarterNPC.class).getValue()).size() == 1)); + sendMessage = sendMessage && (!hasOption(OptionStarterNPC.class) || (QuestsConfiguration.isRequirementReasonSentOnMultipleQuests() || getOption(OptionStarterNPC.class).getValue().getQuests().size() == 1)); for (AbstractRequirement ar : getOptionValueOrDef(OptionRequirements.class)) { if (!ar.test(p)) { if (sendMessage) ar.sendReason(p); @@ -350,7 +356,7 @@ public void finish(Player p){ questDatas.setTimer(cal.getTimeInMillis()); } Utils.spawnFirework(p.getLocation(), getOptionValueOrDef(OptionFirework.class)); - Utils.playPluginSound(p, QuestsConfiguration.getFinishSound(), 1); + Utils.playPluginSound(p, getOptionValueOrDef(OptionEndSound.class), 1); QuestsAPI.propagateQuestsHandlers(handler -> handler.questFinish(acc, p, this)); Bukkit.getPluginManager().callEvent(new QuestFinishEvent(p, Quest.this)); @@ -395,15 +401,26 @@ public String toString(){ return "Quest{id=" + id + ", npcID=" + ", branches=" + manager.toString() + ", name=" + getName() + "}"; } - public void saveToFile() throws Exception { + public boolean saveToFile() throws Exception { if (!file.exists()) file.createNewFile(); YamlConfiguration fc = new YamlConfiguration(); BeautyQuests.savingFailure = false; save(fc); - DebugUtils.logMessage("Saving quest " + id + " into " + file.getPath()); - if (BeautyQuests.savingFailure) BeautyQuests.getInstance().createQuestBackup(file.toPath(), "Error when saving quest."); - fc.save(file); + if (BeautyQuests.savingFailure) { + BeautyQuests.logger.warning("An error occurred while saving quest " + id); + return false; + } + String questData = fc.saveToString(); + String oldQuestDatas = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + if (questData.equals(oldQuestDatas)) { + DebugUtils.logMessage("Quest " + id + " was up-to-date."); + return false; + }else { + DebugUtils.logMessage("Saving quest " + id + " into " + file.getPath()); + Files.write(file.toPath(), questData.getBytes(StandardCharsets.UTF_8)); + return true; + } } private void save(ConfigurationSection section) throws Exception{ diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java index 961b51f8..cd4d7c45 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java +++ b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java @@ -27,10 +27,12 @@ public class QuestsManager implements Iterable { private final AtomicInteger lastID = new AtomicInteger(); private final BeautyQuests plugin; + private final File saveFolder; public QuestsManager(BeautyQuests plugin, int lastID, File saveFolder) throws IOException { this.plugin = plugin; this.lastID.set(lastID); + this.saveFolder = saveFolder; try (Stream files = Files.walk(saveFolder.toPath(), Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS)) { files.filter(Files::isRegularFile).filter(path -> !path.getFileName().toString().contains("backup")).filter(path -> "yml".equalsIgnoreCase(Utils.getFilenameExtension(path.getFileName().toString()).orElse(null))).forEach(path -> { @@ -57,6 +59,14 @@ public int incrementLastID() { return lastID.incrementAndGet(); } + public BeautyQuests getPlugin() { + return plugin; + } + + public File getSaveFolder() { + return saveFolder; + } + public List getQuests() { return quests; } @@ -87,11 +97,7 @@ public void unloadQuests() { public void removeQuest(Quest quest) { quests.remove(quest); if (quest.hasOption(OptionStarterNPC.class)) { - BQNPC value = quest.getOptionValueOrDef(OptionStarterNPC.class); - NPCStarter starter = plugin.getNPCs().get(value); - if (starter == null) { - plugin.getLogger().warning("NPC Starter not registered for quest " + quest.getID() + ". NPC: " + (value == null ? "not set" : value.getId())); - }else starter.removeQuest(quest); + quest.getOption(OptionStarterNPC.class).getValue().removeQuest(quest); } } @@ -100,9 +106,7 @@ public void addQuest(Quest quest) { quests.add(quest); if (quest.hasOption(OptionStarterNPC.class)) { BQNPC npc = quest.getOptionValueOrDef(OptionStarterNPC.class); - if (npc != null) { - plugin.getNPCs().computeIfAbsent(npc, NPCStarter::new).addQuest(quest); - } + if (npc != null) npc.addQuest(quest); } quest.load(); } diff --git a/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java b/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java index ea18cbf0..51e00163 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java +++ b/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java @@ -1,6 +1,7 @@ package fr.skytasul.quests.structure.pools; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @@ -18,7 +19,6 @@ import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayerPoolDatas; import fr.skytasul.quests.players.PlayersManager; -import fr.skytasul.quests.structure.NPCStarter; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; @@ -37,7 +37,7 @@ public class QuestPool implements Comparable { private final boolean avoidDuplicates; private final List requirements; - NPCStarter starter; + BQNPC npc; List quests = new ArrayList<>(); QuestPool(int id, int npcID, String hologram, int maxQuests, int questsPerLaunch, boolean redoAllowed, long timeDiff, boolean avoidDuplicates, List requirements) { @@ -52,14 +52,9 @@ public class QuestPool implements Comparable { this.requirements = requirements; if (npcID >= 0) { - BQNPC npc = QuestsAPI.getNPCsManager().getById(npcID); + npc = QuestsAPI.getNPCsManager().getById(npcID); if (npc != null) { - starter = BeautyQuests.getInstance().getNPCs().get(npc); - if (starter == null) { - starter = new NPCStarter(npc); - BeautyQuests.getInstance().getNPCs().put(npc, starter); - } - starter.addPool(this); + npc.addPool(this); return; } } @@ -117,7 +112,8 @@ public int compareTo(QuestPool o) { public ItemStack getItemStack(String action) { return ItemUtils.item(XMaterial.CHEST, Lang.poolItemName.format(id), - Lang.poolItemNPC.format(npcID + " (" + (starter == null ? "unknown" : starter.getNPC().getName()) + ")"), + Lang.poolItemNPC.format(npcID + " (" + (npc == null ? "unknown" : npc.getName()) + + ")"), Lang.poolItemMaxQuests.format(maxQuests), Lang.poolItemQuestsPerLaunch.format(questsPerLaunch), Lang.poolItemRedo.format(redoAllowed ? Lang.Enabled : Lang.Disabled), @@ -150,7 +146,14 @@ public boolean canGive(Player p, PlayerAccount acc) { if (datas.getLastGive() + timeDiff > System.currentTimeMillis()) return false; - if (!requirements.stream().allMatch(x -> x.test(p))) return false; + for (AbstractRequirement requirement : requirements) { + try { + if (!requirement.test(p)) return false; + }catch (Exception ex) { + BeautyQuests.logger.severe("Cannot test requirement " + requirement.getClass().getSimpleName() + " in pool " + id + " for player " + p.getName(), ex); + return false; + } + } List notDoneQuests = avoidDuplicates ? quests.stream().filter(quest -> !datas.getCompletedQuests().contains(quest.getID())).collect(Collectors.toList()) : quests; if (notDoneQuests.isEmpty()) { // all quests completed @@ -171,31 +174,38 @@ public String give(Player p) { List started = new ArrayList<>(questsPerLaunch); for (int i = 0; i < questsPerLaunch; i++) { for (AbstractRequirement requirement : requirements) { - if (!requirement.test(p)) { + try { + if (requirement.test(p)) continue; requirement.sendReason(p); - return null; + }catch (Exception ex) { + BeautyQuests.logger.severe("Cannot test requirement " + requirement.getClass().getSimpleName() + " in pool " + id + " for player " + p.getName(), ex); } + if (started.isEmpty()) return null; + break; } - List notDoneQuests = avoidDuplicates ? quests.stream().filter(quest -> !datas.getCompletedQuests().contains(quest.getID())).collect(Collectors.toList()) : quests; - if (notDoneQuests.isEmpty()) { // all quests completed - if (!redoAllowed) { + List notCompleted = avoidDuplicates ? quests.stream().filter(quest -> !datas.getCompletedQuests().contains(quest.getID())).collect(Collectors.toList()) : quests; + if (notCompleted.isEmpty()) { // all quests completed + notCompleted = replenishQuests(datas); + if (notCompleted.isEmpty()) { + //System.out.println("QuestPool.give() not completed empty"); if (started.isEmpty()) return Lang.POOL_ALL_COMPLETED.toString(); break; } - notDoneQuests = quests.stream().filter(Quest::isRepeatable).collect(Collectors.toList()); - if (notDoneQuests.isEmpty()) { - if (started.isEmpty()) return Lang.POOL_ALL_COMPLETED.toString(); - break; - } - datas.setCompletedQuests(quests.stream().filter(quest -> !quest.isRepeatable()).map(Quest::getID).collect(Collectors.toSet())); }else if (acc.getQuestsDatas().stream().filter(quest -> quest.hasStarted() && quests.contains(quest.getQuest())).count() >= maxQuests) { + //System.out.println("QuestPool.give() max"); if (started.isEmpty()) return Lang.POOL_MAX_QUESTS.format(maxQuests); break; } - List available = notDoneQuests.stream().filter(quest -> quest.isLauncheable(p, acc, false)).collect(Collectors.toList()); + List notStarted = notCompleted.stream().filter(quest -> !quest.hasStarted(acc)).collect(Collectors.toList()); + //System.out.println("QuestPool.give() not completed " + notCompleted.stream().map(x -> Integer.toString(x.getID())).collect(Collectors.joining(" "))); + if (notStarted.isEmpty()) notStarted = replenishQuests(datas); + //System.out.println("QuestPool.give() not started " + notStarted.stream().map(x -> Integer.toString(x.getID())).collect(Collectors.joining(" "))); + + List available = notStarted.stream().filter(quest -> quest.isLauncheable(p, acc, false)).collect(Collectors.toList()); if (available.isEmpty()) { + //System.out.println("QuestPool.give() no available"); if (started.isEmpty()) return Lang.POOL_NO_AVAILABLE.toString(); break; }else { @@ -204,6 +214,7 @@ public String give(Player p) { quest = available.get(ThreadLocalRandom.current().nextInt(available.size())); datas.setTempStartQuest(quest); } + started.add(quest); quest.attemptStart(p, () -> { datas.setLastGive(System.currentTimeMillis()); datas.setTempStartQuest(null); @@ -213,12 +224,28 @@ public String give(Player p) { return "started quest(s) #" + started.stream().map(x -> Integer.toString(x.getID())).collect(Collectors.joining(", ")); } + List replenishQuests(PlayerPoolDatas datas) { + if (!redoAllowed) return Collections.emptyList(); + List notDoneQuests = quests.stream() + .filter(Quest::isRepeatable) + .filter(quest -> !quest.hasStarted(datas.getAccount())) + .collect(Collectors.toList()); + if (!notDoneQuests.isEmpty()) { + datas.setCompletedQuests(quests + .stream() + .filter(quest -> !notDoneQuests.contains(quest)) + .map(Quest::getID) + .collect(Collectors.toSet())); + } + return notDoneQuests; + } + void unload() { - if (starter != null) starter.removePool(this); + if (npc != null) npc.removePool(this); } public void unloadStarter() { - starter = null; + npc = null; } public void save(ConfigurationSection config) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/ChatUtils.java b/core/src/main/java/fr/skytasul/quests/utils/ChatUtils.java index 4cb91acf..2b00d611 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/ChatUtils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/ChatUtils.java @@ -28,6 +28,7 @@ public static void main(String[] args) { test("§aHello §x§1§1§1§1§1§2the§lre, it's me", 12, 30, "§aHello §x§1§1§1§1§1§2the§lre,", "§x§1§1§1§1§1§2§lit's me"); test("§aHello §x§1§1§1§1§1§2there, §lit's me, owo §ofellas §nowo", 26, 100, "§aHello §x§1§1§1§1§1§2there, §lit's me, owo", "§x§1§1§1§1§1§2§l§ofellas §nowo"); test("1 §x§B§B§E§E§D§DHello there, §lhow are you, fellow §ostranger§x§B§B§E§E§D§D?", 40, 100, "1 §x§B§B§E§E§D§DHello there, §lhow are you, fellow", "§x§B§B§E§E§D§D§l§ostranger§x§B§B§E§E§D§D?"); + test("§aHello, thisisaverylongword and thisisa§lverylongword", 10, 25, "§aHello,", "§athisisaver", "§aylongword", "§aand"); } private static int testID = 0; @@ -132,7 +133,7 @@ public static List wordWrap(String rawString, int maxLineLength, int cri //System.out.println("linelength: " + lineLength + " | wordlength: " + wordLength); lines.add(line.toString()); if (word.length() >= maxLineLength || word.length() >= criticalLineLength) { - //System.out.println("entire word too long. split"); + //System.out.println("entire word too long. split: " + word.toString() + " length " + wordLength + " colors " + colorsWord); lines.addAll(splitColoredString(word.toString(), wordLength, maxLineLength, criticalLineLength, colorsWord)); lineLength = 0; line = new StringBuilder(maxLineLength); @@ -219,11 +220,12 @@ private static String getColorDifference(String oldColors, String newColors) { private static List splitColoredString(String string, int stringLength, int maxLength, int criticalLength, String startColors) { List split = new ArrayList<>(); - while (stringLength >= maxLength && string.length() >= criticalLength) { + while (stringLength >= maxLength || string.length() >= criticalLength) { int colorIndex = string.indexOf('§'); - if (colorIndex > maxLength) { // before color -> only text - split.add(string.substring(0, maxLength)); - string = startColors + string.substring(maxLength); + if (colorIndex > maxLength || colorIndex == -1) { // before color -> only text + split.add(startColors + string.substring(0, maxLength)); + string = string.substring(maxLength); + stringLength -= maxLength; }else { int length = 0; int previousIndex = -2; @@ -233,13 +235,30 @@ private static List splitColoredString(String string, int stringLength, length += matcher.start() - previousIndex - 2; if (length > maxLength || matcher.start() > criticalLength) { split.add(string.substring(0, previousIndex)); - string = colors + string.substring(previousIndex); + string = startColors + string.substring(previousIndex); startColors = colors; + matcher = COLOR.matcher(string); + stringLength -= length; + length = 0; + previousIndex = -2; + continue; } colors = appendRawColorString(colors, matcher.group()); previousIndex = matcher.start(); - stringLength -= length; } + length += string.length() - (previousIndex + 2); + int truncated = 0; + if (length >= maxLength) { + truncated = maxLength + startColors.length(); + split.add(string.substring(0, truncated)); + string = startColors + string.substring(truncated); + }else if (string.length() >= criticalLength) { + truncated = criticalLength - startColors.length(); + split.add(string.substring(0, truncated)); + string = startColors + string.substring(truncated); + } + stringLength -= truncated; + startColors = colors; } } split.add(string); diff --git a/core/src/main/java/fr/skytasul/quests/utils/Database.java b/core/src/main/java/fr/skytasul/quests/utils/Database.java index de46d0a7..a6fcef4f 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Database.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Database.java @@ -1,132 +1,99 @@ package fr.skytasul.quests.utils; +import java.io.Closeable; +import java.io.IOException; import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Statement; -import java.util.Properties; + +import javax.sql.DataSource; import org.bukkit.configuration.ConfigurationSection; +import org.mariadb.jdbc.MariaDbPoolDataSource; + +import com.mysql.cj.jdbc.MysqlDataSource; import fr.skytasul.quests.BeautyQuests; public class Database { private ConfigurationSection config; - private Properties properties; - private String host, database; - private int port; + private String databaseName; - private Connection connection; + private DataSource source; - public Database(ConfigurationSection config) { + public Database(ConfigurationSection config) throws SQLException { this.config = config; - this.host = config.getString("host"); - this.database = config.getString("database"); - this.port = config.getInt("port"); + this.databaseName = config.getString("database"); - properties = new Properties(); - properties.setProperty("user", config.getString("username")); - properties.setProperty("password", config.getString("password")); - if (!config.getBoolean("ssl")) { - properties.setProperty("verifyServerCertificate", "false"); - properties.setProperty("useSSL", "false"); + try { + Class.forName("org.mariadb.jdbc.MariaDbPoolDataSource"); + MariaDbPoolDataSource msource = new MariaDbPoolDataSource(); + msource.setServerName(config.getString("host")); + msource.setPortNumber(config.getInt("port")); + msource.setDatabaseName(databaseName); + msource.setUser(config.getString("username")); + msource.setPassword(config.getString("password")); + + msource.setPoolName("beautyquests"); + msource.setMaxIdleTime(60); + msource.setLoginTimeout(20); + + source = msource; + }catch (ClassNotFoundException e) { + MysqlDataSource msource = new MysqlDataSource(); + msource.setServerName(config.getString("host")); + msource.setPortNumber(config.getInt("port")); + msource.setDatabaseName(databaseName); + msource.setUser(config.getString("username")); + msource.setPassword(config.getString("password")); + + msource.setConnectTimeout(20); + boolean ssl = config.getBoolean("ssl"); + msource.setVerifyServerCertificate(ssl); + msource.setUseSSL(ssl); + + source = msource; + } + DebugUtils.logMessage("Created SQL data source: " + source.getClass().getName()); + // Yes, I know there is literally the same code twice. + // Unfortunately, there is no common interface + // between MariaDB and MySQL pool data source + // which provides the configuration methods. + } + + public void testConnection() throws SQLException { + DebugUtils.logMessage("Trying to connect to " + config.getString("host")); + try (Connection connection = source.getConnection()) { + if (!connection.isValid(0)) + throw new SQLException("Could not establish database connection."); } } public String getDatabase() { - return database; + return databaseName; } public ConfigurationSection getConfig() { return config; } - public boolean openConnection() { - if (!isClosed()) return false; - - // it seems no longer useful to load the Driver manually - /*try { - Class.forName("com.mysql.jdbc.Driver"); - }catch (ClassNotFoundException e) { - BeautyQuests.logger.severe("Database driver not found."); - return false; - }*/ - - try { - connection = DriverManager.getConnection("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.getDatabase(), properties); - DebugUtils.logMessage("Opened database connection."); - }catch (SQLException ex) { - BeautyQuests.logger.severe("An exception occurred when connecting to the database.", ex); - return false; - } - return true; - } - - public boolean isClosed() { - try { - return connection == null || connection.isClosed() || !connection.isValid(0); - }catch (SQLException e) { - e.printStackTrace(); - return true; - } - } - public void closeConnection() { - if (!isClosed()) { + if (source instanceof Closeable) { try { - connection.close(); - DebugUtils.logMessage("Closed database connection."); - }catch (SQLException ex) { - BeautyQuests.logger.severe("An exception occurred when closing database connection.", ex); + ((Closeable) source).close(); + }catch (IOException e) { + e.printStackTrace(); } - connection = null; } } - public PreparedStatement prepareStatement(String sql) throws SQLException { - return connection.prepareStatement(sql); + public Connection getConnection() throws SQLException { + return source.getConnection(); } - - public PreparedStatement prepareStatementGeneratedKeys(String sql) throws SQLException { - return connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); - } - - public Connection getConnection() { - return connection; - } - + public static Database getInstance(){ return BeautyQuests.getInstance().getBQDatabase(); } - public class BQStatement { - private final String statement; - private boolean returnGeneratedKeys; - - private PreparedStatement prepared; - - public BQStatement(String statement) { - this(statement, false); - } - - public BQStatement(String statement, boolean returnGeneratedKeys) { - this.statement = statement; - this.returnGeneratedKeys = returnGeneratedKeys; - } - - public PreparedStatement getStatement() throws SQLException { - if (prepared == null || prepared.isClosed() || !prepared.getConnection().isValid(0)) { - openConnection(); - prepared = returnGeneratedKeys ? connection.prepareStatement(statement, Statement.RETURN_GENERATED_KEYS) : connection.prepareStatement(statement); - } - return prepared; - } - - public String getStatementCommand() { - return statement; - } - } } diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index ed5d64d2..e0c283a8 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -64,6 +64,7 @@ public enum Lang{ WRITE_MESSAGE("msg.writeMessage"), WRITE_START_MESSAGE("msg.writeStartMessage"), WRITE_END_MESSAGE("msg.writeEndMsg"), + WRITE_END_SOUND("msg.writeEndSound"), DESC_MESSAGE("msg.writeDescriptionText"), START_TEXT("msg.writeStageText"), MOVE_TELEPORT_POINT("msg.moveToTeleportPoint"), @@ -436,6 +437,8 @@ public enum Lang{ edit("inv.details.editQuestName"), endMessage("inv.details.endMessage"), endMessageLore("inv.details.endMessageLore"), + endSound("inv.details.endSound"), + endSoundLore("inv.details.endSoundLore"), startMessage("inv.details.startMessage"), startMessageLore("inv.details.startMessageLore"), startDialog("inv.details.startDialog"), @@ -745,6 +748,7 @@ public enum Lang{ BucketWater("misc.bucket.water"), BucketLava("misc.bucket.lava"), BucketMilk("misc.bucket.milk"), + BucketSnow("misc.bucket.snow"), ClickRight("misc.click.right"), ClickLeft("misc.click.left"), diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index 57fb85b9..c4e09e48 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -23,7 +23,6 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.EntityType; import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -187,8 +186,24 @@ public static String locationToString(Location lc){ return Lang.teleportation.format(lc.getBlockX(), lc.getBlockY(), lc.getBlockZ(), lc.getWorld() == null ? Lang.Unknown.toString() : lc.getWorld().getName()); } + private static boolean cachedScoreboardPresent = false; + private static long cachedScoreboardPresenceExp = 0; public static Location upLocationForEntity(LivingEntity en, double value) { - return en.getLocation().add(0, QuestsConfiguration.getHologramsHeight() + NMS.getNMS().entityNameplateHeight(en) + value + (en.getType() != EntityType.PLAYER || Bukkit.getScoreboardManager().getMainScoreboard().getObjective(DisplaySlot.BELOW_NAME) == null ? 0.0 : 0.24), 0); + double height = value; + height += QuestsConfiguration.getHologramsHeight(); + height += NMS.getNMS().entityNameplateHeight(en); + if (en instanceof Player) { + if (cachedScoreboardPresenceExp < System.currentTimeMillis()) { + cachedScoreboardPresenceExp = System.currentTimeMillis() + 60_000; + cachedScoreboardPresent = Bukkit.getScoreboardManager().getMainScoreboard().getObjective(DisplaySlot.BELOW_NAME) != null; + // as a new Objective object is allocated each time we check this, + // it is better to cache the boolean for memory consumption. + // scoreboards are not intended to change frequently, therefore it is + // not a problem to cache this value for a minute. + } + if (cachedScoreboardPresent) height += 0.24; + } + return en.getLocation().add(0, height, 0); } public static boolean isSimilar(ItemStack item1, ItemStack item2) { @@ -404,6 +419,7 @@ public static void playPluginSound(Player p, String sound, float volume){ public static void playPluginSound(Player p, String sound, float volume, float pitch) { if (!QuestsConfiguration.playSounds()) return; + if ("none".equals(sound)) return; try { p.playSound(p.getLocation(), Sound.valueOf(sound), volume, pitch); }catch (Exception ex) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java index 76cdd90b..d9be535a 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java @@ -35,6 +35,7 @@ import fr.skytasul.quests.utils.compatibility.mobs.BQBoss; import fr.skytasul.quests.utils.compatibility.mobs.CitizensFactory; import fr.skytasul.quests.utils.compatibility.mobs.MythicMobs; +import fr.skytasul.quests.utils.compatibility.mobs.MythicMobs5; import fr.skytasul.quests.utils.compatibility.npcs.BQCitizens; import fr.skytasul.quests.utils.compatibility.npcs.BQSentinel; import fr.skytasul.quests.utils.compatibility.npcs.BQServerNPCs; @@ -83,13 +84,21 @@ public class DependenciesManager implements Listener { return true; }); + public static final BQDependency mm = new BQDependency("MythicMobs", () -> { + try { + Class.forName("io.lumine.xikage.mythicmobs.mobs.MythicMob"); + QuestsAPI.registerMobFactory(new MythicMobs()); + }catch (ClassNotFoundException ex) { + QuestsAPI.registerMobFactory(new MythicMobs5()); + } + }); + public static final BQDependency holod2 = new BQDependency("HolographicDisplays", () -> QuestsAPI.setHologramsManager(new BQHolographicDisplays2()), null, plugin -> plugin.getClass().getName().equals("com.gmail.filoghost.holographicdisplays.HolographicDisplays")); public static final BQDependency holod3 = new BQDependency("HolographicDisplays", () -> QuestsAPI.setHologramsManager(new BQHolographicDisplays3()), null, plugin -> plugin.getClass().getName().equals("me.filoghost.holographicdisplays.plugin.HolographicDisplays")); public static final BQDependency sentinel = new BQDependency("Sentinel", BQSentinel::initialize); public static final BQDependency wg = new BQDependency("WorldGuard", BQWorldGuard::init, () -> BQWorldGuard.getInstance().disable(), null); - public static final BQDependency mm = new BQDependency("MythicMobs", () -> QuestsAPI.registerMobFactory(new MythicMobs())); public static final BQDependency jobs = new BQDependency("Jobs", () -> QuestsAPI.getRequirements().register(new RequirementCreator("jobLevelRequired", JobLevelRequirement.class, ItemUtils.item(XMaterial.LEATHER_CHESTPLATE, Lang.RJobLvl.toString()), JobLevelRequirement::new))); public static final BQDependency fac = new BQDependency("Factions", () -> QuestsAPI.getRequirements().register(new RequirementCreator("factionRequired", FactionRequirement.class, ItemUtils.item(XMaterial.WITHER_SKELETON_SKULL, Lang.RFaction.toString()), FactionRequirement::new))); public static final BQDependency acc = new BQDependency("AccountsHook"); @@ -205,7 +214,7 @@ boolean testCompatibility(boolean after) { void initialize() { try { if (initialize != null) initialize.run(); - }catch (Exception ex) { + }catch (Throwable ex) { BeautyQuests.logger.severe("An error occurred while initializing " + pluginNames.toString() + " integration", ex); enabled = false; } diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/GPS.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/GPS.java index 8ad4e254..341b2ebe 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/GPS.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/GPS.java @@ -17,6 +17,7 @@ static void init(){ public static boolean launchCompass(Player p, Location location) { if (api.gpsIsActive(p)) return false; + if (location == null) return false; api.startCompass(p, location); return true; } diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java new file mode 100644 index 00000000..73528157 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java @@ -0,0 +1,131 @@ +package fr.skytasul.quests.utils.compatibility.mobs; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import org.bukkit.DyeColor; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.mobs.MobFactory; +import fr.skytasul.quests.api.options.QuestOption; +import fr.skytasul.quests.gui.Inventories; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; +import fr.skytasul.quests.utils.XMaterial; +import fr.skytasul.quests.utils.nms.NMS; + +import io.lumine.mythic.api.mobs.MythicMob; +import io.lumine.mythic.api.skills.placeholders.PlaceholderString; +import io.lumine.mythic.bukkit.MythicBukkit; +import io.lumine.mythic.bukkit.events.MythicMobDeathEvent; + +public class MythicMobs5 implements MobFactory { + + @Override + public String getID() { + return "mythicMobs"; + } + + private ItemStack item = ItemUtils.item(XMaterial.BLAZE_POWDER, Lang.mythicMob.toString()); + @Override + public ItemStack getFactoryItem() { + return item; + } + + @Override + public void itemClick(Player p, Consumer run) { + new PagedGUI("List of MythicMobs", DyeColor.PINK, MythicBukkit.inst().getMobManager().getMobTypes(), null, MythicMob::getInternalName) { + @Override + public ItemStack getItemStack(MythicMob object) { + XMaterial mobItem; + try { + mobItem = XMaterial.mobItem(getEntityType(object)); + }catch (Exception ex) { + mobItem = XMaterial.SPONGE; + BeautyQuests.logger.warning("Unknow entity type for MythicMob " + object.getInternalName(), ex); + } + return ItemUtils.item(mobItem, object.getInternalName()); + } + + @Override + public void click(MythicMob existing, ItemStack item, ClickType clickType) { + Inventories.closeAndExit(p); + run.accept(existing); + } + }.sortValues(MythicMob::getInternalName).create(p); + } + + @Override + public MythicMob fromValue(String value) { + return MythicBukkit.inst().getMobManager().getMythicMob(value).orElse(null); + } + + @Override + public String getValue(MythicMob data) { + return data.getInternalName(); + } + + @Override + public String getName(MythicMob data) { + PlaceholderString displayName = data.getDisplayName(); + if (displayName != null) return displayName.get(); + return data.getInternalName(); + } + + @Override + public EntityType getEntityType(MythicMob data) { + String typeName; + if (data.getEntityType() == null) { + typeName = data.getMythicEntity() == null ? null : data.getMythicEntity().getClass().getSimpleName().substring(6); + }else { + typeName = data.getEntityType().toUpperCase(); + } + if (typeName.contains("BABY_")) typeName = typeName.substring(5); + if (typeName.equalsIgnoreCase("MPET")) typeName = data.getConfig().getString("MPet.Anchor"); + if (NMS.getMCVersion() < 11 && typeName.equals("WITHER_SKELETON")) typeName = "SKELETON"; + EntityType type = EntityType.fromName(typeName); + if (type == null) { + try { + type = EntityType.valueOf(typeName); + }catch (IllegalArgumentException ex) {} + } + return type; + } + + @Override + public List getDescriptiveLore(MythicMob data) { + try { + return Arrays.asList( + QuestOption.formatDescription("Base Health: " + data.getHealth().get()), + QuestOption.formatDescription("Base Damage: " + data.getDamage().get()), + QuestOption.formatDescription("Base Armor: " + data.getArmor().get())); + }catch (NoSuchMethodError e) { + return Arrays.asList("§cError when retrieving mob informations", "§c-> §oPlease update MythicMobs"); + } + } + + @EventHandler + public void onMythicDeath(MythicMobDeathEvent e) { + if (e.getKiller() == null) return; + if (!(e.getKiller() instanceof Player)) return; + callEvent(e, e.getMob().getType(), e.getEntity(), (Player) e.getKiller()); + } + + public static void sendMythicMobsList(Player p){ + Utils.sendMessage(p, Lang.MYTHICMOB_LIST.toString()); + StringBuilder stb = new StringBuilder("§a"); + for (MythicMob mm : MythicBukkit.inst().getMobManager().getMobTypes()) { + stb.append(mm.getInternalName() + "; "); + } + Utils.sendMessage(p, stb.toString()); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java index 5775da0b..965b8041 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java @@ -75,7 +75,7 @@ protected BQNPC create(Location location, EntityType type, String name) { return new BQCitizensNPC(npc); } - public static class BQCitizensNPC implements BQNPC { + public static class BQCitizensNPC extends BQNPC { private NPC npc; diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java index 33e0cf45..26b870e3 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java @@ -65,7 +65,7 @@ public void onInteract(NPCInteractEvent e) { super.clickEvent(null, e.getNpc().getNpcPojo().getId(), e.getPlayer(), e.isLeftClick() ? ClickType.LEFT : ClickType.RIGHT); } - public static class BQServerNPC implements BQNPC { + public static class BQServerNPC extends BQNPC { private final NPC npc; diff --git a/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java b/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java index 27754cb7..b1ab086f 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java +++ b/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java @@ -26,7 +26,7 @@ public class LoggerHandler extends Handler implements ILoggerHandler { private final Date launchDate = new Date(); - private File file; + private final File file; private PrintWriter stream; private SimpleDateFormat format = new SimpleDateFormat("[HH:mm:ss] "); diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java b/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java index 6181c185..33f4789c 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java @@ -78,7 +78,7 @@ public TestResult onClick(Player p) { if (status != null && status.task != null) return TestResult.DENY; } - if (p.isSneaking() && dialog.isSkippable() && test(p) == TestResult.ALLOW) { + if (p.isSneaking() && dialog != null && dialog.isSkippable() && test(p) == TestResult.ALLOW) { Lang.DIALOG_SKIPPED.sendWP(p); removePlayer(p); end(p); diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 49f078a3..9effe8d3 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -47,6 +47,8 @@ msg: writeEndMsg: '§aWrite the message that will be sent at the end of the quest, "null" if you want the default one or "none" if you want none. You can use "{0}" which will be replaced with the rewards obtained.' + writeEndSound: '§aWrite the sound name that will be played to the player at the end of + the quest, "null" if you want the default one or "none" if you want none:' writeDescriptionText: '§aWrite the text which describes the goal of the stage:' writeStageText: '§aWrite the text that will be sent to the player at the beginning of the step:' @@ -455,6 +457,8 @@ inv: editQuestName: §lEdit quest endMessage: §eEdit end message endMessageLore: Message which will be sent to the player at the end of the quest. + endSound: §eEdit end sound + endSoundLore: Sound which will be played at the end of the quest. startMessage: §eEdit start message startMessageLore: Message which will be sent to the player at the beginning of the quest. startDialog: §eEdit start dialog @@ -777,6 +781,7 @@ misc: water: Water bucket lava: Lava bucket milk: Milk bucket + snow: Snow bucket click: right: Right click left: Left click diff --git a/core/src/main/resources/locales/fr_FR.yml b/core/src/main/resources/locales/fr_FR.yml index 6007a020..b03ad474 100644 --- a/core/src/main/resources/locales/fr_FR.yml +++ b/core/src/main/resources/locales/fr_FR.yml @@ -154,7 +154,7 @@ msg: edit: '§6/{0} edit : §eModifier une quête.' remove: '§6/{0} remove : §eSupprimer une quête avec un ID spécifié ou en cliquant sur son NPC.' finishAll: '§6/{0} finishAll : §eFinir toutes les quêtes d''un joueur.' - setStage: '§6/{0} setStage [nouvelle branche] [nouvelle étape]: §ePasser l''étape en cours/lancer une branche/lancer une étape pour une branche.' + setStage: '§6/{0} setStage [nouvelle branche] [nouvelle étape]: §ePasser l''étape en cours/lancer une branche/lancer une étape pour une branche.' startDialog: '§6/{0} startDialog : §eDémarre le dialogue en attente pour une étape "NPC" ou le dialogue de début d''une quête.' resetPlayer: '§6/{0} resetPlayer : §eSupprimer les données d''un joueur.' resetPlayerQuest: '§6/{0} resetPlayerQuest [id] : §eSupprimer les données d''une seule quête pour un joueur.' @@ -736,6 +736,7 @@ misc: water: Seau d'eau lava: Seau de lave milk: Seau de lait + snow: Seau de neige click: right: Clic droit left: Clic gauche diff --git a/core/src/main/resources/locales/th_TH.yml b/core/src/main/resources/locales/th_TH.yml index fd2b56b6..d5652395 100644 --- a/core/src/main/resources/locales/th_TH.yml +++ b/core/src/main/resources/locales/th_TH.yml @@ -11,6 +11,7 @@ msg: cancelling: '§cกระบวนการสร้างเควสถูกยกเลิก.' editCancelling: '§cการแก้ไขเควส ถูกยกเลิก.' invalidID: '§cภารกิจ id {0} ไม่มีอยู่จริง.' + invalidPoolID: '§cThe pool {0} does not exist.' alreadyStarted: '§cคุณเริ่มภารกิจเรียบร้อยแล้ว!' quests: maxLaunched: '§cไม่สามารถรับได้มากกว่า {0} quest(s) ในเวลาเดียวกัน...' @@ -26,6 +27,7 @@ msg: questItem: drop: '§cคุณไม่สามารถทิ้ง ไอเท่ม เควสนี้ได้!' craft: '§cไอเท่มเควสชิ้นนี้ไม่สามารถคราฟได้!' + eat: '§cผู้เล่นไม่สามารถกินของชิ้นนี้ได้!' stageMobs: noMobs: '§cสเตจนี้ไม่จำเป็นต้องฆ่า mobs.' listMobs: '§aคุณต้องฆ่า {0}.' @@ -35,7 +37,9 @@ msg: writeMobAmount: '§aกำหนดจำนวน mobs ที่ต้องฆ่า:' writeMobName: '§aเขียนชื่อที่กำหนดเองของ mob ที่จะฆ่า:' writeChatMessage: '§aเขียนข้อความ: (Add "{SLASH}" หากคุณต้องการให้ใช้ command ในการเริ่มต้น.)' - writeEndMessage: "§aเขียนข้อความที่ต้องการส่ง เมื่อจบภารกิจ หรือจบสเตจ:\n" + writeMessage: '§aเขียนข้อความเพื่อส่งให้ผู้เล่น' + writeStartMessage: "§aWrite the message that will be sent at the beginning of the quest, \"null\" if you want the default one or \"none\" if you want none:\n" + writeEndMsg: "§aWrite the message that will be sent at the end of the quest, \"null\" if you want the default one or \"none\" if you want none. You can use \"{0}\" which will be replaced with the rewards obtained.\n" writeDescriptionText: '§aเขียนข้อความ เพื่ออธิบายเป้าหมายของสเตจ:' writeStageText: '§aเขียนข้อความ ที่จะส่งไปให้ผู้เล่น เมื่อเริ่ม step:' moveToTeleportPoint: '§aไปยังจุด teleport ที่ต้องการ .' @@ -73,11 +77,22 @@ msg: commandsDisabled: ตอนนี้คุณไม่ได้รับอนุญาตให้ใช้ commands! indexOutOfBounds: '§หมายเลข {0} อยู่นอกขอบเขต! ต้องอยู่ระหว่าง {1} ถึง {2}' invalidBlockData: '§blockdata {0} ไม่ถูกต้อง หรือเข้ากันไม่ได้กับ block {1}' + invalidBlockTag: '§cแท็กบล็อกไม่พร้อมใช้งาน {0}' bringBackObjects: พาฉันกลับมาที่ {0} inventoryFull: '§cช่องเก็บ ไอเท่ม ของคุณเต็ม, ไอเทมหล่นลงพื้น.' playerNeverConnected: '§cไม่สามารถค้นหาข้อมูลเกี่ยวกับผู้เล่นนี้ได้ {0}' playerNotOnline: '§cผู้เล่น {0} offline.' + playerDataNotFound: "§cข้อมูลของผู้เล่น {0} ไม่พบ\n" + versionRequired: 'Version required: §l{0}' + restartServer: '§7รีสตาร์ทเซิร์ฟเวอร์ของคุณเพื่อดูการแก้ไข' + dialogs: + skipped: '§8§o Dialog skipped.' command: + downloadTranslations: + syntax: '§cคุณต้องระบุภาษาที่จะดาวน์โหลด ตัวอย่าง: "/quests downloadTranslations en_US"' + notFound: "§cLanguage {0} ไม่พบสำหรับรุ่น {1}\n" + exists: '§cไฟล์ {0} มีอยู่แล้ว ผนวก "จริง" ต่อท้ายคำสั่งของคุณเพื่อเขียนทับ (/quests downloadแปล จริง)' + downloaded: 'ดาวน์โหลด §aLanguage {0} แล้ว! §7ตอนนี้คุณต้องแก้ไขไฟล์ "/plugins/BeautyQuests/config.yml" เพื่อเปลี่ยนค่าของ §ominecraftTranslationsFile§7 ด้วย {0} จากนั้นรีสตาร์ทเซิร์ฟเวอร์' checkpoint: noCheckpoint: '§cไม่พบ checkpoint สำหรับภารกิจ {0}' questNotStarted: '§cคุณไม่ได้ทำภารกิจนี้.' @@ -87,6 +102,11 @@ msg: next: "§a stage ถูกข้าม.\n" nextUnavailable: '§ตัวเลือก "ข้าม" ไม่สามารถใช้ได้เมื่อผู้เล่นอยู่ในจุดสิ้นสุด branch.' set: '§aStage {0} เริ่ม.' + startDialog: + impossible: "§cImpossible to start the dialog now.\n" + noDialog: '§cThe player has no pending dialog.' + alreadyIn: '§cผู้เล่นกำลังเล่นกล่องโต้ตอบอยู่แล้ว' + success: '§aเริ่มกล่องโต้ตอบสำหรับผู้เล่น {0} ในภารกิจ {1}!' playerNeeded: '§cคุณต้องเป็นผู้เล่น เพื่อดำเนินการ command นี้!' incorrectSyntax: '§csyntax ไม่ถูกต้อง.' noPermission: '§c permissions ของคุณใช้ command นี้ไม่ได้! (Required: {0})' @@ -100,11 +120,13 @@ msg: leaveAll: '§คุณบังคับให้สิ้นสุด {0} เควส) ข้อผิดพลาด: {1}' resetPlayer: player: '§6ข้อมูลทั้งหมดเกี่ยวกับภารกิจของคุณ {0} รายการได้ถูกลบโดย {1}.' + remover: '§6{0} ข้อมูลภารกิจ (และ {2} กลุ่ม) ของ {1} ถูกลบแล้ว' resetPlayerQuest: player: '§6ข้อมูลทั้งหมดเกี่ยวกับภารกิจ {0} ถูกลบโดย {1}' remover: '§6 {0} ข้อมูลภารกิจของ {1} ถูกลบไปแล้ว' resetQuest: '§6ลบข้อมูลของภารกิจสำหรับ {0} players.' startQuest: '§6คุณบังคับให้เริ่มภารกิจ {0} (UUID ของผู้เล่น: {1})' + startQuestNoRequirements: '§cผู้เล่นไม่ตรงตามข้อกำหนดสำหรับภารกิจ {0}... ต่อท้าย "จริง" ที่ท้ายคำสั่งของคุณเพื่อข้ามการตรวจสอบข้อกำหนด' cancelQuest: '§6คุณได้ยกเลิกภารกิจ {0}.' cancelQuestUnavailable: '§ภารกิจ {0} ไม่สามารถยกเลิกได้' backupCreated: '§6คุณได้สร้างการสำรองข้อมูลของ ภารกิจ และข้อมูลผู้เล่นทั้งหมดเรียบร้อยแล้ว' @@ -112,6 +134,9 @@ msg: backupQuestsFailed: '§การสร้างข้อมูลสำรองสำหรับเควสทั้งหมดล้มเหลว' adminModeEntered: '§คุณได้เข้าสู่โหมดผู้ดูแลระบบ' adminModeLeft: '§คุณออกจากโหมดผู้ดูแลระบบแล้ว' + resetPlayerPool: + timer: "§aคุณได้รีเซ็ต {0} ตัวจับเวลาพูลของ {1}\n" + full: '§คุณได้รีเซ็ต {0} ข้อมูลพูลของ {1}' scoreboard: lineSet: '§6คุณแก้ไข line เรียบร้อยแล้ว {0}' lineReset: '§6คุณรีเซ็ต line สำเร็จแล้ว {0}' @@ -120,6 +145,9 @@ msg: resetAll: '§6คุณรีเซ็ต สกอร์บอร์ด ของผู้เล่นสำเร็จแล้ว {0}' hidden: '§6ป้ายบอกคะแนนของผู้เล่น {0} ถูกซ่อนไว้' shown: '§6ป้ายบอกคะแนนของผู้เล่น {0} มีการแสดง.' + own: + hidden: '§6ป้ายของคุณถูกซ่อนอยู่ในขณะนี้' + shown: '§6ป้ายของคุณจะปรากฏขึ้นอีกครั้ง' help: header: '§6§lBeautyQuests — Help' create: '§6/{0} create: §eสร้างภารกิจ' @@ -127,12 +155,14 @@ msg: remove: '§6/{0} remove : §eลบเควสด้วย id ที่ระบุหรือคลิกที่ NPC เมื่อไม่ได้กำหนดไว้' finishAll: '§6/{0} finishAll : §eจบภารกิจทั้งหมดของผู้เล่น' setStage: '§6/{0} setStage [new branch] [new stage]: §eข้ามขั้นตอน stage/start the branch/set a stage สำหรับ branch.' + startDialog: '§6/{0} startDialog : §eเริ่มกล่องโต้ตอบที่รอดำเนินการสำหรับด่าน NPC หรือกล่องโต้ตอบเริ่มต้นสำหรับภารกิจ' resetPlayer: '§6/{0} resetPlayer : §eลบข้อมูลทั้งหมดเกี่ยวกับผู้เล่น' resetPlayerQuest: '§6/{0} resetPlayerQuest [id]: §eลบข้อมูลของภารกิจให้ผู้เล่น' seePlayer: '§6/{0} seePlayer : §eดูข้อมูลเกี่ยวกับผู้เล่น' reload: '§6/{0} reload: §eSบันทึกและโหลดการกำหนดค่าและไฟล์ทั้งหมดซ้ำ. (§cdeprecated§e)' start: '§6/{0} start [id]: §eบังคับให้เริ่มเควส' setItem: '§6/{0} setItem : §eบันทึก hologram ไอเท่ม' + setFirework: '§6/{0} setFirework: §eแก้ไขจุดสิ้นสุดของดอกไม้ไฟเริ่มต้น' adminMode: '§6/{0} adminMode: §eสลับโหมดผู้ดูแลระบบ (มีประโยชน์สำหรับการแสดงข้อความบันทึกเล็กน้อย)' version: '§6/{0} version: §eดูเวอร์ชันปลั๊กอินปัจจุบัน' save: '§6/{0} save: §eทำการบันทึกปลั๊กอินด้วยตนเอง' @@ -185,12 +215,6 @@ msg: dialog: syntax: '§cCorrect syntax: {0}{1} ' syntaxRemove: '§cCorrect sytax: remove ' - player: เพิ่ม §aMessage "{0}" สำหรับผู้เล่นแล้ว. - npc: เพิ่ม §aMessage "{0}" สำหรับ NPC แล้ว - noSender: '§aMessage "{0}" ถูกเพิ่มโดยไม่มีผู้ส่ง' - messageRemoved: '§aMessage "{0}" ถูกลบออก' - edited: ข้อความ "{0}" ได้ถูกแก้ไขแล้ว - soundAdded: เพิ่ม§aSound "{0}" สำหรับ message "{1}" cleared: '§a {0} ข้อความที่ถูกลบ' help: header: '§6§lBeautyQuests — Dialog editor help' @@ -216,8 +240,6 @@ msg: epicBossDoesntExist: '§c Epic Boss ตัวนี้ไม่มีอยู่จริง' textList: syntax: '§cCorrect syntax: ' - added: เพิ่ม §aText "{0}" แล้ว - removed: '§aText "{0}" ถูกลบออก' help: header: '§6§lBeautyQuests — List editor help' add: '§6add : §eเพิ่มข้อความ' @@ -225,12 +247,10 @@ msg: list: '§6list: §eดูข้อความที่เพิ่มทั้งหมด' close: '§6close: §eตรวจสอบข้อความที่เพิ่มเข้ามา' noSuchElement: '§ไม่มี element ดังกล่าว element ที่อนุญาต ได้แก่ §e {0}' - comparisonType: '§aตัวเลือก การเปรียบเทียบ type ที่คุณต้องการ among {0}. การเปรียบเทียบ ค่าเริ่มต้น คือ: §e§lมากกว่า หรือ เท่ากับ§r§a. Type §onull§r§a เพื่อใช้.' scoreboardObjectiveNotFound: '§cไม่ทราบวัตถุประสงค์ของ สกอร์บอร์ด' pool: hologramText: 'เขียนข้อความโฮโลแกรมที่กำหนดเองสำหรับพูลนี้ (หรือ "null" ถ้าคุณต้องการใช้ค่าเริ่มต้น)' maxQuests: 'เขียนจำนวนสูงสุดของ ภารกิจ ที่สามารถเรียกใช้งานได้จาก pool นี้' - time: 'เขียนจำนวนวันก่อนที่ผู้เล่นจะทำภารกิจใหม่ได้' writeCommandDelay: '§aเขียนการหน่วงเวลาคำสั่งที่ต้องการ ใน ticks.' advancement: finished: เสร็จแล้ว @@ -429,7 +449,6 @@ inv: notStarted: ยังไม่เริ่มเควส finished: ภารกิจเสร็จสิ้นแล้ว inProgress: ภารกิจกำลังดำเนินการ - loreCancel: '§c§oคลิกเพื่อยกเลิกภารกิจ' loreStart: คลิกเพื่อเริ่มภารกิจ loreStartUnavailable: notc§oคุณมีคุณสมบัติไม่ตรงตามข้อกำหนดในการเริ่มภารกิจ canRedo: '§3§คุณสามารถเริ่มภารกิจนี้ใหม่ได้!' @@ -527,7 +546,6 @@ scoreboard: craft: '§eสร้าง §6{0}' bucket: '§eเติม §6{0}' location: '§eไปที่ §6{0}§e, §6{1}§e, §6{2}§e ใน §6{3}' - playTime: '§eเล่น §6{0} game ticks' breed: ผสมพันธ์ §6{0} tame: ทำให้เชื่อง §6{0} indication: diff --git a/core/src/main/resources/locales/vi_VN.yml b/core/src/main/resources/locales/vi_VN.yml index f3cf31b4..f88b6ac6 100644 --- a/core/src/main/resources/locales/vi_VN.yml +++ b/core/src/main/resources/locales/vi_VN.yml @@ -27,6 +27,7 @@ msg: questItem: drop: '§cBạn không thể làm rớt vật phẩm nhiệm vụ!' craft: '§cBạn không thể dùng vật phẩm nhiệm vụ để chế tạo!' + eat: '§cBạn không thể ăn vật phẩm nhiệm vụ!' stageMobs: noMobs: '§cGiai đoạn này không cần giết quái nào.' listMobs: '§aBạn phải giết {0}.' @@ -34,7 +35,7 @@ msg: writeRegionName: '§aViết tên của khu vực cần thiết cho giai đoạn tiếp theo:' writeXPGain: '§aViết số điểm kinh nghiệm mà người chơi sẽ nhận được: (Giá trị cuối cùng: {0})' writeMobAmount: '§aViết số lượng mob cần tiêu diệt:' - writeMobName: '§aViết tên của mobs mà bạn muốn giết' + writeMobName: '§aViết tên của mobs phải giết' writeChatMessage: '§aViết tin nhắn bắt buộc: (Thêm "{SLASH}" ngay từ đầu nếu bạn muốn một lệnh.)' writeMessage: '§aViết tin nhắn sẽ gửi đến người chơi' writeStartMessage: '§aViết thông báo sẽ gửi khi bắt đầu nhiệm vụ, "null" nếu bạn muốn là 1 thông báo bình thường hoặc "none" nếu bạn không muốn' @@ -50,7 +51,7 @@ msg: writeQuestTimer: '§aViết thời gian cần thiết (tính bằng phút) trước khi bạn có thể khởi động lại nhiệm vụ: (Viết "null" nếu bạn muốn bộ đếm thời gian mặc định.) ' writeConfirmMessage: '§aViết thông báo xác nhận được hiển thị khi một người chơi để bắt đầu nhiệm vụ: (Viết "null" nếu bạn muốn thông báo mặc định.)' writeQuestDescription: '§aViết mô tả về nhiệm vụ, hiển thị trong gui nhận nhiệm vụ.' - writeQuestMaterial: '§aViết tài liệu của vật phẩm nhiệm vụ.' + writeQuestMaterial: '§aViết vật liệu của item nhiệm vụ.' requirements: quest: '§cBạn chưa hoàn thành nhiệm vụ §e{0}§c!' level: '§cCấp độ bạn phải ở {0}!' @@ -267,6 +268,7 @@ inv: editMobsKill: '§eChỉnh sửa mobs bị tiêu diệt' selectItems: '§eChỉnh sửa yêu cầu vật phẩm' selectRegion: '§7Chọn khu vực region' + toggleRegionExit: Khi kết thúc nhiệm vụ stageStartMsg: '§eChỉnh sửa thông báo khi bắt đầu' editLocation: '§eChỉnh sửa địa điểm' changeEntityType: '§eThay đổi loại quái' diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index 2c4a32dc..afb3640c 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -7,6 +7,9 @@ website: https://www.spigotmc.org/resources/beautyquests.39255/ api-version: 1.13 main: fr.skytasul.quests.BeautyQuests +libraries: +- org.mariadb.jdbc:mariadb-java-client:2.7.5 + softdepend: - WorldGuard - MythicMobs diff --git a/dist/pom.xml b/dist/pom.xml index 8beff75b..bc47f590 100644 --- a/dist/pom.xml +++ b/dist/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 diff --git a/pom.xml b/pom.xml index 50b08c79..65bbe030 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 pom beautyquests @@ -16,7 +16,7 @@ 1.8 1.8 unknown - 0.19.1 + 0.19.2 diff --git a/v1_10_R1/pom.xml b/v1_10_R1/pom.xml index 54bd0946..bbcf5bcf 100644 --- a/v1_10_R1/pom.xml +++ b/v1_10_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_11_R1/pom.xml b/v1_11_R1/pom.xml index 8245fca4..173f1532 100644 --- a/v1_11_R1/pom.xml +++ b/v1_11_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_12_R1/pom.xml b/v1_12_R1/pom.xml index b16e0309..006d4c54 100644 --- a/v1_12_R1/pom.xml +++ b/v1_12_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_13_R2/pom.xml b/v1_13_R2/pom.xml index 383acd4d..50aa4a35 100644 --- a/v1_13_R2/pom.xml +++ b/v1_13_R2/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_14_R1/pom.xml b/v1_14_R1/pom.xml index 6fb1f581..53584b89 100644 --- a/v1_14_R1/pom.xml +++ b/v1_14_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_15_R1/pom.xml b/v1_15_R1/pom.xml index 89f8c78f..75e32bb1 100644 --- a/v1_15_R1/pom.xml +++ b/v1_15_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_16_R1/pom.xml b/v1_16_R1/pom.xml index a1ca6c89..eb55a56d 100644 --- a/v1_16_R1/pom.xml +++ b/v1_16_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_16_R2/pom.xml b/v1_16_R2/pom.xml index b4b501ef..8d7cbab8 100644 --- a/v1_16_R2/pom.xml +++ b/v1_16_R2/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_16_R3/pom.xml b/v1_16_R3/pom.xml index 152c1c44..bf4b702f 100644 --- a/v1_16_R3/pom.xml +++ b/v1_16_R3/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_17_R1/pom.xml b/v1_17_R1/pom.xml index 5aa10e97..0fa64bbf 100644 --- a/v1_17_R1/pom.xml +++ b/v1_17_R1/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 diff --git a/v1_18_R1/pom.xml b/v1_18_R1/pom.xml index d78de7f9..1107f649 100644 --- a/v1_18_R1/pom.xml +++ b/v1_18_R1/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 diff --git a/v1_18_R2/pom.xml b/v1_18_R2/pom.xml index 43feb0b8..01eb9b2d 100644 --- a/v1_18_R2/pom.xml +++ b/v1_18_R2/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 diff --git a/v1_9_R1/pom.xml b/v1_9_R1/pom.xml index 56d27497..7108afc0 100644 --- a/v1_9_R1/pom.xml +++ b/v1_9_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true diff --git a/v1_9_R2/pom.xml b/v1_9_R2/pom.xml index af76384b..a57f75c5 100644 --- a/v1_9_R2/pom.xml +++ b/v1_9_R2/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.1 + 0.19.2 true