Files
Sdf1_login/txt/QuestTracker.txt
T

2494 lines
81 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package Sdf1_login;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class QuestTracker {
private final Plugin plugin;
private final PlayerStorage storage;
private final Map<String, String>
playerBiome = new HashMap<>();
private final Map<String, List<QuestFile>>
questCache = new HashMap<>();
private final List<String>
checkInRecords = new ArrayList<>();
private final Map<String, String>
lastCheckInPos = new HashMap<>();
public void recordCheckIn(
String player,
String biomeKey,
String category,
String worldName,
double x, double y, double z) {
String record = player
+ "|" + worldName
+ "|" + biomeKey
+ "|" + category
+ "|" + String.format(
"%.1f", x)
+ "|" + String.format(
"%.1f", y)
+ "|" + String.format(
"%.1f", z)
+ "|" + System.currentTimeMillis();
checkInRecords.add(record);
plugin.getLogger().info(
"[QuestTracker] 打卡记录: "
+ record);
}
public List<String> getCheckInRecords() {
return new ArrayList<>(checkInRecords);
}
public List<String> getPlayerRecords(
String player) {
List<String> list = new ArrayList<>();
for (String r : checkInRecords) {
if (r.startsWith(
player.toLowerCase()
+ "|")) {
list.add(r);
}
}
return list;
}
public QuestTracker(Plugin plugin) {
this.plugin = plugin;
this.storage = new PlayerStorage(
plugin.getDataFolder(),
plugin.getLogger());
loadAllQuests();
}
public void debugPlayer(String player) {
plugin.getLogger().info(
"[QuestTracker] === "
+ player + " 任务数据 ===");
}
public void shutdown() {
if (storage != null) {
storage.close();
}
}
/**
* 玩家移动时记录当前群系
*/
public void onPlayerMove(
String player,
String biomeName) {
playerBiome.put(player, biomeName);
}
/**
* 标记玩家已打卡该群系
*/
// ========== 群系分类映射表 ==========
private static final Map<String, String>
BIOME_CATEGORY = new HashMap<>();
static {
// 海洋群系
BIOME_CATEGORY.put("minecraft:ocean", "海洋");
BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋");
BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋");
// 平原群系
BIOME_CATEGORY.put("minecraft:plains", "平原");
BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原");
BIOME_CATEGORY.put("minecraft:meadow", "平原");
// 森林群系
BIOME_CATEGORY.put("minecraft:forest", "森林");
BIOME_CATEGORY.put("minecraft:flower_forest", "森林");
BIOME_CATEGORY.put("minecraft:birch_forest", "森林");
BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林");
BIOME_CATEGORY.put("minecraft:dark_forest", "森林");
BIOME_CATEGORY.put("minecraft:taiga", "森林");
BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林");
BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林");
BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林");
BIOME_CATEGORY.put("minecraft:cherry_grove", "森林");
BIOME_CATEGORY.put("minecraft:jungle", "森林");
BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林");
BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林");
// 沙漠群系
BIOME_CATEGORY.put("minecraft:desert", "沙漠");
BIOME_CATEGORY.put("minecraft:savanna", "沙漠");
BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠");
BIOME_CATEGORY.put("minecraft:beach", "沙漠");
BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠");
BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠");
// 恶地群系
BIOME_CATEGORY.put("minecraft:badlands", "恶地");
BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地");
BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地");
// 雪原群系
BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原");
BIOME_CATEGORY.put("minecraft:grove", "雪原");
BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原");
BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原");
BIOME_CATEGORY.put("minecraft:frozen_river", "雪原");
BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原");
BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原");
BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原");
// 沙砾群系
BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾");
BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾");
BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾");
// 沼泽群系
BIOME_CATEGORY.put("minecraft:swamp", "沼泽");
BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽");
// 河流
BIOME_CATEGORY.put("minecraft:river", "河流");
// 蘑菇岛
BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛");
// 地下群系
BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下");
BIOME_CATEGORY.put("minecraft:lush_caves", "地下");
BIOME_CATEGORY.put("minecraft:deep_dark", "地下");
// 下界群系
BIOME_CATEGORY.put("minecraft:nether_wastes", "下界");
BIOME_CATEGORY.put("minecraft:crimson_forest", "下界");
BIOME_CATEGORY.put("minecraft:warped_forest", "下界");
BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界");
BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界");
// 末地群系
BIOME_CATEGORY.put("minecraft:the_end", "末地");
BIOME_CATEGORY.put("minecraft:the_void", "末地");
BIOME_CATEGORY.put("minecraft:end_highlands", "末地");
BIOME_CATEGORY.put("minecraft:end_midlands", "末地");
BIOME_CATEGORY.put("minecraft:end_barrens", "末地");
}
// 所有群系大类列表
private static final String[] ALL_CATEGORIES =
{"海洋", "平原", "森林", "沙漠",
"恶地", "雪原", "沙砾", "沼泽",
"河流", "蘑菇岛", "地下",
"下界", "末地"};
// 所有群系分类的条件关键词映射
private static final Map<String, String>
CATEGORY_KEYWORDS = new HashMap<>();
static {
CATEGORY_KEYWORDS.put("海洋", "海洋");
CATEGORY_KEYWORDS.put("平原", "平原");
CATEGORY_KEYWORDS.put("森林", "森林");
CATEGORY_KEYWORDS.put("沙漠", "沙漠");
CATEGORY_KEYWORDS.put("恶地", "恶地");
CATEGORY_KEYWORDS.put("雪原", "雪原");
CATEGORY_KEYWORDS.put("沙砾", "沙砾");
CATEGORY_KEYWORDS.put("沼泽", "沼泽");
CATEGORY_KEYWORDS.put("河流", "河流");
CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛");
CATEGORY_KEYWORDS.put("地下", "地下");
CATEGORY_KEYWORDS.put("下界", "下界");
CATEGORY_KEYWORDS.put("末地", "末地");
// 条件文本中的别名映射
CATEGORY_KEYWORDS.put("地狱", "下界");
CATEGORY_KEYWORDS.put("下届", "下界");
CATEGORY_KEYWORDS.put("主岛", "末地");
}
/**
* 用世界维度名分类(最可靠)
* 世界名含 the_nether → 下界
* 世界名含 the_end → 末地
* 其余 → 主世界
*/
public String mapBiomeCategory(
String rawKey, String worldName) {
// 下界维度优先用世界名
if (worldName != null) {
String w = worldName.toLowerCase();
if (w.contains("nether"))
return "下界";
if (w.contains("end"))
return "末地";
}
// 用群系映射表
if (rawKey != null) {
String cat =
BIOME_CATEGORY.get(
rawKey.toLowerCase());
if (cat != null) return cat;
}
return "主世界";
}
// ========== 解析为区块 ==========
private List<Section> parseIntoSections(
List<String> lines) {
boolean hasHash = false;
boolean hasDash = false;
for (String l : lines) {
String t = l.trim();
if (t.startsWith("#")) hasHash = true;
if (t.startsWith("----")
|| t.startsWith("====")
|| t.startsWith("****"))
hasDash = true;
}
if (hasHash) {
return parseHashSections(lines);
} else if (hasDash) {
List<String> converted =
new ArrayList<>();
for (String l : lines) {
String t = l.trim();
if (t.startsWith("----")
|| t.startsWith("====")
|| t.startsWith("****")) {
String title = t
.replaceAll(
"^[-=*]+\\s*",
"")
.replaceAll(
"\\s*[-=*]+$",
"")
.trim();
if (title.isEmpty())
continue;
converted.add("# " + title);
} else {
converted.add(l);
}
}
return parseHashSections(converted);
}
return new ArrayList<>();
}
private List<Section> parseHashSections(
List<String> lines) {
List<Section> sections =
new ArrayList<>();
String curTitle = null;
List<String> curLines =
new ArrayList<>();
for (String raw : lines) {
String t = raw.trim();
if (t.startsWith("#")) {
if (curTitle != null) {
sections.add(new Section(
curTitle,
new ArrayList<>(
curLines)));
}
curTitle = t.replaceFirst(
"^#+\\s*", "").trim();
curLines = new ArrayList<>();
} else if (t.isEmpty()) {
// skip
} else {
String cond = t.replaceFirst(
"^-\\s+", "").trim();
if (!cond.isEmpty()
&& curTitle != null) {
curLines.add(cond);
}
}
}
if (curTitle != null) {
sections.add(new Section(
curTitle, curLines));
}
return sections;
}
private List<Section> buildFlatSections(
List<String> lines,
String category) {
List<String> conditions =
new ArrayList<>();
for (String raw : lines) {
String t = raw.trim();
if (t.isEmpty()) continue;
String cond = t.replaceFirst(
"^-\\s+", "").trim();
if (!cond.isEmpty()) {
conditions.add(cond);
}
}
if (conditions.isEmpty())
return new ArrayList<>();
List<Section> list =
new ArrayList<>();
list.add(new Section(
"第一阶段", conditions));
return list;
}
// ========== 文件加载入口 ==========
private void loadAllQuests() {
loadCategory("新人任务");
loadCategory("主线任务");
loadCategory("支线任务");
}
private void loadCategory(String name) {
plugin.getLogger().info(
"[QuestTracker] " + name
+ ": 扫描中...");
File folder = new File(
plugin.getDataFolder(),
name);
String path = "plugins/Sdf1_login/"
+ name + "/";
if (!folder.exists()) {
folder.mkdirs();
plugin.getLogger().warning(
"[QuestTracker] 已创建目录: "
+ path);
questCache.put(name,
new ArrayList<>());
return;
}
File[] mdFiles = folder.listFiles(
(d, n) -> n.endsWith(".md"));
if (mdFiles == null
|| mdFiles.length == 0) {
plugin.getLogger().warning(
"[QuestTracker] " + path
+ " 下无 .md 文件");
questCache.put(name,
new ArrayList<>());
return;
}
Arrays.sort(mdFiles);
plugin.getLogger().info(
"[QuestTracker] " + name
+ ": 发现 " + mdFiles.length
+ " 个文件,开始扫描...");
// 合并所有文件的阶段
TreeMap<Integer, QuestFile> merged =
new TreeMap<>();
int fileCount = 0;
for (int fi = 0;
fi < mdFiles.length; fi++) {
File file = mdFiles[fi];
List<String> lines =
readFileLines(file);
if (lines.isEmpty()) {
plugin.getLogger().warning(
"[QuestTracker] "
+ file.getName()
+ ": 空文件,跳过");
continue;
}
List<Section> sections =
parseIntoSections(lines);
if (sections.isEmpty()) {
sections = buildFlatSections(
lines, name);
}
if (sections.isEmpty()) {
plugin.getLogger().warning(
"[QuestTracker] "
+ file.getName()
+ ": 无法识别,跳过");
continue;
}
// 每个文件独立处理阶段号
int defaultNum = fi + 1;
buildSingleFileStages(
sections, name,
merged, defaultNum);
fileCount++;
}
if (merged.isEmpty()) {
plugin.getLogger().warning(
"[QuestTracker] " + name
+ ": 无有效任务数据");
questCache.put(name,
new ArrayList<>());
return;
}
// 日志
for (Map.Entry<Integer, QuestFile> en
: merged.entrySet()) {
int num = en.getKey() >= 1000
? en.getKey() - 999
: en.getKey();
plugin.getLogger().info(
"[QuestTracker] " + name
+ " 阶段" + num
+ " \""
+ en.getValue().displayName
+ "\" → 条件"
+ en.getValue()
.conditions.size()
+ "项, 详情"
+ en.getValue()
.details.size()
+ "项, 奖励"
+ en.getValue()
.rewards.size()
+ "项");
}
List<QuestFile> list =
new ArrayList<>(
merged.values());
questCache.put(name, list);
plugin.getLogger().info(
"[QuestTracker] " + name
+ ": 解析完成 → "
+ list.size() + " 个阶段"
+ " (来自 " + fileCount
+ " 个文件)");
}
private Integer extractStageNumber(
String title) {
if (title == null) return null;
String t = title.toLowerCase().trim();
// 含阶段/stage/关/chapter 关键词
boolean isStage =
t.contains("阶段")
|| t.contains("stage")
|| t.contains("关卡")
|| t.contains("章节")
|| t.contains("chapter");
if (isStage) {
String digits = title
.replaceAll("[^0-9]", "");
if (!digits.isEmpty()) {
try {
return Integer.parseInt(digits);
} catch (NumberFormatException e) {
// fall through
}
}
return parseChineseNumber(title);
}
// 无关键词但以数字开头(如 "1. 挖掘")
String trimmed = t
.replaceAll("^[.、\\s]+", "")
.trim();
if (trimmed.length() > 0
&& Character.isDigit(
trimmed.charAt(0))) {
String num = trimmed
.replaceAll("[^0-9]", "");
if (!num.isEmpty()) {
try {
return Integer.parseInt(num);
} catch (NumberFormatException e) {
// fall through
}
}
}
return null;
}
// ========== 领取记录 ==========
/**
* 标记已领取某阶段奖励
*/
public void markClaimed(
String player, QuestFile qf) {
if (player == null || qf == null)
return;
// 用阶段编号代替显示名,保证稳定
int num = getStageNumber(qf);
String key = "claimed_"
+ qf.fileName + "_" + num;
storage.setCondition(player, key);
}
public boolean hasClaimed(
String player, QuestFile qf) {
if (player == null || qf == null)
return false;
int num = getStageNumber(qf);
String key = "claimed_"
+ qf.fileName + "_" + num;
return toBool(
storage.getCondition(
player, key));
}
private int parseChineseNumber(
String title) {
String[] cn = {"一", "二", "三",
"四", "五", "六", "七",
"八", "九", "十"};
if (title.contains("十")) {
for (int j = 0; j < 9; j++) {
if (title.contains(cn[j])) {
return 10 + j + 1;
}
}
return 10;
}
for (int i = 0; i < 9; i++) {
if (title.contains(cn[i])) {
return i + 1;
}
}
return 0;
}
// ========== md 解析核心 ==========
/**
* 读取文件所有行
*/
private List<String> readFileLines(
File file) {
List<String> lines =
new ArrayList<>();
try {
BufferedReader br =
new BufferedReader(
new FileReader(file));
String line;
while ((line = br.readLine())
!= null) {
lines.add(line);
}
br.close();
} catch (Exception e) {
plugin.getLogger().warning(
"[QuestTracker] 读取 "
+ file.getName()
+ " 失败: "
+ e.getMessage());
}
return lines;
}
/**
* 自动检测格式并解析单个文件
*/
private List<QuestFile> parseMdFile(
File file, String category) {
List<String> lines =
readFileLines(file);
if (lines.isEmpty())
return new ArrayList<>();
// 检测分隔符
boolean hasHash = false;
boolean hasDash = false;
for (String l : lines) {
String t = l.trim();
if (t.startsWith("#"))
hasHash = true;
if (t.startsWith("----")
|| t.startsWith("====")
|| t.startsWith("****"))
hasDash = true;
}
if (hasHash) {
// # 分隔格式
return parseSections(
lines, category);
} else if (hasDash) {
// ---- 分隔格式:转换为 # 后复用
List<String> converted =
new ArrayList<>();
for (String l : lines) {
String t = l.trim();
if (t.startsWith("----")
|| t.startsWith("====")
|| t.startsWith("****")) {
String title = t
.replaceAll(
"^[-=*]+\\s*",
"")
.replaceAll(
"\\s*[-=*]+$",
"")
.trim();
if (title.isEmpty())
title = "separator";
converted.add("# " + title);
} else {
converted.add(l);
}
}
return parseSections(
converted, category);
} else {
// 无分隔符:整文件一个阶段
return parseFlat(lines, category);
}
}
/**
* # 分隔格式 → 区块 → 阶段
*/
private List<QuestFile> parseSections(
List<String> lines,
String category) {
// 拆分为区块
List<Section> sections =
new ArrayList<>();
String curTitle = null;
List<String> curLines =
new ArrayList<>();
for (String raw : lines) {
String t = raw.trim();
// # 开头 → 新区块
if (t.startsWith("#")) {
if (curTitle != null) {
sections.add(
new Section(
curTitle,
new ArrayList<>(
curLines)));
}
curTitle = t.replaceFirst(
"^#+\\s*", "").trim();
curLines = new ArrayList<>();
}
// 空行/分隔线 → 跳过
else if (t.isEmpty()
|| t.startsWith("----")
|| t.startsWith("====")
|| t.startsWith("***")) {
// skip
}
// 普通行 → 区块内容
else {
String cond = t.replaceFirst(
"^-\\s+", "").trim();
if (!cond.isEmpty()) {
curLines.add(cond);
}
}
}
// 保存最后一个区块
if (curTitle != null) {
sections.add(
new Section(
curTitle, curLines));
}
// 直接转为 QuestFile 列表
List<QuestFile> result =
new ArrayList<>();
int num = 1;
for (Section sec : sections) {
QuestFile qf = new QuestFile();
qf.displayName =
extractDisplayName(
sec.title);
qf.fileName = category;
qf.conditions =
new ArrayList<>(sec.lines);
qf.details = new ArrayList<>();
qf.rewards = new ArrayList<>();
result.add(qf);
num++;
}
return result;
}
/**
* 无分隔符:整文件 = 一个阶段
*/
private List<QuestFile> parseFlat(
List<String> lines,
String category) {
List<QuestFile> list =
new ArrayList<>();
QuestFile qf = makeDefaultStage(
category);
for (String raw : lines) {
String t = raw.trim();
if (t.isEmpty()) continue;
String cond = t.replaceFirst(
"^-\\s+", "").trim();
if (!cond.isEmpty()) {
qf.conditions.add(cond);
}
}
if (!qf.conditions.isEmpty()) {
list.add(qf);
}
return list;
}
// ========== Vault 经济奖励发放 ==========
/**
* 发放阶段奖励
* 含经济内容的走 Vault API
*/
public void claimRewards(
Player player,
QuestFile qf) {
if (qf == null
|| qf.rewards == null
|| qf.rewards.isEmpty()) {
player.sendMessage(
"§c无可领取的奖励");
return;
}
net.milkbowl.vault.economy.Economy econ =
((Main) plugin).getEconomy();
int sentCount = 0;
for (String reward : qf.rewards) {
String rt = reward.trim();
if (rt.isEmpty()) continue;
// 检查可选奖励标记
if (hasOptionalMarker(rt)) {
if (!areAllConditionsMet(player.getName(), qf))
player.sendMessage(
"§7[可选奖励] "
+ "需完成所有可选条件: "
+ rt);
continue;
}
// 按 + 分割子项
String[] parts =
rt.split("[+,]");
for (String part : parts) {
part = part.trim();
if (part.isEmpty()) continue;
// 尝试发经济
double amt =
extractEconomyAmount(part);
if (amt > 0 && econ != null) {
double before =
econ.getBalance(player);
econ.depositPlayer(
player, amt);
player.sendMessage(
"§a§l[任务奖励] §f"
+ part
+ " §7(余额: §e"
+ String.format(
"%.2f", before)
+ " §7→ §a"
+ String.format(
"%.2f",
econ.getBalance(
player))
+ "§7)");
// 通知提成系统
if (plugin instanceof Main) {
((Main) plugin)
.getCommission()
.payCommission(
player
.getName(),
amt);
}
sentCount++;
continue;
}
// 尝试发物品
java.util.List<ItemStack> items =
parseRewardItems(part);
if (!items.isEmpty()) {
for (ItemStack is : items) {
player.getInventory()
.addItem(is);
}
player.sendMessage(
"§a§l[任务奖励] §f"
+ part);
sentCount++;
continue;
}
// 非经济非物品 → 仅消息
player.sendMessage(
"§a§l[任务奖励] §f"
+ part);
sentCount++;
}
}
plugin.getLogger().info(
"[QuestTracker] "
+ player.getName()
+ " 领取了 \""
+ qf.displayName
+ "\" 共 " + sentCount
+ " 项奖励");
}
/**
* 检查奖励文本是否含可选标记
*/
private boolean hasOptionalMarker(
String text) {
return text.contains("(可选)")
|| text.contains("(进阶)")
|| text.contains("【可选】")
|| text.contains("【进阶】");
}
/**
* 检查阶段所有条件(含可选)是否全部满足
* 用于决定可选奖励是否发放
*/
private boolean areAllConditionsMet(
String player, QuestFile qf) {
if (qf.conditions == null
|| qf.conditions.isEmpty())
return true;
for (String cond : qf.conditions) {
if (!isConditionMet(player, cond))
return false;
}
return true;
}
/**
* 从奖励文本中提取经济金额
* 支持: "500游戏币" "500" "金币500"
* "$500" "1000-5000"(随机) "1000~5000"
*/
private double extractEconomyAmount(
String text) {
String t = text.replaceAll(" ", "");
// 必须包含经济关键词才算经济奖励
boolean hasEcon =
t.contains("游戏币")
|| t.contains("金币")
|| t.contains("积分")
|| t.contains("块钱")
|| t.contains("元")
|| t.contains("$")
|| t.contains("经济")
|| t.contains("余额")
|| t.contains("发钱");
if (!hasEcon) return 0;
// 范围: "1000-5000" 或 "1000~5000"
Matcher rm = Pattern
.compile("(\\d+)[-~](\\d+)")
.matcher(t);
if (rm.find()) {
double min = Double.parseDouble(
rm.group(1));
double max = Double.parseDouble(
rm.group(2));
return min + Math.random()
* (max - min);
}
// 纯数字(含 $ 前缀)
Matcher nm = Pattern
.compile("\\$?(\\d+(\\.\\d+)?)")
.matcher(t);
if (nm.find()) {
return Double.parseDouble(
nm.group(1));
}
return 0;
}
/**
* 解析奖励物品文本
* "皮革套装*1" → 4件皮革装备
* "木剑*1" → 1把木剑
* "钻石*64" → 64颗钻石
*/
private List<ItemStack> parseRewardItems(
String text) {
List<ItemStack> result =
new ArrayList<>();
// 去掉条件前缀
// 如 "完成注册登录:皮革套装*1"
if (text.contains(":")) {
text = text.substring(
text.indexOf(":") + 1);
}
if (text.contains(":")) {
text = text.substring(
text.indexOf(":") + 1);
}
// 按 + 分割多个物品
String[] parts =
text.split("[+,]");
for (String part : parts) {
part = part.trim();
if (part.isEmpty()) continue;
// 跳过经济文本
if (extractEconomyAmount(part) > 0)
continue;
int amount = 1;
String itemName = part;
// 解析 *N 数量
if (part.contains("*")) {
String[] sp =
part.split("\\*");
itemName = sp[0].trim();
try {
amount = Integer.parseInt(
sp[1].trim());
} catch (Exception e) {
amount = 1;
}
}
List<Material> mats =
matchMaterials(itemName);
for (Material m : mats) {
int give = m.name()
.contains("HELMET")
|| m.name()
.contains("CHESTPLATE")
|| m.name()
.contains("LEGGINGS")
|| m.name()
.contains("BOOTS")
? 1 : amount;
result.add(
new ItemStack(m, give));
}
}
return result;
}
/**
* 中文物品名 → Material 列表
* 套装返回多个部件
*/
private List<Material> matchMaterials(
String name) {
List<Material> list =
new ArrayList<>();
String n = name.toLowerCase().trim();
// 套装
if (n.contains("皮革套装")
|| n.contains("皮套装")) {
list.add(Material.LEATHER_HELMET);
list.add(Material.LEATHER_CHESTPLATE);
list.add(Material.LEATHER_LEGGINGS);
list.add(Material.LEATHER_BOOTS);
return list;
}
if (n.contains("铁套装")
|| n.contains("铁甲")) {
list.add(Material.IRON_HELMET);
list.add(Material.IRON_CHESTPLATE);
list.add(Material.IRON_LEGGINGS);
list.add(Material.IRON_BOOTS);
return list;
}
if (n.contains("钻石套装")
|| n.contains("钻甲")) {
list.add(Material.DIAMOND_HELMET);
list.add(Material.DIAMOND_CHESTPLATE);
list.add(Material.DIAMOND_LEGGINGS);
list.add(Material.DIAMOND_BOOTS);
return list;
}
// 木制工具套装
if (n.contains("木制工具")
|| n.contains("木工具")) {
list.add(Material.WOODEN_SWORD);
list.add(Material.WOODEN_PICKAXE);
list.add(Material.WOODEN_AXE);
list.add(Material.WOODEN_SHOVEL);
return list;
}
if (n.contains("石制工具")
|| n.contains("石工具")) {
list.add(Material.STONE_SWORD);
list.add(Material.STONE_PICKAXE);
list.add(Material.STONE_AXE);
list.add(Material.STONE_SHOVEL);
return list;
}
if (n.contains("铁工具")) {
list.add(Material.IRON_SWORD);
list.add(Material.IRON_PICKAXE);
list.add(Material.IRON_AXE);
list.add(Material.IRON_SHOVEL);
return list;
}
// 单个物品
if (n.contains("木剑"))
list.add(Material.WOODEN_SWORD);
else if (n.contains("木镐"))
list.add(Material.WOODEN_PICKAXE);
else if (n.contains("铁剑"))
list.add(Material.IRON_SWORD);
else if (n.contains("铁镐"))
list.add(Material.IRON_PICKAXE);
else if (n.contains("钻石剑"))
list.add(Material.DIAMOND_SWORD);
else if (n.contains("钻石镐"))
list.add(Material.DIAMOND_PICKAXE);
else if (n.contains("钻石"))
list.add(Material.DIAMOND);
else if (n.contains("铁锭"))
list.add(Material.IRON_INGOT);
else if (n.contains("金锭")
|| n.contains("金块"))
list.add(Material.GOLD_INGOT);
else if (n.contains("煤炭"))
list.add(Material.COAL);
else if (n.contains("木棍"))
list.add(Material.STICK);
else if (n.contains("弓"))
list.add(Material.BOW);
else if (n.contains("箭"))
list.add(Material.ARROW);
else if (n.contains("床"))
list.add(Material.RED_BED);
else {
// 尝试直接匹配 Material 名
try {
list.add(Material.valueOf(
n.toUpperCase()
.replaceAll("[^A-Z_]",
"")));
} catch (Exception e) {
// 未知物品
}
}
return list;
}
private QuestFile getOrCreateStage(
TreeMap<Integer, QuestFile> map,
int key,
String category) {
if (!map.containsKey(key)) {
QuestFile qf = new QuestFile();
qf.displayName = "阶段 " + key;
qf.fileName = category;
qf.conditions = new ArrayList<>();
qf.details = new ArrayList<>();
qf.rewards = new ArrayList<>();
map.put(key, qf);
}
return map.get(key);
}
/**
* 单个文件的区块 → 阶段
* defaultStageNum: 文件无阶段标题时的默认编号
*/
private void buildSingleFileStages(
List<Section> sections,
String category,
TreeMap<Integer, QuestFile> stageMap,
int defaultStageNum) {
Integer curKey = null;
for (Section sec : sections) {
Integer stageNum =
extractStageNumber(sec.title);
if (stageNum != null) {
curKey = stageNum;
} else if (curKey == null) {
curKey = defaultStageNum;
}
QuestFile qf = getOrCreateStage(
stageMap, curKey, category);
String displayName =
extractDisplayName(sec.title);
// 设置显示名
if (!displayName.isEmpty()
&& qf.displayName
.startsWith("阶段 ")) {
qf.displayName = displayName;
}
// 路由
if (stageNum != null
&& sec.lines.isEmpty()) {
if (!displayName.isEmpty()) {
qf.displayName = displayName;
}
}
else if (isRewardTitle(sec.title)) {
// 奖励 → 只进 rewards
qf.rewards.addAll(sec.lines);
}
else if (isDetailsTitle(sec.title)) {
// 详情 → 进 details + conditions
qf.details.addAll(sec.lines);
for (String line : sec.lines) {
if (!qf.conditions
.contains(line)) {
qf.conditions.add(line);
}
}
}
else if (isConditionsTitle(
sec.title)) {
// 条件 → 只进 conditions
qf.conditions.addAll(sec.lines);
}
else {
// 其他 → 条件
qf.conditions.addAll(sec.lines);
}
}
}
/**
* "主线任务-斗志昂扬" → "斗志昂扬"
* "支线-钓鱼" → "钓鱼"
* "阶段 1" → "阶段 1"
* "第一阶段" → "第一阶段"
*/
private String extractDisplayName(
String title) {
if (title == null) return "";
// 按 - 或 — 或 – 分割
String[] parts = title.split(
"\\s*[-—–]\\s*", 2);
if (parts.length >= 2) {
String right = parts[1].trim();
if (!right.isEmpty()) {
return right;
}
}
return title;
}
private DatabaseManager getDb() {
return ((Main) plugin).getDb();
}
private boolean isDetailsTitle(
String title) {
String t = title.toLowerCase();
return t.contains("任务详情")
|| t.contains("详情")
|| t.contains("任务说明")
|| t.contains("任务目标")
|| t.contains("description")
|| t.contains("任务要求");
}
private boolean isConditionsTitle(
String title) {
String t = title.toLowerCase();
return t.contains("解锁条件")
|| t.contains("条件")
|| t.contains("前置条件")
|| t.contains("前置要求")
|| t.contains("解锁")
|| t.contains("condition");
}
private void ensureStage(
TreeMap<Integer, QuestFile> map,
String category,
QuestFile current,
int counter) {
if (current != null) return;
// 用大数字确保排在有编号阶段后面
int key = 1000 + counter;
QuestFile qf = new QuestFile();
qf.displayName = "阶段 " + (counter + 1);
qf.fileName = category;
qf.conditions = new ArrayList<>();
qf.rewards = new ArrayList<>();
map.put(key, qf);
// 更新调用方的引用(通过返回值)
// 这里用 map 操作,current 无法直接更新
// 所以改用返回值方式
}
/**
* 自动将条件中的奖励内容移到 rewards
*/
private void separateRewards(QuestFile qf) {
List<String> remaining =
new ArrayList<>();
for (String line : qf.conditions) {
if (isRewardContent(line)) {
if (!qf.rewards.contains(line)) {
qf.rewards.add(line);
}
} else {
remaining.add(line);
}
}
qf.conditions = remaining;
}
private boolean checkStageDependency(
String player,
String condition) {
Matcher m = Pattern.compile(
"第([一二三四五六七八九十\\d]+)"
+ "阶段")
.matcher(condition);
while (m.find()) {
String numStr = m.group(1);
int stageNum =
parseChineseNumber(numStr);
if (stageNum == 0) {
try {
stageNum =
Integer.parseInt(numStr);
} catch (Exception e) {
return false;
}
}
boolean stageDone = false;
for (List<QuestFile> quests
: questCache.values()) {
for (QuestFile qf : quests) {
Integer qfNum =
extractStageNumber(
qf.displayName);
if (qfNum != null
&& qfNum == stageNum) {
if (isStageComplete(player,
qf.conditions)) {
stageDone = true;
}
break;
}
}
if (stageDone) break;
}
if (!stageDone) return false;
}
return true;
}
/**
* 判断一行内容是否为奖励
*/
private boolean isRewardContent(
String line) {
String l = line.toLowerCase();
// 含货币关键词
if (l.contains("游戏币")
|| l.contains("金币")
|| l.contains("积分")
|| l.contains("钻石")
|| l.contains("发钱")
|| l.contains("发放")
|| l.contains("经验")
|| l.contains("等级")
|| l.contains("经济")
|| l.contains("余额")
|| l.contains("块钱"))
return true;
// 纯数字+单位(如 "500游戏币")
if (l.matches(
".*\\d+\\s*(游戏币|金币|元|积分|"
+ "钻石|块钱|经验|级|经济|余额).*"))
return true;
// $前缀(如 "$500")
if (l.matches(".*\\$\\d+.*"))
return true;
// 范围格式(如 "1000-5000" 或 "1000~5000")
if (l.matches(".*\\d+[-~]\\d+.*")
&& !l.contains("阶段")
&& !l.contains("stage"))
return true;
return false;
}
// ========== 标题分类 ==========
private boolean isStageTitle(
String title) {
String t = title.toLowerCase();
return t.contains("阶段")
|| t.contains("stage")
|| t.contains("第一")
|| t.contains("第二")
|| t.contains("第三")
|| t.contains("第四")
|| t.contains("第五");
}
private boolean isRewardTitle(
String title) {
String t = title.toLowerCase();
return t.contains("奖励")
|| t.contains("奖品")
|| t.contains("报酬")
|| t.contains("reward")
|| t.contains("奖赏")
|| t.contains("礼包内容")
|| t.contains("礼包")
|| t.contains("物品奖励");
}
private QuestFile makeDefaultStage(
String category) {
QuestFile qf = new QuestFile();
qf.displayName = "第一阶段";
qf.fileName = category;
qf.conditions = new ArrayList<>();
qf.rewards = new ArrayList<>();
return qf;
}
private static class Section {
final String title;
final List<String> lines;
Section(String title,
List<String> lines) {
this.title = title;
this.lines = lines;
}
}
// ========== 公开查询 ==========
public List<QuestFile> getQuests(
String category) {
List<QuestFile> list =
questCache.get(category);
return list != null ? list
: new ArrayList<>();
}
public int getCompletedStageCount(
String player,
List<QuestFile> quests) {
int count = 0;
if (quests == null) return 0;
for (QuestFile qf : quests) {
if (isStageComplete(player,
qf.conditions)) {
count++;
}
}
return count;
}
public QuestFile getCurrentStage(
String player,
List<QuestFile> quests) {
if (quests == null) return null;
for (QuestFile qf : quests) {
if (!isStageComplete(
player, qf.conditions)) {
return qf;
}
}
return null;
}
public int getStageNumber(
List<QuestFile> quests,
QuestFile qf) {
if (quests == null || qf == null)
return 0;
return quests.indexOf(qf) + 1;
}
public int getStageNumber(
QuestFile qf) {
if (qf == null) return 0;
List<QuestFile> quests =
getQuests(qf.fileName);
return getStageNumber(quests, qf);
}
public boolean isStageCompleted(
String player,
QuestFile qf) {
if (qf == null) return false;
return isStageComplete(
player, qf.conditions);
}
public List<String> getUnmetConditions(
String player,
List<String> conditions) {
List<String> unmet =
new ArrayList<>();
if (conditions == null) return unmet;
for (String cond : conditions) {
if (isOptional(cond)) continue;
if (!isConditionMet(
player, cond)) {
unmet.add(
getProgress(
player, cond));
}
}
return unmet;
}
public String getProgress(
String player,
String rawCond) {
if (player == null
|| rawCond == null)
return "§7未知";
String key = stripOptional(rawCond);
// 打卡群系进度
if (key.contains("打卡")
&& key.contains("群系")) {
String biome =
extractBiome(key);
if ("ALL".equals(biome)) {
int total = getCounter(
player, "biome_ALL");
return "§7群系打卡: §e"
+ total + "/1";
}
if (!biome.isEmpty()) {
int catCount = getCounter(
player,
"biomecat_" + biome);
return "§7打卡 " + biome
+ "群系: §e"
+ catCount + "/1";
}
int total = getCounter(
player, "biome_ALL");
return "§7群系打卡: §e"
+ total + "/1";
}
// 阶段依赖
if (key.contains("完成")
&& key.contains("阶段")) {
boolean done = checkStageDependency(
player, key);
return done
? "§a✓ " + key
: "§c✗ " + key;
}
// 注册登录(检查主数据库是否已注册)
// 注册登录(检查主数据库是否已注册)
if ("完成注册登录".equals(key)) {
boolean done = getDb() != null
&& getDb().userExists(
player);
return done
? "§a✓ 完成注册登录"
: "§c✗ 完成注册登录";
}
// 绑定邮箱
if (key.contains("绑定Email")
|| key.contains("绑定邮箱")) {
Object val =
getDb().getField(
player, "email");
boolean done = val != null
&& !val.toString().isEmpty();
return done
? "§a✓ 绑定Email"
: "§c✗ 绑定Email";
}
// 在线时间
if (key.contains("在线")
&& key.contains("小时")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
long cur = getCounterLong(
player, "PLAYTIME");
return "§7在线: §e"
+ formatMs(cur) + "/"
+ needed + "小时";
}
// 打招呼/发消息/聊天
if (key.contains("打招呼")
|| key.contains("打个招呼")
|| key.contains("发消息")
|| key.contains("聊天")) {
int cur = getCounter(
player, "CHAT");
return "§7聊天: §e" + cur + "/1";
}
// 破坏/放置方块
if ((key.contains("破坏")
|| key.contains("放置"))
&& key.contains("方块")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int total = getCounter(
player, "MINE:ALL")
+ getCounter(
player, "PLACE:ALL");
return "§7方块操作: §e"
+ total + "/" + needed;
}
// 连续登录
if (key.contains("连续")
&& key.contains("登录")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
Object val =
getDb().getField(
player,
"checkin_streak");
int streak = val instanceof Number
? ((Number) val).intValue()
: 0;
return "§7连续登录: §e"
+ streak + "/" + needed + "天";
}
// 挖矿
if (key.startsWith("MINE:")) {
String[] parts = key.split(":");
if (parts.length >= 3) {
int needed =
parseInt(parts[2]);
int cur = getCounter(
player,
"MINE:" + parts[1]);
return "§7挖 " + parts[1]
+ ": §e" + cur
+ "/" + needed;
}
}
// 击杀生物
if (key.startsWith("KILL:")) {
String[] parts = key.split(":");
if (parts.length >= 3) {
int needed =
parseInt(parts[2]);
int cur = getCounter(
player,
"KILL:" + parts[1]);
return "§7杀 " + parts[1]
+ ": §e" + cur
+ "/" + needed;
}
}
// 击杀玩家
if (key.startsWith("KILL_PLAYER")) {
String[] parts = key.split(":");
int needed = parts.length >= 2
? parseInt(parts[1]) : 1;
int cur = getCounter(
player, "KILL_PLAYER");
return "§7击杀玩家: §e"
+ cur + "/" + needed;
}
// 合成
if (key.startsWith("CRAFT")) {
String[] parts = key.split(":");
int needed = parts.length >= 2
? parseInt(parts[1]) : 1;
int cur = getCounter(
player, "CRAFT");
return "§7合成: §e"
+ cur + "/" + needed;
}
// 钓鱼
if (key.startsWith("FISH")) {
String[] parts = key.split(":");
int needed = parts.length >= 2
? parseInt(parts[1]) : 1;
int cur = getCounter(
player, "FISH");
return "§7钓鱼: §e"
+ cur + "/" + needed;
}
// 在线时长(毫秒)
if (key.startsWith("PLAYTIME")) {
String[] parts = key.split(":");
long needed = parts.length >= 2
? Long.parseLong(parts[1])
: 0;
long cur = getCounterLong(
player, "PLAYTIME");
return "§7在线: §e"
+ formatMs(cur) + "/"
+ formatMs(needed);
}
// 上报村庄
if (key.contains("上报")
&& key.contains("村庄")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int cur = getCounter(
player, "REPORT_VILLAGE");
return "§7上报村庄: §e"
+ cur + "/" + needed;
}
// 探索村庄
if (key.contains("探索")
&& key.contains("村庄")) {
int cur = getCounter(
player, "REPORT_VILLAGE");
return "§7探索村庄: §e"
+ cur + "/1";
}
// 近7天登录
if (key.contains("7天")
|| key.contains("七天")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int streak = getIntField(
player,
"checkin_streak");
if (streak == 0) {
streak = getIntField(
player, "streak");
}
if (streak == 0) {
streak = getIntField(
player,
"total_checkin_days");
}
return "§7近7天登录: §e"
+ streak + "/"
+ needed + "天";
}
// 完成新手任务全部
if (key.contains("新手任务")
&& key.contains("全部")) {
List<QuestFile> quests =
getQuests("新人任务");
int done = 0;
for (QuestFile qf : quests) {
if (isStageComplete(
player,
qf.conditions))
done++;
}
return "§7新手任务: §e"
+ done + "/"
+ quests.size();
}
// 完成主线任务全部
if (key.contains("主线任务")
&& key.contains("全部")) {
List<QuestFile> quests =
getQuests("主线任务");
int done = 0;
for (QuestFile qf : quests) {
if (isStageComplete(
player,
qf.conditions))
done++;
}
return "§7主线任务: §e"
+ done + "/"
+ quests.size();
}
return "§7" + key;
}
private int getIntField(
String player, String field) {
try {
Object val = getDb().getField(
player, field);
if (val instanceof Number) {
return ((Number) val).intValue();
}
if (val != null) {
return Integer.parseInt(
val.toString().trim());
}
} catch (Exception e) {
// 忽略
}
return 0;
}
public List<String> checkInBiome(
String player,
String rawBiomeKey,
String worldName,
double x, double y, double z) {
List<String> matched =
new ArrayList<>();
// 32格去重
String posKey =
player + "_lastPos";
String curPos = worldName
+ "|" + String.format(
"%.0f", x / 32)
+ "|" + String.format(
"%.0f", y / 32)
+ "|" + String.format(
"%.0f", z / 32);
String lastPos =
lastCheckInPos.get(posKey);
if (curPos.equals(lastPos)) {
plugin.getLogger().info(
"[QuestTracker] 打卡忽略: "
+ player
+ " 32格内重复");
return matched;
}
lastCheckInPos.put(posKey, curPos);
String category =
mapBiomeCategory(
rawBiomeKey, worldName);
// 记录总打卡
int allCur = getCounter(
player, "biome_ALL");
setCounter(player, "biome_ALL",
allCur + 1);
// 记录分类打卡
if (!category.isEmpty()
&& !"主世界"
.equals(category)) {
String catKey =
"biomecat_" + category;
int catCur = getCounter(
player, catKey);
setCounter(player, catKey,
catCur + 1);
}
// 记录具体群系
String bioKey =
"biome_" + rawBiomeKey;
int bioCur = getCounter(
player, bioKey);
setCounter(player, bioKey,
bioCur + 1);
// 检查所有任务条件是否匹配
List<String> categories =
Arrays.asList(
"新人任务",
"主线任务",
"支线任务");
for (String cat : categories) {
List<QuestFile> quests =
getQuests(cat);
for (QuestFile qf : quests) {
if (isStageCompleted(
player, qf))
continue;
for (String cond
: qf.conditions) {
if (isOptional(cond))
continue;
String condKey =
stripOptional(cond);
if (!condKey
.contains("打卡")
|| !condKey
.contains("群系"))
continue;
String target =
extractBiome(condKey);
boolean hit = false;
if ("ALL".equals(target)
|| "风景点"
.equals(target)) {
hit = true;
} else if (!target.isEmpty()
&& target.equals(
category)) {
hit = true;
}
if (hit && !matched
.contains(cond)) {
matched.add(cond);
}
}
}
}
return matched;
}
/**
* 从条件文本中提取目标群系分类
* "打卡上报地狱任意群系" → "下界"
* "打卡任意海洋群系" → "海洋"
* "打卡任意群系" → "ALL"
* "打卡任意风景点" → ""
*/
private String extractBiome(String text) {
if (text == null) return "";
// 逐个匹配分类关键词
for (Map.Entry<String, String> en
: CATEGORY_KEYWORDS.entrySet()) {
if (text.contains(en.getKey())) {
return en.getValue();
}
}
if (text.contains("风景点")
|| text.contains("风景")) {
return "ALL";
}
// 有"群系"但无具体分类 → ALL
if (text.contains("群系")) {
return "ALL";
}
return "";
}
// ========== 标记条件 ==========
public void markConditionComplete(
String player,
String conditionName) {
if (player == null
|| conditionName == null)
return;
storage.setCondition(
player, conditionName);
}
// ========== 事件回调 ==========
public void onBlockMined(
String player, String material) {
String key = "MINE:" + material;
setCounter(player, key,
getCounter(player, key) + 1);
// 总计数
setCounter(player, "MINE:ALL",
getCounter(player, "MINE:ALL")
+ 1);
}
public boolean onReportVillage(
String player,
String worldName,
double x, double y, double z) {
String dbKey = "village_lastPos";
String curPos = worldName
+ "|" + String.format(
"%.0f", x / 32)
+ "|" + String.format(
"%.0f", y / 32)
+ "|" + String.format(
"%.0f", z / 32);
Object lastObj =
storage.getCondition(
player, dbKey);
String lastPos = lastObj != null
? lastObj.toString() : "";
if (curPos.equals(lastPos)) {
return false;
}
storage.setConditionRaw(
player, dbKey, curPos);
String key = "REPORT_VILLAGE";
int cur = getCounter(player, key);
setCounter(player, key, cur + 1);
return true;
}
public void onMobKill(
String player, String mobType) {
String key = "KILL:" + mobType;
int cur = getCounter(player, key);
setCounter(player, key, cur + 1);
}
public void onPlayerKill(
String player) {
String key = "KILL_PLAYER";
int cur = getCounter(player, key);
setCounter(player, key, cur + 1);
}
public void onItemCrafted(
String player) {
String key = "CRAFT";
int cur = getCounter(player, key);
setCounter(player, key, cur + 1);
}
public void onFishCaught(
String player) {
String key = "FISH";
int cur = getCounter(player, key);
setCounter(player, key, cur + 1);
}
public void onPlayTime(
String player, long ms) {
String key = "PLAYTIME";
long cur = getCounterLong(
player, key);
setCounter(player, key, cur + ms);
}
// ========== 内部:条件判定 ==========
private boolean isStageComplete(
String player,
List<String> conditions) {
if (conditions == null
|| conditions.isEmpty())
return true;
for (String cond : conditions) {
if (isOptional(cond)) continue;
if (!isConditionMet(
player, cond))
return false;
}
return true;
}
public boolean isConditionMet(
String player,
String rawCond) {
if (player == null
|| rawCond == null)
return false;
String key = stripOptional(rawCond);
// 打卡群系
if (key.contains("打卡")
&& (key.contains("群系")
|| key.contains("风景点"))) {
String biome =
extractBiome(key);
if ("ALL".equals(biome)) {
// 打卡任意群系
int total = getCounter(
player, "biome_ALL");
return total >= 1;
}
if (!biome.isEmpty()) {
// 打卡特定分类群系
int catCount = getCounter(
player,
"biomecat_" + biome);
}
// 无特定要求
return getCounter(
player, "biome_ALL") >= 1;
}
// 阶段依赖
if (key.contains("完成")
&& key.contains("阶段")) {
return checkStageDependency(
player, key);
}
// 注册登录
if ("完成注册登录".equals(key)) {
return getDb() != null
&& getDb().userExists(
player);
}
// 绑定邮箱
if (key.contains("绑定Email")
|| key.contains("绑定邮箱")) {
Object val =
getDb().getField(
player, "email");
return val != null
&& !val.toString().isEmpty();
}
// 在线时间
if (key.contains("在线")
&& key.contains("小时")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
long cur = getCounterLong(
player, "PLAYTIME");
return cur >= needed * 3600000L;
}
// 打招呼/发消息/聊天
if (key.contains("打招呼")
|| key.contains("打个招呼")
|| key.contains("发消息")
|| key.contains("聊天")) {
return getCounter(
player, "CHAT") >= 1;
}
// 破坏/放置方块
if ((key.contains("破坏")
|| key.contains("放置"))
&& key.contains("方块")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int total = getCounter(
player, "MINE:ALL")
+ getCounter(
player, "PLACE:ALL");
return total >= needed;
}
// 连续登录
if (key.contains("连续")
&& key.contains("登录")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
Object val =
getDb().getField(
player,
"checkin_streak");
int streak = val instanceof Number
? ((Number) val).intValue()
: 0;
return streak >= needed;
}
// 挖矿
if (key.startsWith("MINE:")) {
String[] p = key.split(":");
if (p.length >= 3) {
return getCounter(player,
"MINE:" + p[1])
>= parseInt(p[2]);
}
}
// 击杀生物
if (key.startsWith("KILL:")) {
String[] p = key.split(":");
if (p.length >= 3) {
return getCounter(player,
"KILL:" + p[1])
>= parseInt(p[2]);
}
}
// 击杀玩家
if (key.startsWith("KILL_PLAYER")) {
String[] p = key.split(":");
int n = p.length >= 2
? parseInt(p[1]) : 1;
return getCounter(player,
"KILL_PLAYER") >= n;
}
// 合成
if (key.startsWith("CRAFT")) {
String[] p = key.split(":");
int n = p.length >= 2
? parseInt(p[1]) : 1;
return getCounter(
player, "CRAFT") >= n;
}
// 钓鱼
if (key.startsWith("FISH")) {
String[] p = key.split(":");
int n = p.length >= 2
? parseInt(p[1]) : 1;
return getCounter(
player, "FISH") >= n;
}
// 在线时长
if (key.startsWith("PLAYTIME")) {
String[] p = key.split(":");
long n = p.length >= 2
? Long.parseLong(p[1]) : 0;
return getCounterLong(player,
"PLAYTIME") >= n;
}
// 上报村庄
if (key.contains("上报")
&& key.contains("村庄")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int cur = getCounter(
player, "REPORT_VILLAGE");
return cur >= needed;
}
// 探索村庄
if (key.contains("探索")
&& key.contains("村庄")) {
return getCounter(
player, "REPORT_VILLAGE")
>= 1;
}
// 近7天登录记录
if (key.contains("7天")
|| key.contains("七天")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
// 诊断:读取数据库所有字段
try {
var user = getDb().getUser(
player);
if (user != null) {
plugin.getLogger().info(
"[QuestTracker] "
+ "登录诊断 "
+ player + ": "
+ user.toString());
} else {
plugin.getLogger().info(
"[QuestTracker] "
+ "登录诊断: "
+ player
+ " getUser返回null");
}
} catch (Exception e) {
plugin.getLogger().info(
"[QuestTracker] "
+ "登录诊断异常: "
+ e.getMessage());
}
int streak = getIntField(
player,
"checkin_streak");
if (streak == 0) {
streak = getIntField(
player, "streak");
}
if (streak == 0) {
streak = getIntField(
player,
"total_checkin_days");
}
plugin.getLogger().info(
"[QuestTracker] 登录检测: "
+ player
+ " 需要=" + needed
+ " 实际=" + streak);
return streak >= needed;
}
// 完成新手任务全部内容
if (key.contains("新手任务")
&& key.contains("全部")) {
List<QuestFile> quests =
getQuests("新人任务");
for (QuestFile qf : quests) {
if (!isStageComplete(
player,
qf.conditions))
return false;
}
return true;
}
// 完成主线任务全部内容
if (key.contains("主线任务")
&& key.contains("全部")) {
List<QuestFile> quests =
getQuests("主线任务");
for (QuestFile qf : quests) {
if (!isStageComplete(
player,
qf.conditions))
return false;
}
return true;
}
return false;
}
public void debugLogin(String player) {
try {
var user = getDb().getUser(player);
if (user != null) {
plugin.getLogger().info(
"[QuestTracker] "
+ "=== " + player
+ " 数据库全部字段 ===");
for (var entry
: user.entrySet()) {
plugin.getLogger().info(
"[QuestTracker] "
+ entry.getKey()
+ " = "
+ entry.getValue()
+ " ("
+ (entry.getValue()
!= null
? entry.getValue()
.getClass()
.getSimpleName()
: "null")
+ ")");
}
} else {
plugin.getLogger().info(
"[QuestTracker] "
+ "getUser返回null: "
+ player);
}
} catch (Exception e) {
plugin.getLogger().info(
"[QuestTracker] "
+ "debug异常: "
+ e.getMessage());
}
}
// ========== 内部:计数器 ==========
private int getCounter(
String player, String key) {
Object val = storage.getCondition(
player, "cnt_" + key);
if (val instanceof Number) {
return ((Number) val).intValue();
}
return 0;
}
private long getCounterLong(
String player, String key) {
Object val = storage.getCondition(
player, "cnt_" + key);
if (val instanceof Number) {
return ((Number) val).longValue();
}
return 0;
}
private void setCounter(
String player, String key,
int value) {
storage.setConditionRaw(
player, "cnt_" + key,
String.valueOf(value));
}
private void setCounter(
String player, String key,
long value) {
storage.setConditionRaw(
player, "cnt_" + key,
String.valueOf(value));
}
// ========== 工具 ==========
private boolean toBool(Object val) {
if (val == null) return false;
if (val instanceof Boolean)
return (Boolean) val;
if (val instanceof Number)
return ((Number) val)
.intValue() != 0;
String s = String.valueOf(val);
return "1".equals(s)
|| "true"
.equalsIgnoreCase(s);
}
public boolean isOptional(String cond) {
if (cond == null) return false;
String c = cond;
return c.contains("(进阶)")
|| c.contains("(可选)")
|| c.contains(",可选)")
|| c.contains(",可选)")
|| c.contains("【可选】")
|| c.contains("【进阶】");
}
private String stripOptional(String cond) {
if (cond == null) return "";
return cond.replace("(进阶)", "")
.replace("(可选)", "")
.replace(",可选)", "")
.replace(",可选)", "")
.replace("【可选】", "")
.replace("【进阶】", "")
.trim();
}
private int parseInt(String s) {
try {
return Integer.parseInt(s.trim());
} catch (Exception e) {
return 0;
}
}
private Object getDbField(
String player, String field) {
Main main = (Main) plugin;
return main.getDb().getField(
player, field);
}
/**
* 从文本中提取第一个数字
* "在线时间≥1小时" → 1
* "破坏/放置方块100个" → 100
*/
private int parseNumberFromText(
String text) {
java.util.regex.Matcher m =
java.util.regex.Pattern
.compile("(\\d+)")
.matcher(text);
if (m.find()) {
try {
return Integer.parseInt(
m.group(1));
} catch (Exception e) {
return 0;
}
}
return 0;
}
public void onBlockPlaced(
String player, String material) {
String key = "PLACE:" + material;
setCounter(player, key,
getCounter(player, key) + 1);
setCounter(player, "PLACE:ALL",
getCounter(player, "PLACE:ALL")
+ 1);
}
public void onPlayerChat(
String player) {
String key = "CHAT";
setCounter(player, key,
getCounter(player, key) + 1);
}
private String formatMs(long ms) {
long sec = ms / 1000;
long min = sec / 60;
long hr = min / 60;
if (hr > 0)
return hr + "小时"
+ (min % 60) + "分";
if (min > 0)
return min + "分"
+ (sec % 60) + "秒";
return sec + "秒";
}
// ========== 数据结构 ==========
public static class QuestFile {
public String displayName = "";
public String fileName = "";
public List<String> details =
new ArrayList<>();
public List<String> rewards =
new ArrayList<>();
public List<String> conditions =
new ArrayList<>();
public boolean hasConditions() {
return conditions != null
&& !conditions.isEmpty();
}
}
}