commit a60609bb3b4a6758664d7694438f5ef9e05b44c1
Author: yhy <1763917516@qq.com>
Date: Thu Jun 4 06:58:32 2026 +0800
初始化项目
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6d0414d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+target/
+out/
+lib/
+libs/
+.vscode/
+.codex/
+.idea/
+*.iml
+*.class
+*.log
+logs/
+*.db
+*.sqlite
+*.sqlite3
+.DS_Store
+Thumbs.db
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..dfa04c0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,130 @@
+
+
+ 4.0.0
+
+ org.example
+ AuQuestEngine
+ 1.0-SNAPSHOT
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+
+ papermc-repo
+ https://repo.papermc.io/repository/maven-public/
+
+
+ public-survive
+ https://repo.aurora-pixels.com/repository/public-survive/
+
+
+ momi-releases
+ https://repo.momirealms.net/releases/
+
+
+
+
+
+ com.zaxxer
+ HikariCP
+ 5.1.0
+ compile
+
+
+ com.mysql
+ mysql-connector-j
+ 8.4.0
+ compile
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.46.1.0
+ compile
+
+
+ io.papermc.paper
+ paper-api
+ 1.21.11-R0.1-SNAPSHOT
+ provided
+
+
+ me.clip.placeholderapi
+ PlaceholderAPI
+ 2.12.2
+ provided
+
+
+ net.citizensnpcs
+ Citizens
+ 2.0.41
+ provided
+
+
+ io.lumine.mythic.bukkit
+ MythicMobs
+ 5.12.0-SNAPSHOT-548b7d33
+ provided
+
+
+ net.momirealms
+ custom-fishing
+ 2.3.23.1
+ provided
+
+
+ net.momirealms
+ custom-crops
+ 3.6.52
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+
+
+
+
+
+ com.zaxxer.hikari
+ com.io.yaohun.questengine.libs.hikari
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/io/yaohun/questengine/QEMain.java b/src/main/java/com/io/yaohun/questengine/QEMain.java
new file mode 100644
index 0000000..d9499f0
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/QEMain.java
@@ -0,0 +1,101 @@
+package com.io.yaohun.questengine;
+
+import com.io.yaohun.questengine.command.QuestAdminCommand;
+import com.io.yaohun.questengine.command.QuestCommand;
+import com.io.yaohun.questengine.config.Config;
+import com.io.yaohun.questengine.hook.QuestPlaceholderExpansion;
+import com.io.yaohun.questengine.listener.PlayerQuestListener;
+import com.io.yaohun.questengine.listener.task.*;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.storage.PlayerQuestSaveQueue;
+import com.io.yaohun.questengine.player.storage.PlayerQuestStorageManager;
+import com.io.yaohun.questengine.quest.QuestManager;
+import com.io.yaohun.questengine.quest.QuestResetManager;
+import com.io.yaohun.questengine.util.MessageUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class QEMain extends JavaPlugin {
+
+ private static QEMain instance;
+ public static boolean DEBUG = false;
+
+ @Override
+ public void onEnable() {
+ instance = this;
+ saveDefaultConfig();
+ Config.reloadConfig(this,false);
+ MessageUtil.init(this);
+ PlayerQuestStorageManager.init(this);
+
+ QuestManager.reloadQuestManager(this);
+ Bukkit.getPluginManager().registerEvents(new PlayerQuestListener(), this);
+ registerQuestListener();
+ getCommand("aquest").setExecutor(new QuestCommand());
+ getCommand("aquestadmin").setExecutor(new QuestAdminCommand());
+
+ if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
+ new QuestPlaceholderExpansion().register();
+ getLogger().info("已接入 PlaceholderAPI");
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] 已接入前置 §ePlaceholderAPI");
+ }
+
+ PlayerQuestSaveQueue.start();
+ QuestResetManager.start();
+ }
+
+ private void registerQuestListener() {
+ getServer().getPluginManager().registerEvents(new QuestBlockBreakListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestBlockPlaceListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestBreedListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestCraftItemListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestExpGainListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestFeedAnimalListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestFeedPlayerListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestInteractEntityListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestKillTypeListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestSmeltFoodListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestSmeltOreListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestCommandListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestOpenGuiListener(), this);
+ // 2026-6-4 新增任务前置
+ getServer().getPluginManager().registerEvents(new QuestPickupItemListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestBucketListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestCollectListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestTameEntityListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestVillagerListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestBrewPotionListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestUseAnvilListener(), this);
+ getServer().getPluginManager().registerEvents(new QuestInteractItemListener(), this);
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] 正在接入任务前置: ");
+ if (Bukkit.getPluginManager().getPlugin("Citizens") != null) {
+ getServer().getPluginManager().registerEvents(new QuestInteractNPCListener(), this);
+ Bukkit.getConsoleSender().sendMessage(" - §eCitizens");
+ }
+ if (Bukkit.getPluginManager().getPlugin("MythicMobs") != null) {
+ getServer().getPluginManager().registerEvents(new QuestKillMythicListener(), this);
+ Bukkit.getConsoleSender().sendMessage(" - §eMythicMobs");
+ }
+ if (Bukkit.getPluginManager().getPlugin("CustomFishing") != null) {
+ getServer().getPluginManager().registerEvents(new QuestFishItemListener(), this);
+ Bukkit.getConsoleSender().sendMessage(" - §eCustomFishing");
+ }
+ if (Bukkit.getPluginManager().getPlugin("CustomCrops") != null) {
+ getServer().getPluginManager().registerEvents(new QuestHarvestCropListener(), this);
+ Bukkit.getConsoleSender().sendMessage(" - §eCustomCrops");
+ }
+ }
+
+
+ @Override
+ public void onDisable() {
+ QuestResetManager.stop();
+ PlayerQuestSaveQueue.flushSync();
+ PlayerQuestManager.saveAll();
+ PlayerQuestStorageManager.close();
+ }
+
+ public static QEMain inst() {
+ return instance;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/api/AuQuestAPI.java b/src/main/java/com/io/yaohun/questengine/api/AuQuestAPI.java
new file mode 100644
index 0000000..2e07bca
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/api/AuQuestAPI.java
@@ -0,0 +1,137 @@
+package com.io.yaohun.questengine.api;
+
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.player.storage.PlayerQuestSaveQueue;
+import com.io.yaohun.questengine.quest.Quest;
+import com.io.yaohun.questengine.quest.QuestManager;
+import com.io.yaohun.questengine.quest.QuestTask;
+import com.io.yaohun.questengine.quest.QuestType;
+import com.io.yaohun.questengine.util.ColorUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+public class AuQuestAPI {
+
+ public static String getQuestDisplayName(String questId) {
+ Quest quest = QuestManager.getQuest(questId);
+ return quest == null ? null : quest.getDisplayName();
+ }
+
+ public static List getQuestDescription(String questId) {
+ Quest quest = QuestManager.getQuest(questId);
+
+ if (quest == null) {
+ return Collections.emptyList();
+ }
+
+ return new ArrayList<>(quest.getDescription());
+ }
+
+ public static QuestType getQuestType(String questId) {
+ Quest quest = QuestManager.getQuest(questId);
+ return quest == null ? null : quest.getType();
+ }
+
+ public static boolean hasCompletedQuest(UUID uuid, String questId) {
+ PlayerQuestProgress progress = PlayerQuestManager.getProgress(uuid, questId);
+ return progress != null && progress.isCompleted();
+ }
+
+ public static boolean hasActiveQuest(UUID uuid, String questId) {
+ PlayerQuestProgress progress = PlayerQuestManager.getProgress(uuid, questId);
+ return progress != null && !progress.isCompleted();
+ }
+
+ public static List getActiveQuests(UUID uuid) {
+ PlayerQuestData data = PlayerQuestManager.getPlayerData(uuid);
+
+ List list = new ArrayList<>();
+
+ for (PlayerQuestProgress progress : data.getQuestProgressMap().values()) {
+ if (!progress.isCompleted()) {
+ list.add(progress.getQuestId());
+ }
+ }
+
+ return list;
+ }
+
+ public static List getCompletedQuests(UUID uuid){
+ PlayerQuestData data = PlayerQuestManager.getPlayerData(uuid);
+ List list = new ArrayList<>();
+ for(PlayerQuestProgress progress : data.getQuestProgressMap().values()){
+ if(progress.isCompleted()){
+ list.add(progress.getQuestId());
+ }
+ }
+ return list;
+ }
+
+ public static boolean abandonQuest(UUID uuid, String questId) {
+ PlayerQuestData data = PlayerQuestManager.getPlayerData(uuid);
+
+ if (!data.hasQuest(questId)) {
+ return false;
+ }
+
+ data.removeQuest(questId);
+ PlayerQuestSaveQueue.markDirty(uuid);
+
+ return true;
+ }
+
+ public static boolean startQuest(UUID uuid, String questId) {
+ if (!QuestManager.hasQuest(questId)) {
+ return false;
+ }
+
+ return PlayerQuestManager.acceptQuest(uuid, questId);
+ }
+
+ public static boolean directCompleteQuest(UUID uuid, String questId){
+ Quest quest = QuestManager.getQuest(questId);
+ if(quest == null){
+ return false;
+ }
+ PlayerQuestData data = PlayerQuestManager.getPlayerData(uuid);
+
+ PlayerQuestProgress progress = data.getQuestProgress(questId);
+
+ if(progress == null){
+ progress = new PlayerQuestProgress(questId);
+ data.addQuest(progress);
+ }
+ if(progress.isCompleted()){
+ return false;
+ }
+ for (QuestTask task : quest.getTasks().values()){
+ progress.setProgress(task.getId(), task.getAmount());
+ }
+
+ progress.setCompleted(true);
+ PlayerQuestSaveQueue.markDirty(uuid);
+ Player player = Bukkit.getPlayer(uuid);
+ if(player != null) {
+ // 完成消息
+ for (String msg : quest.getCompleteMessages()) {
+ player.sendMessage(ColorUtil.color(msg));
+ }
+ // reward message
+ for (String msg : quest.getReward().getMessages()) {
+ player.sendMessage(ColorUtil.color(msg.replace("{name}", quest.getDisplayName())));
+ }
+ // reward command
+ for (String cmd : quest.getReward().getCommands()) {
+ Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", player.getName()));
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/command/QuestAdminCommand.java b/src/main/java/com/io/yaohun/questengine/command/QuestAdminCommand.java
new file mode 100644
index 0000000..849ce41
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/command/QuestAdminCommand.java
@@ -0,0 +1,76 @@
+package com.io.yaohun.questengine.command;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.config.Config;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.storage.PlayerQuestSaveQueue;
+import com.io.yaohun.questengine.player.storage.PlayerQuestStorage;
+import com.io.yaohun.questengine.player.storage.PlayerQuestStorageManager;
+import com.io.yaohun.questengine.player.storage.SQLitePlayerQuestStorage;
+import com.io.yaohun.questengine.quest.Quest;
+import com.io.yaohun.questengine.quest.QuestManager;
+import com.io.yaohun.questengine.util.ColorUtil;
+import com.io.yaohun.questengine.util.MessageUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.io.File;
+
+public class QuestAdminCommand implements CommandExecutor {
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (!sender.hasPermission("admin.use")) {
+ return true;
+ }
+ String prefix = "§f[§cAuQuest§f] §a";
+ if(args.length >= 1 && "debug".equalsIgnoreCase(args[0])){
+ QEMain.DEBUG = !QEMain.DEBUG;
+ sender.sendMessage(prefix+"已切换调试: §e"+QEMain.DEBUG);
+ return true;
+ }
+ if(args.length >= 1 && "reload".equalsIgnoreCase(args[0])){
+ // 先强制保存脏数据
+ PlayerQuestSaveQueue.flushSync();
+ // 再保存全部缓存
+ PlayerQuestManager.saveAll();
+ QEMain plugin = QEMain.inst();
+ plugin.reloadConfig();
+ Config.reloadConfig(plugin, true);
+ MessageUtil.init(plugin);
+ QuestManager.reloadQuestManager(plugin);
+ sender.sendMessage(prefix+"配置文件已重载,当前任务数量: §e"+QuestManager.getAllQuests().size()+"个");
+ return true;
+ }
+ if(args.length >= 2 && "clear".equalsIgnoreCase(args[0])){
+ OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
+ if(target.getUniqueId() == null){
+ sender.sendMessage(prefix+"玩家 "+target.getName()+" 不存在");
+ return true;
+ }
+ PlayerQuestManager.deletePlayerData(target.getUniqueId());
+ sender.sendMessage(prefix+"已清空玩家任务数据: &e" + target.getName());
+ return true;
+ }
+ if (args.length >= 1 && "outsqlite".equalsIgnoreCase(args[0])) {
+ PlayerQuestStorage storage = PlayerQuestStorageManager.getStorage();
+ if (!(storage instanceof SQLitePlayerQuestStorage)) {
+ sender.sendMessage(ColorUtil.color("&7[任务系统] &c当前存储模式不是 SQLite,无法导出。"));
+ return true;
+ }
+ SQLitePlayerQuestStorage sqliteStorage = (SQLitePlayerQuestStorage) storage;
+ File file = sqliteStorage.exportToYaml();
+ if (file == null) {
+ sender.sendMessage(ColorUtil.color("&7[任务系统] &cSQLite 数据导出失败,请查看后台报错。"));
+ return true;
+ }
+ sender.sendMessage(ColorUtil.color("&7[任务系统] &aSQLite 数据已导出: &f" + file.getName()));
+ return true;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/command/QuestCommand.java b/src/main/java/com/io/yaohun/questengine/command/QuestCommand.java
new file mode 100644
index 0000000..45c7a9c
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/command/QuestCommand.java
@@ -0,0 +1,135 @@
+package com.io.yaohun.questengine.command;
+
+import com.io.yaohun.questengine.api.AuQuestAPI;
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.quest.Quest;
+import com.io.yaohun.questengine.quest.QuestConditionChecker;
+import com.io.yaohun.questengine.quest.QuestManager;
+import com.io.yaohun.questengine.quest.QuestTask;
+import com.io.yaohun.questengine.util.ColorUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.UUID;
+
+public class QuestCommand implements CommandExecutor {
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ String prefix = "§f[§cAuQuest§f] §a";
+ if(args.length >= 1 && "direc".equalsIgnoreCase(args[0]) && sender.isOp()){
+ if(args.length < 2){
+ sender.sendMessage(prefix+"用法: /aquest direc <任务ID>");
+ return true;
+ }
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage(prefix+"该命令只能由玩家执行");
+ return true;
+ }
+ String questId = args[1];
+ UUID playerUuid = player.getUniqueId();
+ AuQuestAPI.directCompleteQuest(playerUuid, questId);
+ sender.sendMessage(prefix+"已直接完成任务: §e"+questId);
+ return true;
+ }
+ if(args.length >= 1 && "info".equalsIgnoreCase(args[0])){
+ String playerName = sender.getName();
+ if(sender.hasPermission("admin.use") && args.length >= 2){
+ playerName = args[1];
+ }
+ Player player = Bukkit.getPlayer(playerName);
+ if(player == null){
+ sender.sendMessage(prefix+"玩家 "+playerName+" 不在线");
+ return true;
+ }
+ PlayerQuestData questData = PlayerQuestManager.getPlayerData(player.getUniqueId());
+ sender.sendMessage("§8§m————————————————————");
+ sender.sendMessage(prefix+"玩家: §e"+playerName);
+ sender.sendMessage(prefix+"任务数量: §e"+questData.getQuestProgressMap().size());
+ int completedCount = 0;
+ int activeCount = 0;
+ for(PlayerQuestProgress progress : questData.getQuestProgressMap().values()){
+ Quest quest = QuestManager.getQuest(progress.getQuestId());
+ String questName = quest == null ? progress.getQuestId() : quest.getDisplayName();
+ if(progress.isCompleted()) {
+ completedCount++;
+ sender.sendMessage(ColorUtil.color("&a[已完成] &f" + questName + " &7(" + progress.getQuestId() + ")"));
+ continue;
+ }
+ activeCount++;
+ sender.sendMessage(ColorUtil.color("&e[进行中] &f" + questName + " &7(" + progress.getQuestId() + ")"));
+ if(quest != null){
+ for(QuestTask task : quest.getTasks().values()){
+ int current = progress.getProgress(task.getId());
+ int max = task.getAmount();
+ if(current > max){
+ current = max;
+ }
+ sender.sendMessage(ColorUtil.color(
+ " &8- &7" + task.getDisplayName()
+ + " &f" + current + "&7/&f" + max
+ ));
+ }
+ }
+ }
+ sender.sendMessage(ColorUtil.color("&7进行中: &e" + activeCount + " &7已完成: &a" + completedCount));
+ sender.sendMessage("§8§m————————————————————");
+ return true;
+ }
+ if (args.length < 3) {
+ sender.sendMessage("§c用法: /aquest js <任务ID> <玩家名>");
+ return true;
+ }
+ String sub = args[0];
+ // /aquest js quest_1 yaohun
+ if (sub.equalsIgnoreCase("js")) {
+
+ String questId = args[1];
+
+ Player target = Bukkit.getPlayer(args[2]);
+
+ if (target == null) {
+ sender.sendMessage("§c玩家不存在");
+ return true;
+ }
+
+ Quest quest = QuestManager.getQuest(questId);
+
+ if (quest == null) {
+ sender.sendMessage("§c任务不存在");
+ return true;
+ }
+
+ if (!QuestConditionChecker.canAccept(target, quest, true)) {
+ sender.sendMessage(ColorUtil.color("&7[任务系统] &c玩家不满足任务接受条件。"));
+ return true;
+ }
+
+ boolean success = PlayerQuestManager.acceptQuest(
+ target.getUniqueId(),
+ questId
+ );
+
+ if (!success) {
+ sender.sendMessage("§c该玩家已经接受过这个任务");
+ return true;
+ }
+
+ // 接受消息
+ for (String msg : quest.getReceiveMessages()) {
+ target.sendMessage(ColorUtil.color(msg));
+ }
+
+ sender.sendMessage("§a任务发放成功");
+
+ return true;
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/config/Config.java b/src/main/java/com/io/yaohun/questengine/config/Config.java
new file mode 100644
index 0000000..8985c2b
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/config/Config.java
@@ -0,0 +1,36 @@
+package com.io.yaohun.questengine.config;
+
+import com.io.yaohun.questengine.QEMain;
+import org.bukkit.configuration.file.FileConfiguration;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Config {
+
+ private static List foodTypeList = new ArrayList<>();
+ private static List oreTypeList = new ArrayList<>();
+
+
+ public static void reloadConfig(QEMain plugin, boolean reload){
+ if(reload){
+ reload(plugin);
+ }
+ FileConfiguration config = plugin.getConfig();
+ foodTypeList = config.getStringList("FoodType");
+ oreTypeList = config.getStringList("OreType");
+ }
+
+ private static void reload(QEMain plugin){
+ plugin.reloadConfig();
+ plugin.saveConfig();
+ }
+
+ public static List getFoodTypeList() {
+ return foodTypeList;
+ }
+
+ public static List getOreTypeList() {
+ return oreTypeList;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/hook/QuestPlaceholderExpansion.java b/src/main/java/com/io/yaohun/questengine/hook/QuestPlaceholderExpansion.java
new file mode 100644
index 0000000..db9d8c4
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/hook/QuestPlaceholderExpansion.java
@@ -0,0 +1,126 @@
+package com.io.yaohun.questengine.hook;
+
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.quest.Quest;
+import com.io.yaohun.questengine.quest.QuestManager;
+import com.io.yaohun.questengine.quest.QuestTask;
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class QuestPlaceholderExpansion extends PlaceholderExpansion {
+
+ @Override
+ public @NotNull String getIdentifier() {
+ return "aquest";
+ }
+
+ @Override
+ public @NotNull String getAuthor() {
+ return "yaohun";
+ }
+
+ @Override
+ public @NotNull String getVersion() {
+ return "1.0.0";
+ }
+
+ @Override
+ public boolean persist() {
+ return true;
+ }
+
+ @Override
+ public String onPlaceholderRequest(Player player, String params) {
+
+ if (player == null) {
+ return "";
+ }
+ // %aqe_quest_quest_1_name%
+ if (params.startsWith("quest_") && params.endsWith("_name")) {
+ String questId = params.substring("quest_".length(), params.length() - "_name".length());
+ Quest quest = QuestManager.getQuest(questId);
+ return quest == null ? "" : quest.getDisplayName();
+ }
+
+ // %aqe_quest_quest_1_completed%
+ if (params.startsWith("quest_") && params.endsWith("_completed")) {
+ String questId = params.substring("quest_".length(), params.length() - "_completed".length());
+ PlayerQuestProgress progress = PlayerQuestManager.getProgress(player.getUniqueId(), questId);
+ return progress != null && progress.isCompleted() ? "已完成" : "未完成";
+ }
+
+ // %aqe_quest_quest_1_progress%
+ if (params.startsWith("quest_") && params.endsWith("_progress")) {
+ String questId = params.substring("quest_".length(), params.length() - "_progress".length());
+ return getQuestProgressText(player, questId);
+ }
+
+ // %aqe_task_quest_1_id_1%
+ if (params.startsWith("task_")) {
+ String raw = params.substring("task_".length());
+
+ int lastIndex = raw.lastIndexOf("_id_");
+
+ if (lastIndex == -1) {
+ return "";
+ }
+
+ String questId = raw.substring(0, lastIndex);
+ String taskId = "id_" + raw.substring(lastIndex + "_id_".length());
+
+ return getTaskProgressText(player, questId, taskId);
+ }
+
+ return "";
+ }
+
+ private String getQuestProgressText(Player player, String questId) {
+ Quest quest = QuestManager.getQuest(questId);
+
+ if (quest == null) {
+ return "";
+ }
+
+ PlayerQuestProgress progress = PlayerQuestManager.getProgress(player.getUniqueId(), questId);
+
+ if (progress == null) {
+ return "未接受";
+ }
+
+ int current = 0;
+ int max = 0;
+
+ for (QuestTask task : quest.getTasks().values()) {
+ current += Math.min(progress.getProgress(task.getId()), task.getAmount());
+ max += task.getAmount();
+ }
+
+ return current + "/" + max;
+ }
+
+ private String getTaskProgressText(Player player, String questId, String taskId) {
+ Quest quest = QuestManager.getQuest(questId);
+
+ if (quest == null) {
+ return "";
+ }
+
+ QuestTask task = quest.getTasks().get(taskId);
+
+ if (task == null) {
+ return "";
+ }
+
+ PlayerQuestProgress progress = PlayerQuestManager.getProgress(player.getUniqueId(), questId);
+
+ if (progress == null) {
+ return "0/" + task.getAmount();
+ }
+
+ int current = Math.min(progress.getProgress(taskId), task.getAmount());
+
+ return current + "/" + task.getAmount();
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/PlayerQuestListener.java b/src/main/java/com/io/yaohun/questengine/listener/PlayerQuestListener.java
new file mode 100644
index 0000000..62cafb3
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/PlayerQuestListener.java
@@ -0,0 +1,23 @@
+package com.io.yaohun.questengine.listener;
+
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+public class PlayerQuestListener implements Listener {
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onJoin(PlayerJoinEvent e){
+ PlayerQuestManager.loadPlayer(e.getPlayer().getUniqueId());
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onQuit(PlayerQuitEvent event) {
+ PlayerQuestManager.savePlayerNow(event.getPlayer().getUniqueId());
+ PlayerQuestManager.unloadPlayer(event.getPlayer().getUniqueId());
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestBlockBreakListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBlockBreakListener.java
new file mode 100644
index 0000000..b644c5a
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBlockBreakListener.java
@@ -0,0 +1,33 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+
+public class QuestBlockBreakListener implements Listener {
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockBreak(BlockBreakEvent event) {
+
+ Player player = event.getPlayer();
+
+ String target = event.getBlock().getType().name();
+
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 破坏了方块 "+target);
+ }
+
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.BREAK_BLOCK,
+ target,
+ 1
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestBlockPlaceListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBlockPlaceListener.java
new file mode 100644
index 0000000..45fd46f
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBlockPlaceListener.java
@@ -0,0 +1,29 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockPlaceEvent;
+
+public class QuestBlockPlaceListener implements Listener {
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onPlace(BlockPlaceEvent e){
+ Player player = e.getPlayer();
+ String target = e.getBlock().getType().name();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 放置了方块 "+target);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.PLACE_BLOCK,
+ target,
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestBreedListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBreedListener.java
new file mode 100644
index 0000000..fb3c463
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBreedListener.java
@@ -0,0 +1,31 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Animals;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityBreedEvent;
+
+public class QuestBreedListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onBreed(EntityBreedEvent e){
+ if(e.getBreeder() instanceof Player player){
+ if(e.getEntity() instanceof Animals animals){
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 人工繁殖了 "+animals.getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.BREED,
+ animals.getType().name(),
+ 1
+ );
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestBrewPotionListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBrewPotionListener.java
new file mode 100644
index 0000000..46fd1ba
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBrewPotionListener.java
@@ -0,0 +1,57 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.PortalType;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityPickupItemEvent;
+import org.bukkit.event.inventory.BrewEvent;
+import org.bukkit.inventory.BrewerInventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.PotionMeta;
+import org.bukkit.potion.PotionType;
+
+public class QuestBrewPotionListener implements Listener {
+ //BREW_POTION("酿造药水"),
+
+ @EventHandler(ignoreCancelled = true)
+ public void onBrew(BrewEvent e){
+ BrewerInventory inventory = e.getContents();
+ for (int slot = 0; slot < 3; slot++){
+ ItemStack item = inventory.getItem(slot);
+ if(item == null || item.getType().isAir()){
+ continue;
+ }
+ if (!(item.getItemMeta() instanceof PotionMeta potionMeta)) {
+ continue;
+ }
+ PotionType potionType = potionMeta.getBasePotionType();
+ for (HumanEntity viewer : e.getContents().getViewers()){
+ if (!(viewer instanceof Player player)) {
+ continue;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 酿造了 " + potionType.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.BREW_POTION,
+ potionType.name(),
+ 1
+ );
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.BREW_POTION_COUNT,
+ "ALL",
+ 1
+ );
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestBucketListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBucketListener.java
new file mode 100644
index 0000000..fc21c57
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestBucketListener.java
@@ -0,0 +1,47 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Item;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerBucketEmptyEvent;
+import org.bukkit.event.player.PlayerBucketFillEvent;
+import org.bukkit.event.player.PlayerFishEvent;
+
+public class QuestBucketListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onFillBucket(PlayerBucketFillEvent e){
+ Player player = e.getPlayer();
+ Material material = e.getBucket();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 装取了 "+material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.FILL_BUCKET,
+ material.name(),
+ 1
+ );
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onPlaceBucket(PlayerBucketEmptyEvent e){
+ Player player = e.getPlayer();
+ Material material = e.getBucket();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 倒出了 " + material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.PLACE_BUCKET,
+ material.name(),
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestCollectListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestCollectListener.java
new file mode 100644
index 0000000..c214c54
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestCollectListener.java
@@ -0,0 +1,108 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.block.data.type.Beehive;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.entity.Cow;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Sheep;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.event.player.PlayerShearEntityEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.UUID;
+
+public class QuestCollectListener implements Listener {
+ // COLLECT_ENTITY("剪羊毛,取蘑菇煲"),
+ // COLLECT_BLOCK("收集蜂蜜"),
+
+ @EventHandler(ignoreCancelled = true)
+ public void onShearSheep(PlayerShearEntityEvent e){
+ if(e.getEntity() instanceof Sheep sheep) {
+ if (sheep.isSheared()) {
+ return;
+ }
+ Player player = e.getPlayer();
+ ItemStack stack = e.getItem();
+ Material material = stack.getType();
+ if (QEMain.DEBUG) {
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 剪羊毛 "+material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.COLLECT_ENTITY,
+ "SHEEP",
+ 1
+ );
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onMilkCow(PlayerInteractEntityEvent e){
+ if(e.getRightClicked() instanceof Cow){
+ Player player = e.getPlayer();
+ ItemStack hand = player.getInventory().getItemInMainHand();
+ if(hand == null || hand.getType() != Material.BUCKET){
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 挤牛奶 Cow");
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.COLLECT_ENTITY,
+ "COW",
+ 1
+ );
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onBeehive(PlayerInteractEvent e){
+ if(e.getAction() == Action.RIGHT_CLICK_BLOCK){
+ Player player = e.getPlayer();
+ Block block = e.getClickedBlock();
+ if (block == null) {
+ return;
+ }
+ Material blockType = block.getType();
+ if(blockType != Material.BEEHIVE && blockType != Material.BEE_NEST){
+ return;
+ }
+ ItemStack hand = player.getInventory().getItemInMainHand();
+ if(hand == null || hand.getType().isAir()){
+ return;
+ }
+ Material handType = hand.getType();
+ if (handType != Material.GLASS_BOTTLE && handType != Material.SHEARS) {
+ return;
+ }
+ BlockData data = block.getBlockData();
+ if(data instanceof Beehive hive){
+ if(hive.getHoneyLevel() < hive.getMaximumHoneyLevel()){
+ return;
+ }
+ String target = handType == Material.GLASS_BOTTLE ? "HONEY_BOTTLE" : "HONEYCOMB";
+ if (QEMain.DEBUG) {
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 收集蜂箱 " + target);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.COLLECT_BLOCK,
+ target,
+ 1
+ );
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestCommandListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestCommandListener.java
new file mode 100644
index 0000000..c3e3259
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestCommandListener.java
@@ -0,0 +1,35 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryOpenEvent;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+import org.bukkit.inventory.Inventory;
+
+import java.util.UUID;
+
+public class QuestCommandListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onCommand(PlayerCommandPreprocessEvent e) {
+ Player player = e.getPlayer();
+ String command = e.getMessage();
+ if (command == null || command.isEmpty()) {
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 执行了命令 "+command);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.SEND_COMMAND,
+ command,
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestCraftItemListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestCraftItemListener.java
new file mode 100644
index 0000000..b246282
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestCraftItemListener.java
@@ -0,0 +1,36 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.CraftItemEvent;
+
+public class QuestCraftItemListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onCraft(CraftItemEvent event) {
+ if(event.getWhoClicked() instanceof Player player){
+ if (event.getRecipe() == null || event.getRecipe().getResult() == null) {
+ return;
+ }
+ Material material = event.getRecipe().getResult().getType();
+ if(material.isAir()){
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 合成了 "+material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.CRAFT_ITEM,
+ material.name(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestEnchanItemListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestEnchanItemListener.java
new file mode 100644
index 0000000..7cc1d26
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestEnchanItemListener.java
@@ -0,0 +1,37 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.enchantment.EnchantItemEvent;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.AnvilInventory;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestEnchanItemListener implements Listener {
+ //ENCHANT_ITEM("使用附魔台"),
+
+ @EventHandler(ignoreCancelled = true)
+ public void onEnchant(EnchantItemEvent e){
+ Player player = e.getEnchanter();
+ ItemStack item = e.getItem();
+ if(item == null || item.getType().isAir()){
+ return;
+ }
+ Material material = item.getType();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 附魔了 "+material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.ENCHANT_ITEM,
+ material.name(),
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestExpGainListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestExpGainListener.java
new file mode 100644
index 0000000..e50fe57
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestExpGainListener.java
@@ -0,0 +1,30 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerExpChangeEvent;
+
+public class QuestExpGainListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onExo(PlayerExpChangeEvent e){
+ if(e.getAmount() <= 0){
+ return;
+ }
+ Player player = e.getPlayer();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 获得经验 "+e.getAmount());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.EXP_GAIN,
+ null,
+ e.getAmount()
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestFeedAnimalListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestFeedAnimalListener.java
new file mode 100644
index 0000000..b83e5cf
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestFeedAnimalListener.java
@@ -0,0 +1,38 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Animals;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestFeedAnimalListener implements Listener {
+ @EventHandler(ignoreCancelled = true)
+ public void onFeed(PlayerInteractEntityEvent e) {
+ if(e.getRightClicked() instanceof Animals animals){
+ Player player = e.getPlayer();
+ ItemStack hand = player.getInventory().getItemInMainHand();
+ if (hand == null || hand.getType().isAir()) {
+ return;
+ }
+
+ if (!animals.isBreedItem(hand)) {
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 投喂了 "+animals.getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.FEED_ANIMAL,
+ animals.getType().name(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestFeedPlayerListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestFeedPlayerListener.java
new file mode 100644
index 0000000..d2c2c08
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestFeedPlayerListener.java
@@ -0,0 +1,33 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerItemConsumeEvent;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestFeedPlayerListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onFeed(PlayerItemConsumeEvent e){
+ Player player = e.getPlayer();
+ ItemStack item = e.getItem();
+ if(item == null || item.getType().isAir()){
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 吃掉了 "+item.getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.FEED_PLAYER,
+ item.getType().name(),
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestFishItemListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestFishItemListener.java
new file mode 100644
index 0000000..8b7cbce
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestFishItemListener.java
@@ -0,0 +1,68 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import net.momirealms.customfishing.api.event.FishingResultEvent;
+import net.momirealms.customfishing.api.mechanic.loot.Loot;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Item;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityPickupItemEvent;
+import org.bukkit.event.player.PlayerFishEvent;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestFishItemListener implements Listener {
+
+ // FISH_ITEM("钓鱼获得物品"),
+ // FISH_STAR("钓起鱼的星级"),
+ @EventHandler(ignoreCancelled = true)
+ public void onFishItem(FishingResultEvent e){
+ if(e.getResult() != FishingResultEvent.Result.SUCCESS){
+ return;
+ }
+ Player player = e.getPlayer();
+ Loot loot = e.getLoot();
+ if(loot == null){
+ return;
+ }
+ int amount = e.getAmount();
+ if(amount <= 0){
+ amount = 1;
+ }
+ String fishId = loot.id();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 钓起来了 "+fishId+"x"+amount);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.FISH_ITEM,
+ fishId,
+ amount
+ );
+ String[] startGroup = loot.lootGroup();
+ if(startGroup == null){
+ return;
+ }
+ for (String group : startGroup){
+ if(group == null){
+ continue;
+ }
+ if("no_star".equalsIgnoreCase(group)){
+ continue;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 钓起来了星级鱼 "+group);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.FISH_STAR,
+ group,
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestHarvestCropListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestHarvestCropListener.java
new file mode 100644
index 0000000..0afd1c1
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestHarvestCropListener.java
@@ -0,0 +1,65 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import net.momirealms.customcrops.api.event.CropBreakEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.Ageable;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+public class QuestHarvestCropListener implements Listener {
+
+ // HARVEST_CROP("收获农作物"), 原版
+ /*@EventHandler(ignoreCancelled = true)
+ public void onHarvestCrop(BlockBreakEvent event) {
+ Player player = event.getPlayer();
+ Block block = event.getBlock();
+ BlockData data = block.getBlockData();
+ if(data instanceof Ageable ageable) {
+ if(ageable.getAge() < ageable.getMaximumAir()){
+ return;
+ }
+ Material material = block.getType();
+
+ if (QEMain.DEBUG) {
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 收获了 " + material.name());
+ }
+
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.HARVEST_CROP,
+ material.name(),
+ 1
+ );
+ }
+ }*/
+ // HARVEST_CROP("收获农作物"), 星露谷
+ @EventHandler(ignoreCancelled = true)
+ public void onHarvestCrop(CropBreakEvent e){
+ if (!(e.entityBreaker() instanceof Player player)) {
+ return;
+ }
+ Block block = e.blockBreaker();
+ BlockData data = block.getBlockData();
+ if (data instanceof Ageable ageable) {
+ if (ageable.getAge() < ageable.getMaximumAge()) {
+ return;
+ }
+ }
+ String cropId = e.cropConfig().id();
+ if (QEMain.DEBUG) {
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 收获了 " + cropId);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.HARVEST_CROP,
+ cropId,
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractEntityListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractEntityListener.java
new file mode 100644
index 0000000..56cef90
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractEntityListener.java
@@ -0,0 +1,27 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+
+public class QuestInteractEntityListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onInteract(PlayerInteractEntityEvent e){
+ Player player = e.getPlayer();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 正在交互实体 "+e.getRightClicked().getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.INTERACT_ENTITY,
+ e.getRightClicked().getType().name(),
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractItemListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractItemListener.java
new file mode 100644
index 0000000..ba20e41
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractItemListener.java
@@ -0,0 +1,38 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestInteractItemListener implements Listener {
+
+ // INTERACT_ITEM("交互手持物品"),
+ @EventHandler(ignoreCancelled = true)
+ public void onInteract(PlayerInteractEvent e){
+ if(e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) {
+ ItemStack item = e.getItem();
+ if (item == null || item.getType().isAir()) {
+ return;
+ }
+ Material material = item.getType();
+ Player player = e.getPlayer();
+ if (QEMain.DEBUG) {
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 正在交互手持物品 " + material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.INTERACT_ITEM,
+ material.name(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractNPCListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractNPCListener.java
new file mode 100644
index 0000000..3ad5e56
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestInteractNPCListener.java
@@ -0,0 +1,33 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import net.citizensnpcs.api.event.NPCRightClickEvent;
+import net.citizensnpcs.api.npc.NPC;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+public class QuestInteractNPCListener implements Listener {
+
+ @EventHandler
+ public void onClickNpc(NPCRightClickEvent e){
+ Player player = e.getClicker();
+ NPC npc = e.getNPC();
+ if(npc == null){
+ return;
+ }
+ String target = String.valueOf(npc.getName());
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 正在交互NPC "+target);
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.INTERACT_NPC,
+ target,
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestKillMythicListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestKillMythicListener.java
new file mode 100644
index 0000000..946e94f
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestKillMythicListener.java
@@ -0,0 +1,31 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import io.lumine.mythic.api.mobs.MythicMob;
+import io.lumine.mythic.bukkit.events.MythicMobDeathEvent;
+import io.lumine.mythic.core.mobs.MobType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+public class QuestKillMythicListener implements Listener {
+
+ @EventHandler
+ public void onKill(MythicMobDeathEvent e){
+ if(e.getKiller() instanceof Player player){
+ MythicMob mobType = e.getMobType();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 击杀了 "+mobType.getInternalName());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.KILL_MYTHIC,
+ mobType.getInternalName(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestKillTypeListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestKillTypeListener.java
new file mode 100644
index 0000000..d8b0a38
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestKillTypeListener.java
@@ -0,0 +1,32 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import net.citizensnpcs.api.event.NPCLeftClickEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.NPC;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDeathEvent;
+
+public class QuestKillTypeListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onKill(EntityDeathEvent e){
+ Player killer = e.getEntity().getKiller();
+ if(killer == null){
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+killer.getName()+" 击杀了 "+e.getEntity().getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ killer.getUniqueId(),
+ QuestTaskType.KILL_TYPE,
+ e.getEntity().getType().name(),
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestOpenGuiListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestOpenGuiListener.java
new file mode 100644
index 0000000..4ab81ff
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestOpenGuiListener.java
@@ -0,0 +1,58 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryOpenEvent;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+import org.bukkit.inventory.Inventory;
+
+import java.util.UUID;
+
+public class QuestOpenGuiListener implements Listener {
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onOpenGui(InventoryOpenEvent e){
+ if(e.getPlayer() instanceof Player player){
+ String viewTitle = e.getView().getTitle();
+ if (viewTitle == null || viewTitle.isEmpty()) {
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 正在打开 "+viewTitle);
+ }
+ // 去色标题,方便配置不写颜色也能匹配
+ String strippedTitle = ChatColor.stripColor(viewTitle);
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.OPEN_GUI_TITLE,
+ strippedTitle,
+ 1
+ );
+ }
+ }
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onOpenType(InventoryOpenEvent e){
+ if(e.getPlayer() instanceof Player player){
+ Inventory inventory = e.getInventory();
+ if (inventory == null) {
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 正在打开 "+inventory.getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.OPEN_GUI_TYPE,
+ inventory.getType().name(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestPickupItemListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestPickupItemListener.java
new file mode 100644
index 0000000..20b1606
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestPickupItemListener.java
@@ -0,0 +1,32 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityPickupItemEvent;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestPickupItemListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onPickUp(EntityPickupItemEvent e){
+ if(e.getEntity() instanceof Player player){
+ ItemStack stack = e.getItem().getItemStack();
+ Material material = stack.getType();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 拾取了 "+material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.PICKUP_ITEM,
+ material.name(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestSmeltFoodListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestSmeltFoodListener.java
new file mode 100644
index 0000000..8d44498
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestSmeltFoodListener.java
@@ -0,0 +1,50 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.config.Config;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.FurnaceExtractEvent;
+
+import java.util.List;
+
+public class QuestSmeltFoodListener implements Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ public void onSmelt(FurnaceExtractEvent e){
+ Player player = e.getPlayer();
+
+ Material result = e.getItemType();
+ int amount = e.getItemAmount();
+ if (!isFood(result)) {
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 在熔炉取出了 "+result.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.SMELT_FOOD,
+ result.name(),
+ amount
+ );
+ }
+
+ private boolean isFood(Material material) {
+ List foodTypeList = Config.getFoodTypeList();
+ for (String food : foodTypeList) {
+ if (material.name().contains(food)) {
+ return true;
+ }
+ }
+ return switch (material) {
+ case COOKED_BEEF, COOKED_CHICKEN, COOKED_MUTTON, COOKED_RABBIT, BAKED_POTATO -> true;
+ default -> false;
+ };
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestSmeltOreListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestSmeltOreListener.java
new file mode 100644
index 0000000..0073b69
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestSmeltOreListener.java
@@ -0,0 +1,51 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.config.Config;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.FurnaceExtractEvent;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+
+import java.util.List;
+
+public class QuestSmeltOreListener implements Listener {
+ @EventHandler(ignoreCancelled = true)
+ public void onSmelt(FurnaceExtractEvent e){
+ Player player = e.getPlayer();
+ Material result = e.getItemType();
+ int amount = e.getItemAmount();
+ if(!isOre(result)){
+ return;
+ }
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 在熔炉取出了 "+result.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.SMELT_ORE,
+ result.name(),
+ amount
+ );
+ }
+
+ private boolean isOre(Material material){
+ List oreList = Config.getOreTypeList();
+ for (String ore : oreList) {
+ if(material.name().contains(ore)){
+ return true;
+ }
+ }
+ return switch (material) {
+ case IRON_INGOT, GOLD_INGOT, COPPER_INGOT, NETHERITE_SCRAP, DIAMOND, EMERALD, REDSTONE, COAL, QUARTZ,
+ LAPIS_LAZULI -> true;
+ default -> false;
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestTameEntityListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestTameEntityListener.java
new file mode 100644
index 0000000..f691e5b
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestTameEntityListener.java
@@ -0,0 +1,32 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityTameEvent;
+
+public class QuestTameEntityListener implements Listener {
+
+ // TAME_ENTITY("驯服实体"),
+ @EventHandler(ignoreCancelled = true)
+ public void onTameEntity(EntityTameEvent e){
+ if(e.getOwner() instanceof Player player){
+ EntityType entityType = e.getEntity().getType();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 驯服了 "+entityType.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.TAME_ENTITY,
+ entityType.name(),
+ 1
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestUseAnvilListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestUseAnvilListener.java
new file mode 100644
index 0000000..0dec088
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestUseAnvilListener.java
@@ -0,0 +1,45 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.AnvilInventory;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+public class QuestUseAnvilListener implements Listener {
+ //USE_ANVIL("使用铁砧"),
+
+ @EventHandler(ignoreCancelled = true)
+ public void onUseAnvil(InventoryClickEvent e){
+ if (!(e.getWhoClicked() instanceof Player player)) {
+ return;
+ }
+ if (!(e.getInventory() instanceof AnvilInventory)) {
+ return;
+ }
+ if(e.getRawSlot() != 2){
+ return;
+ }
+ ItemStack result = e.getCurrentItem();
+ if(result == null || result.getType().isAir()){
+ return;
+ }
+ Material material = result.getType();
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 " + player.getName() + " 使用了铁砧 "+material.name());
+ }
+ PlayerQuestManager.addProgress(
+ player.getUniqueId(),
+ QuestTaskType.USE_ANVIL,
+ material.name(),
+ 1
+ );
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/listener/task/QuestVillagerListener.java b/src/main/java/com/io/yaohun/questengine/listener/task/QuestVillagerListener.java
new file mode 100644
index 0000000..4da8855
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/listener/task/QuestVillagerListener.java
@@ -0,0 +1,66 @@
+package com.io.yaohun.questengine.listener.task;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.quest.QuestTaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Villager;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.MerchantInventory;
+import org.bukkit.inventory.MerchantRecipe;
+
+import java.util.UUID;
+
+public class QuestVillagerListener implements Listener {
+
+ //VILLAGER_TRADE_ITEM("村民交易物品"),
+ //VILLAGER_TRADE_JOB("村民交易职业"),
+ @EventHandler(ignoreCancelled = true)
+ public void onVillagerTrade(InventoryClickEvent e){
+ if (!(e.getWhoClicked() instanceof Player player)) {
+ return;
+ }
+ if (!(e.getInventory() instanceof MerchantInventory inventory)) {
+ return;
+ }
+ if(e.getRawSlot() != 2){
+ return;
+ }
+ ItemStack current = e.getCurrentItem();
+ if(current == null || current.getType().isAir()){
+ return;
+ }
+ MerchantRecipe recipe = inventory.getSelectedRecipe();
+ if(recipe == null){
+ return;
+ }
+ UUID playerUuid = player.getUniqueId();
+ ItemStack result = recipe.getResult();
+ // 物品交易任务
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 向村民交易物品 "+result.getType().name());
+ }
+ PlayerQuestManager.addProgress(
+ playerUuid,
+ QuestTaskType.VILLAGER_TRADE_ITEM,
+ result.getType().name(),
+ result.getAmount()
+ );
+ // 职业判定任务
+ if(inventory.getMerchant() instanceof Villager villager){
+ if(QEMain.DEBUG){
+ Bukkit.getConsoleSender().sendMessage("[调试 - 任务] 玩家 "+player.getName()+" 向 "+villager.getProfession().name()+" 村民进行交易");
+ }
+ PlayerQuestManager.addProgress(
+ playerUuid,
+ QuestTaskType.VILLAGER_TRADE_JOB,
+ villager.getProfession().name(),
+ recipe.getVillagerExperience()
+ );
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/PlayerQuestData.java b/src/main/java/com/io/yaohun/questengine/player/PlayerQuestData.java
new file mode 100644
index 0000000..fb9035e
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/PlayerQuestData.java
@@ -0,0 +1,47 @@
+package com.io.yaohun.questengine.player;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PlayerQuestData {
+
+ private final UUID uuid;
+ private final Map questProgressMap = new ConcurrentHashMap<>();
+
+ public PlayerQuestData(UUID uuid) {
+ this.uuid = uuid;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public boolean hasQuest(String questId) {
+ return questProgressMap.containsKey(questId);
+ }
+
+ public void addQuest(PlayerQuestProgress progress) {
+ questProgressMap.put(progress.getQuestId(), progress);
+ }
+
+ public PlayerQuestProgress getQuestProgress(String questId) {
+ return questProgressMap.get(questId);
+ }
+
+ public void removeQuest(String questId) {
+ questProgressMap.remove(questId);
+ }
+
+ public Map getQuestProgressMap() {
+ return questProgressMap;
+ }
+
+ public PlayerQuestData copy() {
+ PlayerQuestData copy = new PlayerQuestData(uuid);
+ for (PlayerQuestProgress progress : questProgressMap.values()) {
+ copy.addQuest(progress.copy());
+ }
+ return copy;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/PlayerQuestManager.java b/src/main/java/com/io/yaohun/questengine/player/PlayerQuestManager.java
new file mode 100644
index 0000000..d38ce50
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/PlayerQuestManager.java
@@ -0,0 +1,219 @@
+package com.io.yaohun.questengine.player;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.storage.PlayerQuestSaveQueue;
+import com.io.yaohun.questengine.player.storage.PlayerQuestStorageManager;
+import com.io.yaohun.questengine.quest.*;
+import com.io.yaohun.questengine.util.ColorUtil;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+public class PlayerQuestManager {
+
+ private static final Map playerDataMap = new ConcurrentHashMap<>();
+
+ public static PlayerQuestData getPlayerData(UUID uuid) {
+ return playerDataMap.computeIfAbsent(
+ uuid,
+ id -> PlayerQuestStorageManager.getStorage().load(id)
+ );
+ }
+
+ public static PlayerQuestData getCachedPlayerData(UUID uuid) {
+ return playerDataMap.get(uuid);
+ }
+
+ public static void loadPlayer(UUID uuid){
+ getPlayerData(uuid);
+ }
+
+ public static void savePlayer(UUID uuid) {
+ PlayerQuestData data = playerDataMap.get(uuid);
+
+ if (data != null) {
+ PlayerQuestSaveQueue.markDirty(uuid);
+ }
+ }
+
+ public static void saveAll() {
+ for (PlayerQuestData data : playerDataMap.values()) {
+ PlayerQuestStorageManager.getStorage().save(data);
+ }
+ }
+
+ public static boolean hasAccepted(UUID uuid, String questId) {
+ return getPlayerData(uuid).hasQuest(questId);
+ }
+
+ public static boolean acceptQuest(UUID uuid, String questId) {
+ PlayerQuestData data = getPlayerData(uuid);
+ if (data.hasQuest(questId)) {
+ return false;
+ }
+ data.addQuest(new PlayerQuestProgress(questId));
+ PlayerQuestSaveQueue.markDirty(uuid);
+ return true;
+ }
+
+ public static PlayerQuestProgress getProgress(UUID uuid, String questId) {
+ return getPlayerData(uuid).getQuestProgress(questId);
+ }
+
+ public static void removeQuest(UUID uuid, String questId) {
+ PlayerQuestData data = getPlayerData(uuid);
+ data.removeQuest(questId);
+ PlayerQuestSaveQueue.markDirty(uuid);
+ }
+
+ public static void removeQuestsFromCache(Collection questIds) {
+ for (PlayerQuestData data : playerDataMap.values()) {
+ for (String questId : questIds) {
+ data.removeQuest(questId);
+ }
+ PlayerQuestSaveQueue.markDirty(data.getUuid());
+ }
+ }
+
+ public static void clearPlayer(UUID uuid) {
+ savePlayerNow(uuid);
+ playerDataMap.remove(uuid);
+ }
+
+ public static void savePlayerNow(UUID uuid) {
+ PlayerQuestData data = playerDataMap.get(uuid);
+
+ if (data != null) {
+ PlayerQuestStorageManager.getStorage().save(data);
+ }
+ }
+
+ public static void unloadPlayer(UUID uuid){
+ playerDataMap.remove(uuid);
+ PlayerQuestSaveQueue.removeDirty(uuid);
+ }
+
+ public static void deletePlayerData(UUID uuid){
+
+ playerDataMap.remove(uuid);
+
+ PlayerQuestStorageManager
+ .getStorage()
+ .deletePlayer(uuid);
+ }
+
+ public static boolean clearAllQuests(UUID uuid){
+
+ PlayerQuestData data = playerDataMap.get(uuid);
+
+ if(data != null){
+ data.getQuestProgressMap().clear();
+ }
+
+ savePlayerNow(uuid);
+
+ return true;
+ }
+
+ public static Map getPlayerDataMap() {
+ return playerDataMap;
+ }
+
+ public static void addProgress(UUID uuid, QuestTaskType type, String target, int amount) {
+ PlayerQuestData playerData = getPlayerData(uuid);
+ boolean changed = false;
+ for (PlayerQuestProgress progress : playerData.getQuestProgressMap().values()) {
+ // 已完成任务跳过
+ if (progress.isCompleted()) {
+ continue;
+ }
+ Quest quest = QuestManager.getQuest(progress.getQuestId());
+ if (quest == null) {
+ continue;
+ }
+ Player player = Bukkit.getPlayer(uuid);
+ if (player == null) {
+ continue;
+ }
+ if (!QuestConditionChecker.canProgress(player, quest)) {
+ continue;
+ }
+ boolean allCompleted = true;
+ for (QuestTask task : quest.getTasks().values()) {
+ // 类型不匹配
+ if (task.getType() != type) {
+ allCompleted = false;
+ continue;
+ }
+ // 目标不匹配
+ if (!task.isTarget(target)) {
+ allCompleted = false;
+ continue;
+ }
+ int current = progress.getProgress(task.getId());
+ // 已达成
+ if (current >= task.getAmount()) {
+ continue;
+ }
+ progress.addProgress(task.getId(), amount);
+ changed = true;
+ int newValue = progress.getProgress(task.getId());
+ if (QEMain.DEBUG) {
+
+ int fixedValue = Math.min(newValue, task.getAmount());
+
+ player.sendMessage(
+ ColorUtil.color(
+ "&7[任务调试] &f"
+ + task.getDisplayName()
+ + " &a+"
+ + amount
+ + " &7("
+ + fixedValue
+ + "/"
+ + task.getAmount()
+ + ")"
+ )
+ );
+ }
+ // 防止超出
+ if (newValue > task.getAmount()) {
+ progress.setProgress(task.getId(), task.getAmount());
+ }
+ }
+ // 检查是否全部完成
+ for (QuestTask task : quest.getTasks().values()) {
+ int current = progress.getProgress(task.getId());
+ if (current < task.getAmount()) {
+ allCompleted = false;
+ break;
+ }
+ }
+ // 完成任务
+ if (allCompleted) {
+ progress.setCompleted(true);
+ changed = true;
+ // 完成消息
+ for (String msg : quest.getCompleteMessages()) {
+ player.sendMessage(ColorUtil.color(msg));
+ }
+ // reward message
+ for (String msg : quest.getReward().getMessages()) {
+ player.sendMessage(ColorUtil.color(msg.replace("{name}", quest.getDisplayName())));
+ }
+ // reward command
+ for (String cmd : quest.getReward().getCommands()) {
+ Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", player.getName()));
+ }
+
+ }
+ }
+ if (changed) {
+ PlayerQuestSaveQueue.markDirty(uuid);
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/PlayerQuestProgress.java b/src/main/java/com/io/yaohun/questengine/player/PlayerQuestProgress.java
new file mode 100644
index 0000000..457235a
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/PlayerQuestProgress.java
@@ -0,0 +1,53 @@
+package com.io.yaohun.questengine.player;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PlayerQuestProgress {
+
+ private final String questId;
+ private final Map taskProgressMap = new ConcurrentHashMap<>();
+ private volatile boolean completed;
+
+ public PlayerQuestProgress(String questId) {
+ this.questId = questId;
+ }
+
+ public String getQuestId() {
+ return questId;
+ }
+
+ public int getProgress(String taskId) {
+ return taskProgressMap.getOrDefault(taskId, 0);
+ }
+
+ public void addProgress(String taskId, int amount) {
+ int current = getProgress(taskId);
+ taskProgressMap.put(taskId, current + amount);
+ }
+
+ public void setProgress(String taskId, int amount) {
+ taskProgressMap.put(taskId, amount);
+ }
+
+ public Map getTaskProgressMap() {
+ return taskProgressMap;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ }
+
+ public PlayerQuestProgress copy() {
+ PlayerQuestProgress copy = new PlayerQuestProgress(questId);
+ copy.setCompleted(completed);
+ for (Map.Entry entry : taskProgressMap.entrySet()) {
+ copy.setProgress(entry.getKey(), entry.getValue());
+ }
+ return copy;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/storage/MySQLPlayerQuestStorage.java b/src/main/java/com/io/yaohun/questengine/player/storage/MySQLPlayerQuestStorage.java
new file mode 100644
index 0000000..3c21670
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/storage/MySQLPlayerQuestStorage.java
@@ -0,0 +1,247 @@
+package com.io.yaohun.questengine.player.storage;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.quest.QuestType;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+
+import java.sql.*;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+public class MySQLPlayerQuestStorage implements PlayerQuestStorage{
+ private final QEMain plugin;
+ private HikariDataSource dataSource;
+ private String table;
+
+ public MySQLPlayerQuestStorage(QEMain plugin){
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void init() {
+ try {
+ FileConfiguration ymal = plugin.getConfig();
+ String host = ymal.getString("Storage.MySQL.Host");
+ int port = ymal.getInt("Storage.MySQL.Port");
+ String database = ymal.getString("Storage.MySQL.Database");
+ String username = ymal.getString("Storage.MySQL.Username");
+ String password = ymal.getString("Storage.MySQL.Password");
+
+ String prefix = ymal.getString("Storage.MySQL.TablePrefix","auquest_");
+
+ this.table = prefix + "player_progress";
+
+ HikariConfig config = new HikariConfig();
+ config.setJdbcUrl(
+ "jdbc:mysql://" + host + ":" + port + "/" + database
+ + "?useSSL=false"
+ + "&characterEncoding=utf8"
+ + "&serverTimezone=Asia/Shanghai"
+ + "&rewriteBatchedStatements=true"
+ );
+
+ config.setUsername(username);
+ config.setPassword(password);
+
+ config.setMaximumPoolSize(ymal.getInt("Storage.MySQL.Pool.MaximumPoolSize", 10));
+ config.setMinimumIdle(ymal.getInt("Storage.MySQL.Pool.MinimumIdle", 2));
+ config.setConnectionTimeout(ymal.getLong("Storage.MySQL.Pool.ConnectionTimeout", 10000));
+ config.setIdleTimeout(ymal.getLong("Storage.MySQL.Pool.IdleTimeout", 600000));
+ config.setMaxLifetime(ymal.getLong("Storage.MySQL.Pool.MaxLifetime", 1800000));
+
+ config.addDataSourceProperty("cachePrepStmts", "true");
+ config.addDataSourceProperty("prepStmtCacheSize", "250");
+ config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
+
+ dataSource = new HikariDataSource(config);
+
+ createTable();
+
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] MySQLStorage 初始化完成.");
+ } catch (Exception e) {
+ plugin.getLogger().warning("[AuQuest] MySQLStorage 初始化失败.");
+ e.printStackTrace();
+ }
+ }
+
+ private void createTable() throws SQLException {
+ String progressSql =
+ "CREATE TABLE IF NOT EXISTS " + table + " (" +
+ "uuid VARCHAR(36) NOT NULL," +
+ "quest_id VARCHAR(64) NOT NULL," +
+ "task_id VARCHAR(64) NOT NULL," +
+ "progress INT NOT NULL DEFAULT 0," +
+ "completed TINYINT(1) NOT NULL DEFAULT 0," +
+ "PRIMARY KEY(uuid, quest_id, task_id)" +
+ ");";
+
+ String resetTable = getResetTable();
+
+ String resetSql =
+ "CREATE TABLE IF NOT EXISTS " + resetTable + " (" +
+ "reset_type VARCHAR(16) NOT NULL," +
+ "reset_key VARCHAR(32) NOT NULL," +
+ "reset_time BIGINT NOT NULL," +
+ "PRIMARY KEY(reset_type, reset_key)" +
+ ");";
+
+ try (Connection connection = dataSource.getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.executeUpdate(progressSql);
+ statement.executeUpdate(resetSql);
+ }
+ }
+
+ private String getResetTable() {
+ String prefix = plugin.getConfig().getString("Storage.MySQL.TablePrefix", "auquest_");
+ return prefix + "reset_record";
+ }
+
+ @Override
+ public PlayerQuestData load(UUID uuid) {
+ PlayerQuestData data = new PlayerQuestData(uuid);
+ String sql = "SELECT quest_id, task_id, progress, completed FROM " + table + " WHERE uuid=?";
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement ps = connection.prepareStatement(sql)) {
+ ps.setString(1, uuid.toString());
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ String questId = rs.getString("quest_id");
+ String taskId = rs.getString("task_id");
+ int progressValue = rs.getInt("progress");
+ boolean completed = rs.getBoolean("completed");
+ PlayerQuestProgress progress = data.getQuestProgress(questId);
+ if (progress == null) {
+ progress = new PlayerQuestProgress(questId);
+ progress.setCompleted(completed);
+ data.addQuest(progress);
+ }
+ progress.setProgress(taskId, progressValue);
+ if (completed) {
+ progress.setCompleted(true);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] 玩家数据读取失败: " + data.getUuid());
+ e.printStackTrace();
+ }
+ return data;
+ }
+
+ @Override
+ public void save(PlayerQuestData data) {
+ PlayerQuestData snapshot = data.copy();
+ String deleteSql = "DELETE FROM " + table + " WHERE uuid=?";
+ String insertSql = "INSERT INTO " + table + " (uuid, quest_id, task_id, progress, completed) " + "VALUES (?, ?, ?, ?, ?)";
+ try (Connection connection = dataSource.getConnection()) {
+ connection.setAutoCommit(false);
+ try (PreparedStatement deletePs = connection.prepareStatement(deleteSql)) {
+ deletePs.setString(1, snapshot.getUuid().toString());
+ deletePs.executeUpdate();
+ }
+ try (PreparedStatement insertPs = connection.prepareStatement(insertSql)) {
+ for (PlayerQuestProgress progress : snapshot.getQuestProgressMap().values()) {
+ if (progress.getTaskProgressMap().isEmpty()) {
+ insertPs.setString(1, snapshot.getUuid().toString());
+ insertPs.setString(2, progress.getQuestId());
+ insertPs.setString(3, "__quest__");
+ insertPs.setInt(4, 0);
+ insertPs.setBoolean(5, progress.isCompleted());
+ insertPs.addBatch();
+ continue;
+ }
+ for (Map.Entry entry : progress.getTaskProgressMap().entrySet()) {
+ insertPs.setString(1, snapshot.getUuid().toString());
+ insertPs.setString(2, progress.getQuestId());
+ insertPs.setString(3, entry.getKey());
+ insertPs.setInt(4, entry.getValue());
+ insertPs.setBoolean(5, progress.isCompleted());
+ insertPs.addBatch();
+ }
+ }
+ insertPs.executeBatch();
+ }
+ connection.commit();
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] 玩家数据保存失败: " + snapshot.getUuid());
+ e.printStackTrace();
+ }
+ }
+ @Override
+ public void close() {
+ if (dataSource != null && !dataSource.isClosed()) {
+ dataSource.close();
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] MySQLStorage 已正常关闭.");
+ }
+ }
+
+ @Override
+ public void deleteQuestsByIds(Collection questIds) {
+ if (questIds == null || questIds.isEmpty()) {
+ return;
+ }
+
+ String sql = "DELETE FROM " + table + " WHERE quest_id = ?";
+
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement ps = connection.prepareStatement(sql)) {
+
+ for (String questId : questIds) {
+ ps.setString(1, questId);
+ ps.addBatch();
+ }
+
+ ps.executeBatch();
+
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] MySQLStorage 删除刷新任务数据失败.");
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public boolean tryMarkReset(QuestType type, String resetKey) {
+ String sql = "INSERT IGNORE INTO " + getResetTable() +
+ " (reset_type, reset_key, reset_time) VALUES (?, ?, ?)";
+
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement ps = connection.prepareStatement(sql)) {
+
+ ps.setString(1, type.name());
+ ps.setString(2, resetKey);
+ ps.setLong(3, System.currentTimeMillis());
+
+ return ps.executeUpdate() == 1;
+
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] MySQLStorage 记录任务刷新状态失败: " + type + " / " + resetKey);
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ @Override
+ public void deletePlayer(UUID uuid) {
+
+ String sql =
+ "DELETE FROM " + table + " WHERE uuid=?";
+
+ try(Connection connection = dataSource.getConnection();
+ PreparedStatement ps = connection.prepareStatement(sql)){
+
+ ps.setString(1, uuid.toString());
+
+ ps.executeUpdate();
+
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestSaveQueue.java b/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestSaveQueue.java
new file mode 100644
index 0000000..92bc6df
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestSaveQueue.java
@@ -0,0 +1,78 @@
+package com.io.yaohun.questengine.player.storage;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+public class PlayerQuestSaveQueue {
+ private static final Set dirtyPlayers = new HashSet<>();
+ private static BukkitTask task;
+
+ public static void markDirty(UUID uuid) {
+ synchronized (dirtyPlayers) {
+ dirtyPlayers.add(uuid);
+ }
+ }
+
+ public static void start() {
+ int intervalSeconds = QEMain.inst().getConfig().getInt("Storage.SaveInterval", 10);
+
+ task = Bukkit.getScheduler().runTaskTimerAsynchronously(
+ QEMain.inst(),
+ PlayerQuestSaveQueue::flushAsync,
+ intervalSeconds * 20L,
+ intervalSeconds * 20L
+ );
+ }
+
+ public static void removeDirty(UUID uuid){
+ synchronized (dirtyPlayers) {
+ dirtyPlayers.remove(uuid);
+ }
+ }
+
+ public static void flushAsync() {
+ Set copy;
+ synchronized (dirtyPlayers) {
+ if (dirtyPlayers.isEmpty()) {
+ return;
+ }
+ copy = new HashSet<>(dirtyPlayers);
+ dirtyPlayers.clear();
+ }
+ for (UUID uuid : copy) {
+ PlayerQuestData data = PlayerQuestManager.getCachedPlayerData(uuid);
+ if (data != null) {
+ PlayerQuestStorageManager.getStorage().save(data);
+ }
+ }
+ }
+
+ public static void flushSync() {
+ if (task != null) {
+ task.cancel();
+ task = null;
+ }
+
+ Set copy;
+
+ synchronized (dirtyPlayers) {
+ copy = new HashSet<>(dirtyPlayers);
+ dirtyPlayers.clear();
+ }
+
+ for (UUID uuid : copy) {
+ PlayerQuestData data = PlayerQuestManager.getCachedPlayerData(uuid);
+
+ if (data != null) {
+ PlayerQuestStorageManager.getStorage().save(data);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestStorage.java b/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestStorage.java
new file mode 100644
index 0000000..70f0c16
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestStorage.java
@@ -0,0 +1,24 @@
+package com.io.yaohun.questengine.player.storage;
+
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.quest.QuestType;
+
+import java.util.Collection;
+import java.util.UUID;
+
+public interface PlayerQuestStorage {
+
+ void init();
+
+ PlayerQuestData load(UUID uuid);
+
+ void save(PlayerQuestData data);
+
+ void deleteQuestsByIds(Collection questIds);
+
+ boolean tryMarkReset(QuestType type, String resetKey);
+
+ void deletePlayer(UUID uuid);
+
+ void close();
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestStorageManager.java b/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestStorageManager.java
new file mode 100644
index 0000000..de25443
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/storage/PlayerQuestStorageManager.java
@@ -0,0 +1,40 @@
+package com.io.yaohun.questengine.player.storage;
+
+import com.io.yaohun.questengine.QEMain;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class PlayerQuestStorageManager {
+
+ private static PlayerQuestStorage storage;
+
+ public static void init(QEMain plugin){
+ FileConfiguration config = plugin.getConfig();
+ String type = config.getString("Storage.Type","yaml").toLowerCase();
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] 数据存储模式: §e"+type);
+ switch (type){
+ case "yaml":
+ storage = new YamlPlayerQuestStorage(plugin);
+ break;
+ case "sqlite":
+ storage = new SQLitePlayerQuestStorage(plugin);
+ break;
+ case "mysql":
+ storage = new MySQLPlayerQuestStorage(plugin);
+ break;
+ default:
+ storage = new YamlPlayerQuestStorage(plugin);
+ }
+ storage.init();
+ }
+
+ public static PlayerQuestStorage getStorage(){
+ return storage;
+ }
+
+ public static void close(){
+ if(storage != null){
+ storage.close();
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/storage/SQLitePlayerQuestStorage.java b/src/main/java/com/io/yaohun/questengine/player/storage/SQLitePlayerQuestStorage.java
new file mode 100644
index 0000000..e3b5835
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/storage/SQLitePlayerQuestStorage.java
@@ -0,0 +1,257 @@
+package com.io.yaohun.questengine.player.storage;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.quest.QuestType;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.sql.*;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+public class SQLitePlayerQuestStorage implements PlayerQuestStorage{
+
+ private final QEMain plugin;
+ private Connection connection;
+
+ public SQLitePlayerQuestStorage(QEMain plugin){
+ this.plugin = plugin;
+ }
+
+ // plugin.getLogger().warning("[AuQuest] 玩家数据保存失败: " + data.getUuid());
+ @Override
+ public void init() {
+ try {
+ File folder = plugin.getDataFolder();
+ if (!folder.exists()) {
+ folder.mkdirs();
+ }
+ FileConfiguration config = plugin.getConfig();
+ String fileName = config.getString("Storage.SQLite.File", "PlayerData.db");
+ File databaseFile = new File(folder, fileName);
+
+ connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
+
+ createTable();
+
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] SQLiteStorage 初始化完成.");
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] SQLiteStorage 初始化失败.");
+ e.printStackTrace();
+ }
+ }
+
+ private void createTable() throws SQLException {
+ String sql = """
+ CREATE TABLE IF NOT EXISTS quest_player_progress (
+ uuid TEXT NOT NULL,
+ quest_id TEXT NOT NULL,
+ task_id TEXT NOT NULL,
+ progress INTEGER NOT NULL DEFAULT 0,
+ completed INTEGER NOT NULL DEFAULT 0,
+ PRIMARY KEY (uuid, quest_id, task_id)
+ );
+ """;
+ try (Statement statement = connection.createStatement()) {
+ statement.executeUpdate(sql);
+ }
+
+ String resetSql = """
+ CREATE TABLE IF NOT EXISTS quest_reset_record (
+ reset_type TEXT NOT NULL,
+ reset_key TEXT NOT NULL,
+ reset_time INTEGER NOT NULL,
+ PRIMARY KEY(reset_type, reset_key)
+ );
+ """;
+
+ try (Statement statement = connection.createStatement()) {
+ statement.executeUpdate(sql);
+ statement.executeUpdate(resetSql);
+ }
+ }
+
+ @Override
+ public synchronized PlayerQuestData load(UUID uuid) {
+ PlayerQuestData data = new PlayerQuestData(uuid);
+ String sql = "SELECT quest_id, task_id, progress, completed FROM quest_player_progress WHERE uuid = ?";
+
+ try (PreparedStatement ps = connection.prepareStatement(sql)) {
+ ps.setString(1, uuid.toString());
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ String questId = rs.getString("quest_id");
+ String taskId = rs.getString("task_id");
+ int progressValue = rs.getInt("progress");
+ boolean completed = rs.getInt("completed") == 1;
+ PlayerQuestProgress progress = data.getQuestProgress(questId);
+ if (progress == null) {
+ progress = new PlayerQuestProgress(questId);
+ progress.setCompleted(completed);
+ data.addQuest(progress);
+ }
+ progress.setProgress(taskId, progressValue);
+ if (completed) {
+ progress.setCompleted(true);
+ }
+ }
+ }
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] 玩家数据读取失败: " + data.getUuid());
+ e.printStackTrace();
+ }
+ return data;
+ }
+
+ @Override
+ public synchronized void save(PlayerQuestData data) {
+ PlayerQuestData snapshot = data.copy();
+ String deleteSql = "DELETE FROM quest_player_progress WHERE uuid = ?";
+ String insertSql = """
+ INSERT INTO quest_player_progress
+ (uuid, quest_id, task_id, progress, completed)
+ VALUES (?, ?, ?, ?, ?)
+ """;
+ try {
+ connection.setAutoCommit(false);
+ try (PreparedStatement deletePs = connection.prepareStatement(deleteSql)) {
+ deletePs.setString(1, snapshot.getUuid().toString());
+ deletePs.executeUpdate();
+ }
+ try (PreparedStatement insertPs = connection.prepareStatement(insertSql)) {
+ for (PlayerQuestProgress progress : snapshot.getQuestProgressMap().values()) {
+ for (Map.Entry entry : progress.getTaskProgressMap().entrySet()) {
+ insertPs.setString(1, snapshot.getUuid().toString());
+ insertPs.setString(2, progress.getQuestId());
+ insertPs.setString(3, entry.getKey());
+ insertPs.setInt(4, entry.getValue());
+ insertPs.setInt(5, progress.isCompleted() ? 1 : 0);
+ insertPs.addBatch();
+ }
+ if (progress.getTaskProgressMap().isEmpty()) {
+ insertPs.setString(1, snapshot.getUuid().toString());
+ insertPs.setString(2, progress.getQuestId());
+ insertPs.setString(3, "__quest__");
+ insertPs.setInt(4, 0);
+ insertPs.setInt(5, progress.isCompleted() ? 1 : 0);
+ insertPs.addBatch();
+ }
+ }
+ insertPs.executeBatch();
+ }
+ connection.commit();
+ } catch (SQLException e) {
+ try {
+ connection.rollback();
+ } catch (SQLException ignored) {
+ }
+ plugin.getLogger().warning("[AuQuest] 玩家数据保存失败: " + snapshot.getUuid());
+ e.printStackTrace();
+ }finally {
+ try {
+ connection.setAutoCommit(true);
+ } catch (SQLException ignored) {
+ }
+ }
+ }
+
+ @Override
+ public synchronized void close() {
+ if (connection == null) {
+ return;
+ }try {
+ connection.close();
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] SQLiteStorage 已正常关闭.");
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public synchronized void deleteQuestsByIds(Collection questIds) {
+ if (questIds == null || questIds.isEmpty()) {
+ return;
+ }
+ String sql = "DELETE FROM quest_player_progress WHERE quest_id = ?";
+
+ try (PreparedStatement ps = connection.prepareStatement(sql)) {
+ for (String questId : questIds) {
+ ps.setString(1, questId);
+ ps.addBatch();
+ }
+ ps.executeBatch();
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] SQLiteStorage 删除刷新任务数据失败.");
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public synchronized void deletePlayer(UUID uuid) {
+
+ String sql =
+ "DELETE FROM quest_player_progress WHERE uuid=?";
+
+ try(PreparedStatement ps = connection.prepareStatement(sql)){
+
+ ps.setString(1, uuid.toString());
+
+ ps.executeUpdate();
+
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public synchronized boolean tryMarkReset(QuestType type, String resetKey) {
+ String sql = "INSERT OR IGNORE INTO quest_reset_record (reset_type, reset_key, reset_time) VALUES (?, ?, ?)";
+
+ try (PreparedStatement ps = connection.prepareStatement(sql)) {
+ ps.setString(1, type.name());
+ ps.setString(2, resetKey);
+ ps.setLong(3, System.currentTimeMillis());
+
+ return ps.executeUpdate() == 1;
+
+ } catch (SQLException e) {
+ plugin.getLogger().warning("[AuQuest] SQLiteStorage 记录任务刷新状态失败: " + type + " / " + resetKey);
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public synchronized File exportToYaml() {
+
+ File outFile = new File(plugin.getDataFolder(), "out-sqlite-" + System.currentTimeMillis() + ".yml");
+ YamlConfiguration yaml = new YamlConfiguration();
+ String sql = "SELECT uuid, quest_id, task_id, progress, completed FROM quest_player_progress ORDER BY uuid, quest_id, task_id";
+
+ try (PreparedStatement ps = connection.prepareStatement(sql);
+ ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ String uuid = rs.getString("uuid");
+ String questId = rs.getString("quest_id");
+ String taskId = rs.getString("task_id");
+ int progress = rs.getInt("progress");
+ boolean completed = rs.getInt("completed") == 1;
+ String path = "players." + uuid + ".quests." + questId;
+ yaml.set(path + ".completed", completed);
+ yaml.set(path + ".progress." + taskId, progress);
+ }
+
+ yaml.save(outFile);
+ return outFile;
+
+ } catch (Exception e) {
+ plugin.getLogger().warning("[AuQuest] SQLiteStorage 数据导出失败");
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/player/storage/YamlPlayerQuestStorage.java b/src/main/java/com/io/yaohun/questengine/player/storage/YamlPlayerQuestStorage.java
new file mode 100644
index 0000000..918094e
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/player/storage/YamlPlayerQuestStorage.java
@@ -0,0 +1,159 @@
+package com.io.yaohun.questengine.player.storage;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestData;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.quest.QuestType;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+public class YamlPlayerQuestStorage implements PlayerQuestStorage {
+
+ private final QEMain plugin;
+
+ public YamlPlayerQuestStorage(QEMain plugin){
+ this.plugin = plugin;
+ }
+ @Override
+ public void init() {
+ File folder = getPlayerFolder();
+ if (!folder.exists()) {
+ folder.mkdirs();
+ }
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] YamlStorage 初始化完成.");
+ }
+
+ @Override
+ public PlayerQuestData load(UUID uuid) {
+ PlayerQuestData data = new PlayerQuestData(uuid);
+ File file = getPlayerFile(uuid);
+ if(!file.exists()){
+ return data;
+ }
+ FileConfiguration yaml = YamlConfiguration.loadConfiguration(file);
+ ConfigurationSection questsSection = yaml.getConfigurationSection("quests");
+ if(questsSection == null){
+ return data;
+ }
+ for (String questId : questsSection.getKeys(false)){
+ PlayerQuestProgress progress = new PlayerQuestProgress(questId);
+ boolean completed = yaml.getBoolean("quests." + questId + ".completed", false);
+ progress.setCompleted(completed);
+ ConfigurationSection progressSection = yaml.getConfigurationSection("quests." + questId + ".progress");
+ if (progressSection != null) {
+ for (String taskId : progressSection.getKeys(false)) {
+ int value = progressSection.getInt(taskId);
+ progress.setProgress(taskId, value);
+ }
+ }
+ data.addQuest(progress);
+ }
+ return data;
+ }
+
+ @Override
+ public void save(PlayerQuestData data) {
+ PlayerQuestData snapshot = data.copy();
+ File file = getPlayerFile(snapshot.getUuid());
+ YamlConfiguration yaml = new YamlConfiguration();
+ for (PlayerQuestProgress progress : snapshot.getQuestProgressMap().values()) {
+ String path = "quests." + progress.getQuestId();
+ yaml.set(path + ".completed", progress.isCompleted());
+ for (Map.Entry entry : progress.getTaskProgressMap().entrySet()) {
+ yaml.set(path + ".progress." + entry.getKey(), entry.getValue());
+ }
+ }
+ try {
+ yaml.save(file);
+ } catch (IOException e) {
+ plugin.getLogger().warning("[AuQuest] 玩家数据保存失败: " + snapshot.getUuid());
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void deleteQuestsByIds(Collection questIds) {
+ File folder = getPlayerFolder();
+ File[] files = folder.listFiles((dir, name) -> name.endsWith(".yml"));
+ if (files == null) {
+ return;
+ }
+ for (File file : files) {
+ YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
+ boolean changed = false;
+ for (String questId : questIds) {
+ String path = "quests." + questId;
+
+ if (yaml.contains(path)) {
+ yaml.set(path, null);
+ changed = true;
+ }
+ }
+ if (changed) {
+ try {
+ yaml.save(file);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean tryMarkReset(QuestType type, String resetKey) {
+ File file = new File(plugin.getDataFolder(), "last_reset.yml");
+
+ YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
+
+ String path = "reset." + type.name();
+
+ String oldKey = yaml.getString(path);
+
+ if (resetKey.equals(oldKey)) {
+ return false;
+ }
+
+ yaml.set(path, resetKey);
+ yaml.set(path + "_time", System.currentTimeMillis());
+
+ try {
+ yaml.save(file);
+ return true;
+ } catch (Exception e) {
+ plugin.getLogger().warning("YAML 记录任务刷新状态失败: " + type + " / " + resetKey);
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ @Override
+ public void close() {
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] YamlStorage 已正常关闭.");
+ }
+
+ @Override
+ public void deletePlayer(UUID uuid) {
+
+ File file = getPlayerFile(uuid);
+
+ if(file.exists()){
+ file.delete();
+ }
+ }
+
+ private File getPlayerFolder() {
+ return new File(plugin.getDataFolder(), "PlayerData");
+ }
+
+ private File getPlayerFile(UUID uuid) {
+ return new File(getPlayerFolder(), uuid + ".yml");
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/quest/Quest.java b/src/main/java/com/io/yaohun/questengine/quest/Quest.java
new file mode 100644
index 0000000..19ff4c9
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/Quest.java
@@ -0,0 +1,75 @@
+package com.io.yaohun.questengine.quest;
+
+import com.io.yaohun.questengine.util.ColorUtil;
+
+import java.util.List;
+import java.util.Map;
+
+public class Quest {
+
+ private final String id;
+ private final String displayName;
+ private final List description;
+ private final QuestType type;
+ private final List receiveMessages;
+ private final List completeMessages;
+ private final Map tasks;
+ private final QuestCondition condition;
+ private final QuestReward reward;
+
+
+ public Quest(String id, String displayName,
+ List description,
+ QuestType type,
+ List receiveMessages,
+ List completeMessages,
+ Map tasks,
+ QuestCondition condition,
+ QuestReward reward) {
+ this.id = id;
+ this.displayName = ColorUtil.color(displayName);
+ this.description = ColorUtil.color(description);
+ this.type = type;
+ this.receiveMessages = ColorUtil.color(receiveMessages);
+ this.completeMessages = ColorUtil.color(completeMessages);
+ this.tasks = tasks;
+ this.condition = condition;
+ this.reward = reward;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public List getDescription() {
+ return description;
+ }
+
+ public QuestType getType() {
+ return type;
+ }
+
+ public List getReceiveMessages() {
+ return receiveMessages;
+ }
+
+ public List getCompleteMessages() {
+ return completeMessages;
+ }
+
+ public Map getTasks() {
+ return tasks;
+ }
+
+ public QuestCondition getCondition() {
+ return condition;
+ }
+
+ public QuestReward getReward() {
+ return reward;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestCondition.java b/src/main/java/com/io/yaohun/questengine/quest/QuestCondition.java
new file mode 100644
index 0000000..75da4cf
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestCondition.java
@@ -0,0 +1,40 @@
+package com.io.yaohun.questengine.quest;
+
+import java.util.List;
+
+public class QuestCondition {
+
+ private final int level;
+ private final List completedQuests;
+ private final List worlds;
+
+ public QuestCondition(int level, List completedQuests, List worlds) {
+ this.level = level;
+ this.completedQuests = completedQuests;
+ this.worlds = worlds;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public List getCompletedQuests() {
+ return completedQuests;
+ }
+
+ public List getWorlds() {
+ return worlds;
+ }
+
+ public boolean hasLevelLimit() {
+ return level > 0;
+ }
+
+ public boolean hasCompletedQuestLimit() {
+ return completedQuests != null && !completedQuests.isEmpty();
+ }
+
+ public boolean hasWorldLimit() {
+ return worlds != null && !worlds.isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestConditionChecker.java b/src/main/java/com/io/yaohun/questengine/quest/QuestConditionChecker.java
new file mode 100644
index 0000000..827be0f
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestConditionChecker.java
@@ -0,0 +1,67 @@
+package com.io.yaohun.questengine.quest;
+
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.PlayerQuestProgress;
+import com.io.yaohun.questengine.util.ColorUtil;
+import com.io.yaohun.questengine.util.MessageUtil;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+
+public class QuestConditionChecker {
+
+ public static boolean canAccept(Player player, Quest quest, boolean sendMessage) {
+ QuestCondition condition = quest.getCondition();
+ if (condition == null) {
+ return true;
+ }
+ if (condition.hasLevelLimit()) {
+ int playerLevel = getPlayerLevel(player);
+ if (playerLevel < condition.getLevel()) {
+ if (sendMessage) {
+ String message = MessageUtil.getMessage("condition_not_quest");
+ message = message.replace("{level}", String.valueOf(condition.getLevel()));
+ MessageUtil.sendMessage(player, message, Sound.ENTITY_VILLAGER_NO);
+ }
+ return false;
+ }
+ }
+ if (condition.hasCompletedQuestLimit()) {
+ for (String needQuestId : condition.getCompletedQuests()) {
+ PlayerQuestProgress progress = PlayerQuestManager.getProgress(player.getUniqueId(), needQuestId);
+ if (progress == null || !progress.isCompleted()) {
+ if (sendMessage) {
+ String message = MessageUtil.getMessage("condition_not_quest");
+ message = message.replace("{quest}", needQuestId);
+ MessageUtil.sendMessage(player, message, Sound.ENTITY_VILLAGER_NO);
+ }
+ return false;
+ }
+ }
+ }
+ if (condition.hasWorldLimit()) {
+ String worldName = player.getWorld().getName();
+ if (!condition.getWorlds().contains(worldName)) {
+ if (sendMessage) {
+ MessageUtil.sendMessageKey(player, "condition_not_world", Sound.ENTITY_VILLAGER_NO);
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean canProgress(Player player, Quest quest) {
+
+ QuestCondition condition = quest.getCondition();
+
+ if (condition == null || !condition.hasWorldLimit()) {
+ return true;
+ }
+
+ return condition.getWorlds().contains(player.getWorld().getName());
+ }
+
+ private static int getPlayerLevel(Player player) {
+ return player.getLevel(); // 这里先用原版等级,后续可替换成你的等级系统API
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestLoader.java b/src/main/java/com/io/yaohun/questengine/quest/QuestLoader.java
new file mode 100644
index 0000000..11d225f
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestLoader.java
@@ -0,0 +1,149 @@
+package com.io.yaohun.questengine.quest;
+
+import com.io.yaohun.questengine.QEMain;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.util.*;
+
+public class QuestLoader {
+
+ private final QEMain plugin;
+
+ public QuestLoader(QEMain plugin){
+ this.plugin = plugin;
+ }
+
+ public Map loadQuests(){
+ Map questMap = new HashMap<>();
+
+ File folder = new File(plugin.getDataFolder(), "Quests");
+ if(!folder.exists()){
+ folder.mkdirs();
+ return questMap;
+ }
+ File[] files = folder.listFiles(((dir, name) -> name.endsWith(".yml")));
+ if(files == null || files.length == 0){
+ return questMap;
+ }
+ int dayCount = 0;
+ int weekCount = 0;
+ int monthCount = 0;
+ int fixedCount = 0;
+ for (File file : files){
+ FileConfiguration config = YamlConfiguration.loadConfiguration(file);
+ for (String questId : config.getKeys(false)){
+ ConfigurationSection section = config.getConfigurationSection(questId);
+ if(section == null){
+ continue;
+ }
+ Quest quest = loadQuest(questId, section, file.getName());
+ if(quest == null){
+ continue;
+ }
+ questMap.put(questId, quest);
+ switch (quest.getType()){
+ case DAILY:
+ dayCount++;
+ break;
+ case WEEKLY:
+ weekCount++;
+ break;
+ case MONTHLY:
+ monthCount++;
+ break;
+ case FIXED:
+ fixedCount++;
+ break;
+ }
+ }
+ }
+ Bukkit.getConsoleSender().sendMessage("[AuQuest] 已载入任务: ["+questMap.size()+"个]");
+ Bukkit.getConsoleSender().sendMessage(" - 每日 > §e"+dayCount+"个");
+ Bukkit.getConsoleSender().sendMessage(" - 每周 > §e"+weekCount+"个");
+ Bukkit.getConsoleSender().sendMessage(" - 每月 > §e"+monthCount+"个");
+ Bukkit.getConsoleSender().sendMessage(" - 长期 > §e"+fixedCount+"个");
+ return questMap;
+ }
+
+ private Quest loadQuest(String questId, ConfigurationSection section, String fileName){
+ try {
+ String displayName = section.getString("display_name", questId);
+ List description = section.getStringList("description");
+ QuestType questType = QuestType.valueOf(
+ section.getString("type", "FIXED").toUpperCase()
+ );
+ List receiveMessages = section.getStringList("messages.receive");
+ List completeMessages = section.getStringList("messages.complete");
+ Map tasks = loadTasks(section.getConfigurationSection("tasks"), questId);
+ if (tasks.isEmpty()) {
+ plugin.getLogger().warning("任务 " + questId + " 没有有效任务目标,文件: " + fileName);
+ return null;
+ }
+ QuestCondition condition = loadCondition(section.getConfigurationSection("conditions"));
+ QuestReward reward = loadReward(section.getConfigurationSection("rewards"), displayName);
+ return new Quest(questId, displayName, description, questType, receiveMessages, completeMessages, tasks, condition, reward);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private Map loadTasks(ConfigurationSection section, String questId) {
+ Map taskMap = new LinkedHashMap<>();
+ if (section == null) {
+ return taskMap;
+ }
+ for (String taskId : section.getKeys(false)) {
+ ConfigurationSection taskSection = section.getConfigurationSection(taskId);
+ if (taskSection == null) {
+ continue;
+ }
+ try {
+ QuestTaskType taskType = QuestTaskType.valueOf(taskSection.getString("type", "BREAK").toUpperCase());
+ String displayName = taskSection.getString("display_name", taskId);
+ int amount = taskSection.getInt("amount", 1);
+ Set targets = new HashSet<>(taskSection.getStringList("targets")
+ );
+ if (targets.isEmpty()) {
+ //plugin.getLogger().warning("任务 " + questId + " 的目标 " + taskId + " 没有配置 targets");
+ continue;
+ }
+ QuestTask task = new QuestTask(taskId, taskType, displayName, amount, targets);
+ taskMap.put(taskId, task);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ return taskMap;
+ }
+
+ private QuestCondition loadCondition(ConfigurationSection section) {
+ if (section == null) {
+ return new QuestCondition(0, Collections.emptyList(), Collections.emptyList());
+ }
+ int level = section.getInt("level", 0);
+ List completedQuests = section.getStringList("completed_quests");
+ List worlds = section.getStringList("worlds");
+ return new QuestCondition(level, completedQuests, worlds);
+ }
+
+ private QuestReward loadReward(ConfigurationSection section,String questName) {
+ if (section == null) {
+ return new QuestReward(Collections.emptyList(), Collections.emptyList());
+ }
+ List commands = section.getStringList("commands");
+ List messages = new ArrayList<>(section.getStringList("messages"));
+ for (int i = 0; i < messages.size(); i++) {
+ String line = messages.get(i);
+ if (line.contains("{name}")) {
+ messages.set(i, line.replace("{name}", questName));
+ }
+ }
+ return new QuestReward(commands, messages);
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestManager.java b/src/main/java/com/io/yaohun/questengine/quest/QuestManager.java
new file mode 100644
index 0000000..95cdb21
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestManager.java
@@ -0,0 +1,38 @@
+package com.io.yaohun.questengine.quest;
+
+import com.io.yaohun.questengine.QEMain;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class QuestManager {
+
+ private static Map questMap = new HashMap<>();
+
+ public static void reloadQuestManager(QEMain plugin) {
+ QuestLoader questLoader = new QuestLoader(plugin);
+ questMap = questLoader.loadQuests();
+ }
+
+ public static Quest getQuest(String questId) {
+ return questMap.get(questId);
+ }
+
+ public static boolean hasQuest(String questId) {
+ return questMap.containsKey(questId);
+ }
+
+ public static Collection getAllQuests() {
+ return Collections.unmodifiableCollection(questMap.values());
+ }
+
+ public static Map getQuestMap() {
+ return Collections.unmodifiableMap(questMap);
+ }
+
+ public static int size() {
+ return questMap.size();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestResetManager.java b/src/main/java/com/io/yaohun/questengine/quest/QuestResetManager.java
new file mode 100644
index 0000000..27cdbe4
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestResetManager.java
@@ -0,0 +1,98 @@
+package com.io.yaohun.questengine.quest;
+
+import com.io.yaohun.questengine.QEMain;
+import com.io.yaohun.questengine.player.PlayerQuestManager;
+import com.io.yaohun.questengine.player.storage.PlayerQuestStorageManager;
+import org.bukkit.Bukkit;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.WeekFields;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+public class QuestResetManager {
+
+ private static int lastCheckMinute = -1;
+ private static BukkitTask task;
+
+ public static void start() {
+ stop();
+ int min = 60;
+ int max = 180;
+ int rand = ThreadLocalRandom.current().nextInt(min, max + 1);
+ task = Bukkit.getScheduler().runTaskTimerAsynchronously(
+ QEMain.inst(),
+ QuestResetManager::checkReset,
+ 20L,
+ 20L * rand
+ );
+ }
+
+ public static void stop() {
+ if (task != null) {
+ task.cancel();
+ task = null;
+ }
+ }
+
+ private static void checkReset() {
+ LocalDateTime now = LocalDateTime.now();
+ if (now.getHour() != 0 || now.getMinute() != 0) {
+ return;
+ }
+ int minuteKey = now.getYear() * 100000 + now.getDayOfYear() * 100 + now.getMinute();
+ if (lastCheckMinute == minuteKey) {
+ return;
+ }
+ lastCheckMinute = minuteKey;
+ resetByType(QuestType.DAILY, getDailyKey(now.toLocalDate()));
+ if (now.getDayOfWeek() == DayOfWeek.MONDAY) {
+ resetByType(QuestType.WEEKLY, getWeeklyKey(now.toLocalDate()));
+ }
+ if (now.getDayOfMonth() == 1) {
+ resetByType(QuestType.MONTHLY, getMonthlyKey(now.toLocalDate()));
+ }
+ }
+
+ private static void resetByType(QuestType type, String resetKey) {
+ List questIds = QuestManager.getAllQuests().stream()
+ .filter(quest -> quest.getType() == type)
+ .map(Quest::getId)
+ .collect(Collectors.toList());
+ if (questIds.isEmpty()) {
+ return;
+ }
+ boolean canReset = PlayerQuestStorageManager.getStorage().tryMarkReset(type, resetKey);
+ if (!canReset) {
+ QEMain.inst().getLogger().info(type + " 任务今日/本期已由其他实例刷新,跳过: " + resetKey);
+ return;
+ }
+ PlayerQuestStorageManager.getStorage().deleteQuestsByIds(questIds);
+ Bukkit.getScheduler().runTask(
+ QEMain.inst(),
+ () -> PlayerQuestManager.removeQuestsFromCache(questIds)
+ );
+ QEMain.inst().getLogger().info("[AuQests] 已刷新 " + type + " 任务,resetKey=" + resetKey + ",清理任务数: " + questIds.size());
+ }
+
+ private static String getDailyKey(LocalDate date) {
+ return "DAILY_" + date;
+ }
+
+ private static String getWeeklyKey(LocalDate date) {
+ WeekFields weekFields = WeekFields.of(Locale.CHINA);
+ int week = date.get(weekFields.weekOfWeekBasedYear());
+ int year = date.get(weekFields.weekBasedYear());
+ return "WEEKLY_" + year + "-W" + week;
+ }
+
+ private static String getMonthlyKey(LocalDate date) {
+ return "MONTHLY_" + date.getYear() + "-" + String.format("%02d", date.getMonthValue());
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestReward.java b/src/main/java/com/io/yaohun/questengine/quest/QuestReward.java
new file mode 100644
index 0000000..839576f
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestReward.java
@@ -0,0 +1,25 @@
+package com.io.yaohun.questengine.quest;
+
+import com.io.yaohun.questengine.util.ColorUtil;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class QuestReward {
+
+ private final List commands;
+ private final List messages;
+
+ public QuestReward(List commands, List messages) {
+ this.commands = commands;
+ this.messages = ColorUtil.color(messages);
+ }
+
+ public List getCommands() {
+ return commands;
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestTask.java b/src/main/java/com/io/yaohun/questengine/quest/QuestTask.java
new file mode 100644
index 0000000..fe711c4
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestTask.java
@@ -0,0 +1,46 @@
+package com.io.yaohun.questengine.quest;
+
+import org.bukkit.Bukkit;
+
+import java.util.Set;
+
+public class QuestTask {
+
+ private final String id;
+ private final QuestTaskType type;
+ private final String displayName;
+ private final int amount;
+ private final Set targets;
+
+ public QuestTask(String id, QuestTaskType type, String displayName, int amount, Set targets) {
+ this.id = id;
+ this.type = type;
+ this.displayName = displayName;
+ this.amount = amount;
+ this.targets = targets;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public QuestTaskType getType() {
+ return type;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public Set getTargets() {
+ return targets;
+ }
+
+ public boolean isTarget(String target) {
+ return targets.contains(target);
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestTaskType.java b/src/main/java/com/io/yaohun/questengine/quest/QuestTaskType.java
new file mode 100644
index 0000000..8772f43
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestTaskType.java
@@ -0,0 +1,54 @@
+package com.io.yaohun.questengine.quest;
+
+public enum QuestTaskType {
+
+ BREAK_BLOCK("破坏方块"),
+ PLACE_BLOCK("放置方块"),
+ CRAFT_ITEM("合成物品"),
+ PICKUP_ITEM("拾取物品"),
+ FISH_ITEM("钓鱼获得物品"),
+ FISH_STAR("钓起鱼的星级"),
+ PLACE_BUCKET("监听倒水、倒岩浆、放置粉雪"),
+ FILL_BUCKET("监听装水、装岩浆、装牛奶"),
+ HARVEST_CROP("收获农作物"),
+ EXP_GAIN("经验获取"),
+ EXP_RPG("RPG经验获取"), // 预留接口
+ ONLINE_TODAY("今日在线时长"), // 预留接口
+ ONLINE_WEEK("本周在线时长"), // 预留接口
+ ONLINE_MONTH("本月在线时长"), // 预留接口
+ BREED("繁殖动物"),
+ COLLECT_LOOT("战利品获取"), // 预留接口
+ COLLECT_ENTITY("剪羊毛,取蘑菇煲"),
+ COLLECT_BLOCK("收集蜂蜜"),
+ TAME_ENTITY("驯服实体"),
+ VILLAGER_TRADE_ITEM("村民交易"),
+ VILLAGER_TRADE_JOB("村民交易"),
+ BREW_POTION("酿造药水"),
+ BREW_POTION_COUNT("酿造药水次数"),
+ USE_ANVIL("铁砧使用"),
+ USE_ITEM("使用物品"), // 预留接口
+ ENCHANT_ITEM("附魔物品"),
+ FEED_ANIMAL("喂动物"),
+ FEED_PLAYER("玩家东西"),
+ SMELT_ORE("烧制矿物"),
+ SMELT_FOOD("烧制食物"),
+ INTERACT_NPC("交互NPC"),
+ INTERACT_ENTITY("交互实体"),
+ INTERACT_ITEM("交互手持物品"),
+ SEND_COMMAND("发送命令"),
+ SEND_CHAT("发送聊天"),
+ OPEN_GUI_TITLE("打开指定标题菜单"),
+ OPEN_GUI_TYPE("打开指定Type菜单"),
+ KILL_TYPE("击杀原版怪"),
+ KILL_MYTHIC("击杀MM怪");
+
+ private final String displayName;
+
+ QuestTaskType(String displayName){
+ this.displayName = displayName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/quest/QuestType.java b/src/main/java/com/io/yaohun/questengine/quest/QuestType.java
new file mode 100644
index 0000000..072cfb0
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/quest/QuestType.java
@@ -0,0 +1,19 @@
+package com.io.yaohun.questengine.quest;
+
+public enum QuestType {
+
+ DAILY("每日"),
+ WEEKLY("每周"),
+ MONTHLY("每月"),
+ FIXED("长期");
+
+ private final String displayName;
+
+ QuestType(String displayName){
+ this.displayName = displayName;
+ }
+
+ public String getDisplayName(){
+ return displayName;
+ }
+}
diff --git a/src/main/java/com/io/yaohun/questengine/util/ColorUtil.java b/src/main/java/com/io/yaohun/questengine/util/ColorUtil.java
new file mode 100644
index 0000000..7ad7216
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/util/ColorUtil.java
@@ -0,0 +1,37 @@
+package com.io.yaohun.questengine.util;
+
+import org.bukkit.ChatColor;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class ColorUtil {
+
+ private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}");
+
+ public static String color(String text) {
+ if (text == null) {
+ return "";
+ }
+ Matcher matcher = HEX_PATTERN.matcher(text);
+ while (matcher.find()) {
+ String hexCode = text.substring(matcher.start(), matcher.end());
+ String replaceSharp = hexCode.replace('#', 'x');
+ char[] ch = replaceSharp.toCharArray();
+ StringBuilder builder = new StringBuilder();
+ for (char c : ch) {
+ builder.append("&").append(c);
+ }
+ text = text.replace(hexCode, builder.toString());
+ matcher = HEX_PATTERN.matcher(text);
+ }
+
+ return ChatColor.translateAlternateColorCodes('&', text);
+ }
+
+ public static List color(List list) {
+ return list.stream().map(ColorUtil::color).collect(Collectors.toList());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/io/yaohun/questengine/util/MessageUtil.java b/src/main/java/com/io/yaohun/questengine/util/MessageUtil.java
new file mode 100644
index 0000000..a3229e9
--- /dev/null
+++ b/src/main/java/com/io/yaohun/questengine/util/MessageUtil.java
@@ -0,0 +1,71 @@
+package com.io.yaohun.questengine.util;
+
+import com.io.yaohun.questengine.QEMain;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+
+import java.io.File;
+import java.util.HashMap;
+
+public class MessageUtil {
+
+ private static HashMap messageMap = new HashMap<>();
+
+ public static void init(QEMain plugin) {
+ messageMap.clear();
+ File file = new File(plugin.getDataFolder(),"lang.yml");
+ // 如果没有这个文件就新建一个~
+ if(!file.exists()){
+ plugin.saveResource("lang.yml",false);
+ }
+ FileConfiguration yml = YamlConfiguration.loadConfiguration(file);
+ for (String ymlKey : yml.getKeys(false)){
+ ConfigurationSection section = yml.getConfigurationSection(ymlKey);
+ for (String key : section.getKeys(false)){
+ String message = ColorUtil.color(section.getString(key));
+ messageMap.put(key,message.replace("&","§"));
+ }
+ }
+ }
+
+ public static String getMessage(String key){
+ if(messageMap.containsKey(key)){
+ return messageMap.get(key);
+ }
+ return "§c未找到节点.§7#"+key;
+ }
+
+ public static void broadcast(String msg) {
+ Bukkit.getServer().broadcastMessage( msg.replace("&","§"));
+ }
+
+ public static void sendMessage(CommandSender sender, String message){
+ sender.sendMessage(message);
+ }
+
+ public static void sendMessageKey(CommandSender sender, String key, Sound sound){
+ sender.sendMessage(getMessage(key));
+ if(sender instanceof Player player){
+ player.playSound(player.getLocation(),sound,1,1);
+ }
+ }
+
+ public static void sendMessage(CommandSender sender, String message, Sound sound){
+ sender.sendMessage(message);
+ if(sender instanceof Player player){
+ player.playSound(player.getLocation(),sound,1,1);
+ }
+ }
+
+ public static void sendDescMessage(CommandSender sender, String message, Sound sound){
+ sender.sendMessage("§f[§c消息§f] §a正确用法: §e"+message);
+ if(sender instanceof Player player){
+ player.playSound(player.getLocation(),sound,1,1);
+ }
+ }
+}
diff --git a/src/main/resources/Quests/新手任务.yml b/src/main/resources/Quests/新手任务.yml
new file mode 100644
index 0000000..b6fbcfb
--- /dev/null
+++ b/src/main/resources/Quests/新手任务.yml
@@ -0,0 +1,44 @@
+# 任务代号 玩家接受任务 /aquest js 代号 玩家名
+quest_1:
+ # 任务名
+ display_name: "&a[初级] 新手成长之路①"
+ # 任务描述内容
+ description:
+ - "&7完成基础生存挑战任务"
+ - "&7砍伐指定木材,熟悉基础资源收集"
+ # 任务类型 DAILY、WEEKLY、MONTHLY、FIXED
+ type: DAILY
+ # 消息
+ messages:
+ receive:
+ - "&a你接受了任务:&f新手成长之路①"
+ complete:
+ - "&a任务完成:&f新手成长之路①"
+ - "&7奖励已发放,请继续成长。"
+ conditions:
+ level: 0
+ completed_quests: []
+ worlds: []
+ # 任务列表
+ tasks:
+ # 任务id
+ id_1:
+ # 任务类型暂定: BREAK、PLACE、KILL
+ type: BREAK_BLOCK
+ # 任务名
+ display_name: 砍伐橡木*128
+ # 任务数量
+ amount: 128
+ # 触发目标列表
+ targets:
+ - SPRUCE_LOG
+ - SPRUCE_WOOD
+ - STRIPPED_SPRUCE_LOG
+ - STRIPPED_SPRUCE_WOOD
+ # 完成任务后触发的内容
+ rewards:
+ commands:
+ - "dlevel give %player% 12000"
+ - "points give %player% 5"
+ messages:
+ - "&a{name}任务已完成"
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..c42a5a7
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,36 @@
+Storage:
+ Type: yaml # yaml / sqlite / mysql
+ SQLite:
+ File: PlayerData.db
+ SaveInterval: 10
+ MySQL:
+ Host: localhost
+ Port: 3306
+ Database: quest
+ Username: root
+ Password: root
+ TablePrefix: AuQuest_
+
+ Pool:
+ MaximumPoolSize: 10
+ MinimumIdle: 2
+ ConnectionTimeout: 10000
+ IdleTimeout: 600000
+ MaxLifetime: 1800000
+FoodType:
+ - COOKED_BEEF
+ - COOKED_CHICKEN
+ - COOKED_MUTTON
+ - COOKED_RABBIT
+ - BAKED_POTATO
+OreType:
+ - IRON_INGOT
+ - GOLD_INGOT
+ - COPPER_INGOT
+ - NETHERITE_SCRAP
+ - DIAMOND
+ - EMERALD
+ - REDSTONE
+ - COAL
+ - QUARTZ
+ - LAPIS_LAZULI
\ No newline at end of file
diff --git a/src/main/resources/lang.yml b/src/main/resources/lang.yml
new file mode 100644
index 0000000..3f1ccaa
--- /dev/null
+++ b/src/main/resources/lang.yml
@@ -0,0 +1,4 @@
+Message:
+ condition_not_level: "等级不足,需要将等级提高至 {level}"
+ condition_not_quest: "需要将 {quest} 任务完成后才能接受此任务."
+ condition_not_world: "需要在指定世界才能执行此任务."
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..601da9f
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,14 @@
+name: AuQuestEngine
+main: com.io.yaohun.questengine.QEMain
+version: 1.0.6
+api-version: 1.21
+author: yaohun
+softdepend:
+ - Citizens
+ - MythicMobs
+ - PlaceholderAPI
+ - CustomFishing
+ - CustomCrops
+commands:
+ aquest:
+ aquestadmin: