修复签到功能任意参数均视为成功的问题,加入签到给经济功能

This commit is contained in:
ypsdf1
2026-05-18 15:29:22 +08:00
parent 302328bc8e
commit ea93ccc087
38 changed files with 2829 additions and 16441 deletions
+2 -1
View File
@@ -42,4 +42,5 @@ build/
txt/
*.xlsx
*.patch
新建*.txt
新建*.txt
群戏归类.txt
@@ -37,6 +37,16 @@ public class ChatFilterManager {
private boolean notifyAdmin = true;
private boolean notifyAll = false;
private int muteDuration = 300;
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean v) {
enabled = v;
}
private static final Pattern URL_PATTERN =
Pattern.compile(
@@ -106,6 +116,7 @@ public class ChatFilterManager {
if (line.startsWith("\uFEFF"))
line = line.substring(1);
String t = line.trim();
if (t.isEmpty()
|| t.startsWith("#"))
continue;
@@ -128,6 +139,12 @@ public class ChatFilterManager {
String[] kv = t.split(":", 2);
String k = kv[0].trim();
String v = kv[1].trim();
if (equals("启用过滤")
|| equals("enabled")) {
enabled = parseBool(v);
continue;
}
if (k.equals("通知管理员")) {
notifyAdmin = parseBool(v);
continue;
@@ -238,6 +255,7 @@ public class ChatFilterManager {
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# Sdf1_chat 聊天过滤配置");
pw.println("启用过滤: " + enabled);
pw.println("通知管理员: true");
pw.println("全服通报: false");
pw.println("禁言时长: 300");
+28 -13
View File
@@ -36,7 +36,8 @@ public class ChatInputManager {
TICKET_ADMIN_TYPE,
TICKET_ADMIN_TITLE,
TICKET_ADMIN_DESC,
TICKET_ADMIN_PROVIDER
TICKET_ADMIN_PROVIDER,
BACK_CHECK_ECONOMY_CONFIRM,
}
public static class PlayerState {
@@ -63,6 +64,11 @@ public class ChatInputManager {
public void reset(Player p) {
states.remove(p.getName());
}
public boolean isInFlow(Player p) {
PlayerState state = states.get(p.getName());
return state != null
&& state.type != InputType.NONE;
}
public void handleInput(Player p, String msg,
Main mainPlugin) {
@@ -542,24 +548,33 @@ public class ChatInputManager {
}
case BACK_CHECKIN: {
int cost = 20;
if (!mainPlugin.getDb()
.deductPoints(
p.getName(), cost)) {
p.sendMessage(
"§c积分不足,补签需 "
+ cost + " 积分");
reset(p);
String result =
mainPlugin.getCheckIn()
.backCheckIn(p, msg);
if (result == null) {
// 等待选择(NEED_CONFIRM)
return;
}
p.sendMessage(
"§a补签成功!扣除 "
+ cost + " 积分。"
+ "日期: " + msg);
if (result.startsWith(
"NEED_CONFIRM:")) {
String[] parts =
result.split(":");
// 存储到Main的Map
mainPlugin.pendingBackCheck.put(
p.getName(),
parts[1] + ":" + parts[2]);
return;
}
if (result.startsWith("§c")) {
p.sendMessage(result);
} else {
p.sendMessage(result);
}
reset(p);
return;
}
case INVITE_INPUT_CODE: {
String code = msg.trim();
String myName = p.getName();
+286 -114
View File
@@ -1,114 +1,286 @@
package Sdf1_login;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CheckInManager {
private final Main plugin;
public CheckInManager(Main plugin) {
this.plugin = plugin;
}
public boolean isCheckedInToday(String name) {
String last = (String) plugin.getDb()
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
return today.equals(last);
}
public String checkIn(Player p) {
String name = p.getName();
if (isCheckedInToday(name)) {
return plugin.getConfig2()
.msg("checkin_already");
}
Object streakObj = plugin.getDb()
.getField(name, "checkin_streak");
int streak = streakObj != null
? ((Number) streakObj).intValue() : 0;
String lastDate = (String) plugin.getDb()
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
if (lastDate != null && !lastDate.isEmpty()) {
try {
java.util.Date lastD = new SimpleDateFormat(
"yyyy-MM-dd").parse(lastDate);
java.util.Date todayD = new SimpleDateFormat(
"yyyy-MM-dd").parse(today);
long diff = todayD.getTime() - lastD.getTime();
if (diff > 86400000L * 2) {
streak = 0;
} else if (diff > 86400000L) {
streak++;
}
} catch (Exception ignored) {
}
}
if (streak == 0) streak = 1;
else streak++;
double multi = 1.0;
if (streak >= 21) multi = 1.5;
else if (streak >= 14) multi = 1.3;
else if (streak >= 7) multi = 1.1;
int points = (int) (1 * multi);
plugin.getDb().addPoints(name, points);
plugin.getDb().setField(
name, "checkin_streak", streak);
plugin.getDb().setField(
name, "last_checkin_date", today);
Object totalObj = plugin.getDb()
.getField(name, "total_checkin_days");
int total = totalObj != null
? ((Number) totalObj).intValue() : 0;
plugin.getDb().setField(
name, "total_checkin_days", total + 1);
return plugin.getConfig2()
.msg("checkin_success")
.replace("{points}",
String.valueOf(points))
.replace("{streak}",
String.valueOf(streak))
.replace("{multi}",
String.valueOf(multi));
}
public String backCheckIn(Player p, String dateStr) {
String name = p.getName();
if (!plugin.getDb().deductPoints(name, 10)) {
return plugin.getConfig2()
.msg("checkin_already");
}
try {
java.util.Date target = new SimpleDateFormat(
"yyyy-MM-dd").parse(dateStr);
java.util.Date now = new SimpleDateFormat(
"yyyy-MM-dd").parse(
new SimpleDateFormat("yyyy-MM-dd")
.format(new Date()));
long diff = now.getTime() - target.getTime();
if (diff < 0 || diff > 3 * 86400000L) {
plugin.getDb().addPoints(name, 10);
return plugin.getConfig2()
.msg("checkin_already");
}
plugin.getDb().addPoints(name, 1);
return "§a补签成功!日期: " + dateStr;
} catch (Exception e) {
plugin.getDb().addPoints(name, 10);
return "§c日期格式错误,请用 yyyy-MM-dd";
}
}
}
package Sdf1_login;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class CheckInManager {
private final Main plugin;
public CheckInManager(Main plugin) {
this.plugin = plugin;
}
// ===== 今日是否已签到 =====
public boolean isCheckedInToday(
String name) {
String last = (String) plugin.getDb()
.getField(name,
"last_checkin_date");
String today = new SimpleDateFormat(
"yyyy-MM-dd")
.format(new Date());
return today.equals(last);
}
// ===== 签到 =====
public String checkIn(Player p) {
String name = p.getName();
if (isCheckedInToday(name)) {
return plugin.getConfig2()
.msg("checkin_already");
}
Object streakObj = plugin.getDb()
.getField(name,
"checkin_streak");
int streak = streakObj != null
? ((Number) streakObj)
.intValue() : 0;
String lastDate = (String) plugin
.getDb().getField(name,
"last_checkin_date");
String today = new SimpleDateFormat(
"yyyy-MM-dd")
.format(new Date());
if (lastDate != null
&& !lastDate.isEmpty()) {
try {
java.util.Date lastD =
new SimpleDateFormat(
"yyyy-MM-dd")
.parse(lastDate);
java.util.Date todayD =
new SimpleDateFormat(
"yyyy-MM-dd")
.parse(today);
long diff = todayD.getTime()
- lastD.getTime();
if (diff > 86400000L * 2) {
streak = 0;
} else if (diff
> 86400000L) {
streak++;
}
} catch (Exception ignored) {
}
}
if (streak == 0) streak = 1;
else streak++;
double multi = 1.0;
if (streak >= 21) multi = 1.5;
else if (streak >= 14) multi = 1.3;
else if (streak >= 7) multi = 1.1;
int basePoints = plugin.getConfig2()
.checkinPoints;
int points =
(int) (basePoints * multi);
plugin.getDb().addPoints(
name, points);
plugin.getDb().setField(
name, "checkin_streak",
streak);
plugin.getDb().setField(
name,
"last_checkin_date", today);
Object totalObj = plugin.getDb()
.getField(name,
"total_checkin_days");
int total = totalObj != null
? ((Number) totalObj)
.intValue() : 0;
plugin.getDb().setField(
name,
"total_checkin_days",
total + 1);
return plugin.getConfig2()
.msg("checkin_success")
.replace("{points}",
String.valueOf(points))
.replace("{streak}",
String.valueOf(streak))
.replace("{multi}",
String.valueOf(multi));
}
// ===== 获取连续天数 =====
public int getStreak(String name) {
Object val = plugin.getDb()
.getField(name,
"checkin_streak");
if (val instanceof Number) {
return ((Number) val)
.intValue();
}
return 0;
}
// ===== 补签入口 =====
public String backCheckIn(
Player p, String dateStr) {
String name = p.getName();
String normalized =
normalizeDate(dateStr);
// 严格日期验证
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy-MM-dd");
sdf.setLenient(false);
java.util.Date target;
try {
target = sdf.parse(normalized);
} catch (Exception ex) {
return "§c无效日期: " + dateStr;
}
String reformat =
sdf.format(target);
if (!reformat.equals(normalized)) {
return "§c无效日期: " + dateStr;
}
String today = sdf.format(new Date());
if (normalized.compareTo(today) >= 0) {
return "§c今天不需要补签";
}
String threeDaysAgo = getPastDate(3);
if (normalized.compareTo(
threeDaysAgo) < 0) {
return "§c只能补签3天内"
+ "(最多到 "
+ threeDaysAgo + ")";
}
Object val = plugin.getDb()
.getField(name,
"backcheck_dates");
String existing = val != null
? val.toString() : "";
boolean alreadyBacked = false;
if (!existing.isEmpty()) {
for (String d
: existing.split(",")) {
if (normalizeDate(d.trim())
.equals(normalized)) {
alreadyBacked = true;
break;
}
}
}
if (alreadyBacked) {
return "§c该日期已补签: "
+ normalized;
}
Object normalCheck =
plugin.getDb().getField(
name,
"last_checkin_date");
String lastCheckin = normalCheck
!= null
? normalCheck.toString()
: "";
if (lastCheckin.equals(normalized)) {
return "§c该日期已签到: "
+ normalized;
}
if (!plugin.getDb()
.deductPoints(name, 10)) {
return "§c积分不足,补签需 10 积分";
}
net.milkbowl.vault.economy
.Economy econ =
plugin.getEconomy();
if (econ != null) {
double bal = econ.getBalance(p);
if (bal < 10.0) {
plugin.getDb()
.addPoints(name, 10);
int mult = plugin.getConfig2()
.backCheckPointMultiplier;
int needed = 10 * mult;
p.sendMessage("§e§l[补签] "
+ "§f经济不足($"
+ String.format(
"%.2f", bal)
+ " < $10)");
p.sendMessage("§7是否用 §a§l"
+ needed + "积分 "
+ "§7代替 10经济?");
p.sendMessage("§7输入 §a1 "
+ "§7同意,"
+ "§c其他 §7取消");
return "NEED_CONFIRM:"
+ normalized
+ ":" + needed;
}
econ.withdrawPlayer(p, 10);
}
doBackCheckIn(p, normalized);
return "§a补签成功!日期: "
+ normalized
+ " §7(扣除10积分+10经济)";
}
// ===== 执行补签 =====
public void doBackCheckIn(
Player p, String dateStr) {
String name = p.getName();
Object val = plugin.getDb()
.getField(name,
"backcheck_dates");
String existing = val != null
? val.toString() : "";
String updated = existing.isEmpty()
? dateStr
: existing + "," + dateStr;
plugin.getDb().setField(name,
"backcheck_dates", updated);
int streak = getStreak(name);
plugin.getDb().setField(name,
"checkin_streak",
streak + 1);
}
// ===== 标准化日期 =====
private String normalizeDate(
String input) {
String s = input.trim()
.replace("/", "-");
if (s.matches("\\d{8}")) {
return s.substring(0, 4) + "-"
+ s.substring(4, 6) + "-"
+ s.substring(6, 8);
}
return s;
}
// ===== 获取N天前日期 =====
private String getPastDate(
int daysAgo) {
Calendar cal =
Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH,
-daysAgo);
return new SimpleDateFormat(
"yyyy-MM-dd")
.format(cal.getTime());
}
}
+272 -219
View File
@@ -4,14 +4,15 @@ import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static java.lang.Double.parseDouble;
public class ConfigManager {
private final File dataFolder;
private final Map<String, String> messages = new LinkedHashMap<>();
private final Map<String, String> smtpSettings = new LinkedHashMap<>();
private final Map<String, String> messages =
new LinkedHashMap<>();
private final Map<String, String> smtpSettings =
new LinkedHashMap<>();
// ===== 公共字段 =====
public String adminTag = "admin";
public String adminPassword = "qweasd";
public int maxAccountsPerIP = 3;
@@ -27,9 +28,26 @@ public class ConfigManager {
public String smtpConfiguringPlayer = "";
public String broadcastServerUrl = "";
public int httpPort = 8080;
public String garbageTag= "admin";
public String garbageTag = "admin";
// 签到经济奖励
public double checkinRewardFixed = 0;
public double checkinRewardMin = 0;
public double checkinRewardMax = 0;
public boolean checkinGivePoints = true;
public int checkinPoints = 10;
public String checkinRewardType = "none";
public int backCheckPointMultiplier = 5;
// 提成/工单
public double providerPointsPerScore = 2.0;
public int baseEconomyReward = 100;
public double providerEconomyPerScore = 50.0;
public int requesterPointsReward = 10;
// 垃圾站
public boolean garbageEnabled = true;
public int garbageInterval = 300;
public int garbageMaxRounds = 1;
public ConfigManager(File dataFolder) {
this.dataFolder = dataFolder;
@@ -39,232 +57,132 @@ public class ConfigManager {
public void loadMessages() {
messages.clear();
File file = new File(dataFolder, "消息.txt");
if (!file.exists()) createDefaultMessages(file);
File file =
new File(dataFolder, "消息.txt");
if (!file.exists())
createDefaultMessages(file);
loadMap(file, messages);
}
public String msg(String key) {
String val = messages.getOrDefault(key, key);
String val =
messages.getOrDefault(key, key);
val = val.replace("<b>", "&l");
val = val.replace("</b>", "&r");
val = val.replace("<br>", "\n");
val = val.replace("\\n", "\n");
// &颜色码 转换为 §颜色码
val = val.replaceAll(
"&([0-9a-fk-orA-FK-OR])", "§$1");
return val;
}
private void createDefaultMessages(File f) {
List<String> L = new ArrayList<>();
L.add("# ==========================================");
L.add("# Sdf1_login 消息配置文件");
L.add("# ==========================================");
L.add("# 颜色代码使用 § 符号");
L.add("# §0黑色 §1深蓝 §2深绿 §3深青");
L.add("# §4深红 §5深紫 §6金色 §7灰色");
L.add("# §8深灰 §9蓝色 §a绿色 §b浅蓝");
L.add("# §c红色 §d粉色 §e黄色 §f白色");
L.add("# §l粗体 §n下划线 §o斜体 §r重置");
L.add("#");
L.add("# 占位符用 {xxx} 表示,运行时自动替换");
L.add("# 支持 <b>粗体</b> 标签");
L.add("# 支持 \\n 或 <br> 换行");
L.add("# 修改后执行 /sdf1_login reload 重载");
L.add("# ==========================================");
L.add("# ===== Sdf1_login 消息配置 =====");
L.add("# 占位符用 {xxx} 表示");
L.add("# 修改后执行 /sdf1_login reload");
L.add("");
// --- 登录/注册 ---
L.add("# ---- 登录注册相关 ----");
L.add("# login_timeout: 登录超时被踢出时的提示");
L.add("# ---- 登录注册 ----");
L.add("login_timeout=§c登录超时,请重新登录");
L.add("# not_registered: 未注册玩家收到的提示");
L.add("not_registered=§e您尚未注册,请使用 /reg <密码>");
L.add("# not_logged_in: 已注册但未登录的提示");
L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>");
L.add("# already_registered: 已注册玩家再次注册时的提示");
L.add("already_registered=§c您已注册过账号");
L.add("# already_logged_in: 已登录玩家再次登录时的提示");
L.add("already_logged_in=§c您已登录");
L.add("# reg_success: 注册成功提示");
L.add("reg_success=§a注册成功!欢迎来到服务器");
L.add("# login_success: 登录成功提示");
L.add("login_success=§a登录成功!");
L.add("# login_failed: 密码错误提示");
L.add("login_failed=§c密码错误,请重试");
L.add("");
// --- 密码 ---
L.add("# ---- 密码相关 ----");
L.add("# password_wrong: 旧密码验证失败");
L.add("# ---- 密码 ----");
L.add("password_wrong=§c密码错误");
L.add("# password_changed: 密码修改成功");
L.add("password_changed=§a密码修改成功!");
L.add("# password_format_error: 密码格式不符合要求");
L.add("password_format_error=§c密码需6位以上,含大小写字母和数字");
L.add("# password_same: 新密码与旧密码相同");
L.add("password_same=§c新密码不能与旧密码相同");
L.add("# reset_no_email: 找回密码时未绑定邮箱");
L.add("reset_no_email=§c您未绑定邮箱,无法找回密码");
L.add("# reset_sent: 临时密码已发送到邮箱");
L.add("reset_sent=§a临时密码已发送到您的邮箱,请查收");
L.add("reset_sent=§a临时密码已发送到您的邮箱");
L.add("");
// --- 签到积分 ---
L.add("# ---- 签到积分相关 ----");
L.add("# checkin_already: 今日已签到");
L.add("# ---- 签到积分 ----");
L.add("checkin_already=§c您今日已签到");
L.add("# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率");
L.add("checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}");
L.add("# points_insufficient: 积分不足");
L.add("checkin_success=§a签到成功!获得 {points} 积分");
L.add("points_insufficient=§c积分不足,当前: {points}");
L.add("# points_purchase_success: 积分购买成功");
L.add("points_purchase_success=§a购买成功!消耗 {count} 积分");
L.add("");
// --- 挂机 ---
L.add("# ---- 挂机检测相关 ----");
L.add("# afk_set_enabled: 挂机检测开启");
L.add("# ---- 挂机检测 ----");
L.add("afk_set_enabled=§a挂机检测已开启");
L.add("# afk_set_disabled: 挂机检测关闭");
L.add("afk_set_disabled=§c挂机检测已关闭");
L.add("# afk_kick: 因挂机被踢出");
L.add("afk_kick=§c因挂机过久被踢出");
L.add("");
// --- 邮箱 ---
L.add("# ---- 邮箱相关 ----");
L.add("# email_set: 邮箱设置成功");
L.add("# ---- 邮箱 ----");
L.add("email_set=§a邮箱设置成功:{email}");
L.add("# email_format_error: 邮箱格式错误");
L.add("email_format_error=§c邮箱格式不正确");
L.add("");
// --- 邀请 ---
L.add("# ---- 邀请相关 ----");
L.add("# invite_code_generated: 邀请码生成");
L.add("# ---- 邀请 ----");
L.add("invite_code_generated=§a邀请码:{code}");
L.add("# invite_used: 使用邀请码成功");
L.add("invite_used=§a邀请成功,邀请人:{player}");
L.add("# invite_self: 使用自己的邀请码");
L.add("invite_self=§c不能使用自己的邀请码");
L.add("# invite_not_found: 邀请码不存在");
L.add("invite_not_found=§c邀请码不存在");
L.add("# invite_referral_bonus: 被邀请人签到后邀请人获得奖励");
L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分");
L.add("");
// --- 礼包 ---
L.add("# ---- 新人礼包相关 ----");
L.add("# gift_not_ready: 礼包条件未满足");
L.add("# ---- 礼包 ----");
L.add("gift_not_ready=§c礼包条件未满足");
L.add("# gift_claimed: 礼包领取成功");
L.add("gift_claimed=§a礼包领取成功!");
L.add("");
// --- 审批/IP ---
L.add("# ---- 审批与IP限制 ----");
L.add("# need_approval: 注册需审批");
L.add("# ---- 审批与IP ----");
L.add("need_approval=§e注册需审批 (工单#{id})");
L.add("# ip_max_accounts: IP注册已达上限");
L.add("ip_max_accounts=§cIP注册已达上限");
L.add("");
// --- 管理员 ---
L.add("# ---- 管理员操作 ----");
L.add("# admin_delete_confirm: 管理员删除账号确认提示");
L.add("# ---- 管理员 ----");
L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除");
L.add("");
// --- 聊天过滤 ---
L.add("# ---- 聊天过滤 ----");
L.add("# chat_muted: 禁言玩家尝试聊天");
L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天");
L.add("# chat_url_blocked: 发送违规链接被拦截");
L.add("# {url}=检测到的链接地址");
L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url}");
L.add("# chat_url_violation: 当前违规次数");
L.add("# {count}=当前违规次数");
L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言");
L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规链接: §e{url}");
L.add("chat_url_violation=§7当前违规次数: §e{count}");
L.add("# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数");
L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次)");
L.add("# chat_url_broadcast: 全服通报,{player}=玩家名");
L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url}");
L.add("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚");
L.add("");
// --- 通用 ---
L.add("# ---- 通用 ----");
L.add("# 未知参数: 未知命令或权限不足时的提示");
L.add("未知参数=§c未知参数或权限不足");
L.add("# only_player: 仅玩家可用的命令");
L.add("# only_player=§c此命令仅玩家可用");
writeLines(f, L);
}
// ==================== SMTP ====================
public void loadSmtp() {
smtpSettings.clear();
File file = new File(dataFolder, "SMTP设置.txt");
if (!file.exists()) createDefaultSmtp(file);
File file =
new File(dataFolder, "SMTP设置.txt");
if (!file.exists())
createDefaultSmtp(file);
loadMap(file, smtpSettings);
}
private double parseDouble(String s, double def) {
try { return Double.parseDouble(s.trim()); }
catch (Exception e) { return def; }
}
public String getSmtp(String key) {
return smtpSettings.getOrDefault(key, "");
}
/** 获取挂机白名单列表 */
public List<String> getAfkWhitelist() {
List<String> list = new ArrayList<>();
File file = new File(dataFolder, "挂机白名单.txt");
if (!file.exists()) {
try {
file.createNewFile();
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(file),
StandardCharsets.UTF_8))) {
w.write("# 每行一个玩家名,前面加 # 为注释");
w.newLine();
w.write("# 以下玩家不会被挂机踢出");
w.newLine();
}
} catch (IOException ignored) {}
}
try (BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(file),
StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
list.add(line);
}
}
} catch (IOException ignored) {}
return list;
}
/** 设置SMTP单项(聊天输入用) */
public void setSmtp(String key, String value) {
smtpSettings.put(key, value);
}
/** 将当前SMTP设置写回文件 */
public void saveSmtp() {
File file = new File(dataFolder, "SMTP设置.txt");
File file =
new File(dataFolder, "SMTP设置.txt");
List<String> L = new ArrayList<>();
L.add("# ===== SMTP 邮件服务器配置 =====");
for (Map.Entry<String, String> e : smtpSettings.entrySet()) {
for (Map.Entry<String, String> e
: smtpSettings.entrySet()) {
L.add(e.getKey() + "=" + e.getValue());
}
writeLines(file, L);
@@ -285,17 +203,25 @@ public class ConfigManager {
// ==================== 插件设置 ====================
public void loadSettings() {
File file = new File(dataFolder, "插件设置.txt");
if (!file.exists()) createDefaultSettings(file);
Map<String, String> m = new LinkedHashMap<>();
File file =
new File(dataFolder, "插件设置.txt");
if (!file.exists())
createDefaultSettings(file);
Map<String, String> m =
new LinkedHashMap<>();
loadMap(file, m);
adminTag = m.getOrDefault("管理标签", adminTag);
adminPassword = m.getOrDefault("管理密码", adminPassword);
adminTag = m.getOrDefault(
"管理标签", adminTag);
adminPassword = m.getOrDefault(
"管理密码", adminPassword);
maxAccountsPerIP = parseInt(
m.getOrDefault("每IP最大账号数", "3"), 3);
approvalMode = m.getOrDefault("审批模式", approvalMode);
approvalMode = m.getOrDefault(
"审批模式", approvalMode);
autoApproveDelayMinutes = parseInt(
m.getOrDefault("自动审批延迟分钟", "30"), 30);
m.getOrDefault("自动审批延迟分钟", "30"),
30);
loginTimeout = parseInt(
m.getOrDefault("登录超时秒数", "60"), 60);
afkEnabled = Boolean.parseBoolean(
@@ -304,56 +230,106 @@ public class ConfigManager {
m.getOrDefault("挂机超时秒数", "300"), 300);
smtpSsl = Boolean.parseBoolean(
m.getOrDefault("smtp加密", "true"));
httpPort = parseInt(
m.getOrDefault("http-port", "8080"),
8080);
broadcastServerUrl = m.getOrDefault(
"resource-pack-url", "");
// 提成
providerPointsPerScore = parseDouble(
m.getOrDefault("服务商积分倍率", "2.0"),
2.0);
m.getOrDefault(
"服务商积分倍率", "2.0"), 2.0);
baseEconomyReward = parseInt(
m.getOrDefault("基础经济奖励", "100"),
100);
m.getOrDefault(
"基础经济奖励", "100"), 100);
providerEconomyPerScore = parseDouble(
m.getOrDefault("经济奖励每评分", "50.0"),
50.0);
m.getOrDefault(
"经济奖励每评分", "50.0"), 50.0);
requesterPointsReward = parseInt(
m.getOrDefault("报单人积分奖励", "10"),
10);
m.getOrDefault(
"报单人积分奖励", "10"), 10);
// 垃圾站
garbageEnabled = Boolean.parseBoolean(
m.getOrDefault("垃圾站_启用", "true"));
m.getOrDefault(
"垃圾站_启用", "true"));
garbageInterval = parseInt(
m.getOrDefault("垃圾站_清理间隔秒",
"300"), 300);
m.getOrDefault(
"垃圾站_清理间隔秒", "300"), 300);
garbageMaxRounds = parseInt(
m.getOrDefault("垃圾站_保留轮数",
"1"), 1);
// loadSettings() 末尾添加:
m.getOrDefault(
"垃圾站_保留轮数", "1"), 1);
// 签到
checkinRewardFixed = parseDouble(
m.getOrDefault(
"签到固定金额", "0"), 0);
checkinRewardMin = parseDouble(
m.getOrDefault(
"签到最小金额", "0"), 0);
checkinRewardMax = parseDouble(
m.getOrDefault(
"签到最大金额", "0"), 0);
checkinGivePoints = Boolean.parseBoolean(
m.getOrDefault(
"签到给积分", "true"));
checkinPoints = parseInt(
m.getOrDefault(
"签到积分数量", "10"), 10);
backCheckPointMultiplier = parseInt(
m.getOrDefault(
"补签积分倍率", "5"), 5);
// 自动判断模式
if (checkinRewardFixed > 0) {
checkinRewardType = "fixed";
} else if (checkinRewardMin > 0
&& checkinRewardMax > 0
&& checkinRewardMax
> checkinRewardMin) {
checkinRewardType = "range";
} else {
checkinRewardType = "none";
}
}
public void saveSettings() {
File file = new File(dataFolder, "插件设置.txt");
File file =
new File(dataFolder, "插件设置.txt");
List<String> L = new ArrayList<>();
L.add("# ===== Sdf1_login 插件设置 =====");
L.add("管理标签=" + adminTag);
L.add("管理密码=" + adminPassword);
L.add("每IP最大账号数=" + maxAccountsPerIP);
L.add("审批模式=" + approvalMode);
L.add("自动审批延迟分钟=" + autoApproveDelayMinutes);
L.add("自动审批延迟分钟="
+ autoApproveDelayMinutes);
L.add("登录超时秒数=" + loginTimeout);
L.add("挂机踢出=" + afkEnabled);
L.add("挂机超时秒数=" + afkTimeout);
// 垃圾站设置
L.add("# ---- 资源包 ----");
L.add("resource-pack-url="
+ broadcastServerUrl);
L.add("http-port=" + httpPort);
L.add("# ---- 垃圾站 ----");
L.add("垃圾站_启用=" + garbageEnabled);
L.add("垃圾站_清理间隔秒=" + garbageInterval);
L.add("垃圾站_清理间隔秒="
+ garbageInterval);
L.add("垃圾站_保留轮数=" + garbageMaxRounds);
L.add("# ---- 签到盲盒奖励 ----");
L.add("签到固定金额=" + checkinRewardFixed);
L.add("签到最小金额=" + checkinRewardMin);
L.add("签到最大金额=" + checkinRewardMax);
L.add("签到给积分=" + checkinGivePoints);
L.add("签到积分数量=" + checkinPoints);
L.add("补签积分倍率="
+ backCheckPointMultiplier);
writeLines(file, L);
writeLines(file, L);
}
public double providerPointsPerScore = 2.0;
public int baseEconomyReward = 100;
public double providerEconomyPerScore = 50.0;
public int requesterPointsReward = 10;
public boolean garbageEnabled = true;
public int garbageInterval = 300;
public int garbageMaxRounds = 1;
private void createDefaultSettings(File f) {
List<String> L = new ArrayList<>();
@@ -366,100 +342,177 @@ public class ConfigManager {
L.add("登录超时秒数=60");
L.add("挂机踢出=false");
L.add("挂机超时秒数=300");
L.add("服务商积分倍率=" + providerPointsPerScore);
L.add("基础经济奖励=" + baseEconomyReward);
L.add("经济奖励每评分=" + providerEconomyPerScore);
L.add("报单人积分奖励=" + requesterPointsReward);
L.add("# ---- 资源包 ----");
L.add("resource-pack-url=");
L.add("http-port=8080");
L.add("# ---- 垃圾站 ----");
L.add("垃圾站_启用=true");
L.add("垃圾站_清理间隔秒=300");
L.add("垃圾站_保留轮数=1");
L.add("# ---- 资源包(HTTP) ----");
L.add("resource-pack-url=");
L.add("http-port=8080");
L.add("# ---- 签到盲盒奖励 ----");
L.add("签到固定金额=0");
L.add("签到最小金额=50");
L.add("签到最大金额=500");
L.add("签到给积分=true");
L.add("签到积分数量=10");
L.add("补签积分倍率=5");
writeLines(f, L);
}
// ==================== 通用读写 ====================
// ==================== 通用工具 ====================
private void loadMap(File file, Map<String, String> map) {
try (BufferedReader r = new BufferedReader(
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
public List<String> getAfkWhitelist() {
List<String> list = new ArrayList<>();
File file = new File(
dataFolder, "挂机白名单.txt");
if (!file.exists()) {
try {
file.createNewFile();
try (BufferedWriter w =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(
file),
StandardCharsets
.UTF_8))) {
w.write("# 每行一个玩家名");
w.newLine();
}
} catch (IOException ignored) {}
}
try (BufferedReader r =
new BufferedReader(
new InputStreamReader(
new FileInputStream(
file),
StandardCharsets
.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
int eq = line.indexOf('=');
if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim());
if (!line.isEmpty()
&& !line.startsWith("#")) {
list.add(line);
}
}
} catch (IOException e) {
System.err.println("[Sdf1_login] 读取失败: " + file.getName());
}
} catch (IOException ignored) {}
return list;
}
private void writeLines(File file, List<String> lines) {
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
for (String l : lines) { w.write(l); w.newLine(); }
} catch (IOException e) {
System.err.println("[Sdf1_login] 写入失败: " + file.getName());
}
}
private int parseInt(String s, int def) {
try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; }
}
/**
* 生成6位验证码(字母+数字)
*/
public String generateSmtpVerifyCode() {
String chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789";
StringBuilder sb = new StringBuilder();
java.util.Random r = new java.util.Random();
java.util.Random r =
new java.util.Random();
for (int i = 0; i < 6; i++) {
sb.append(chars.charAt(r.nextInt(
chars.length())));
sb.append(chars.charAt(
r.nextInt(chars.length())));
}
smtpVerifyCode = sb.toString();
smtpVerifyExpire =
System.currentTimeMillis() + 300000L;
System.currentTimeMillis()
+ 300000L;
return smtpVerifyCode;
}
/**
* 检查验证码是否有效
*/
public boolean checkSmtpVerifyCode(String code) {
if (System.currentTimeMillis() > smtpVerifyExpire) {
public boolean checkSmtpVerifyCode(
String code) {
if (System.currentTimeMillis()
> smtpVerifyExpire) {
smtpVerifyCode = "";
return false;
}
return smtpVerifyCode.equalsIgnoreCase(code);
return smtpVerifyCode
.equalsIgnoreCase(code);
}
/**
* 清除验证码
*/
public void clearSmtpVerifyCode() {
smtpVerifyCode = "";
smtpVerifyExpire = 0;
}
/**
* 检查发送间隔是否>=1分钟
*/
public boolean canSendSmtpVerify() {
return System.currentTimeMillis()
- smtpLastSendTime >= 60000L;
}
public void recordSmtpSendTime() {
smtpLastSendTime = System.currentTimeMillis();
smtpLastSendTime =
System.currentTimeMillis();
}
// ==================== 文件读写 ====================
private void loadMap(File file,
Map<String, String> map) {
try (BufferedReader r =
new BufferedReader(
new InputStreamReader(
new FileInputStream(
file),
StandardCharsets
.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (line.isEmpty()
|| line.startsWith("#"))
continue;
int eq = line.indexOf('=');
if (eq > 0) {
map.put(
line.substring(0, eq)
.trim(),
line.substring(eq + 1)
.trim());
}
}
} catch (IOException e) {
System.err.println(
"[Sdf1_login] 读取失败: "
+ file.getName());
}
}
private void writeLines(File file,
List<String> lines) {
try (BufferedWriter w =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(
file),
StandardCharsets
.UTF_8))) {
for (String l : lines) {
w.write(l);
w.newLine();
}
} catch (IOException e) {
System.err.println(
"[Sdf1_login] 写入失败: "
+ file.getName());
}
}
private double parseDouble(
String s, double def) {
try {
return Double.parseDouble(s.trim());
} catch (Exception e) {
return def;
}
}
private int parseInt(String s, int def) {
try {
return Integer.parseInt(s.trim());
} catch (Exception e) {
return def;
}
}
}
+87 -64
View File
@@ -30,7 +30,6 @@ public class GUIManager {
public GUIManager(Main plugin) {
this.plugin = plugin;
}
public void openMain(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_MAIN);
@@ -42,17 +41,21 @@ public class GUIManager {
g.setItem(14, mkItem(Material.EMERALD,
"§d§l积分商城"));
g.setItem(16, mkItem(Material.CHEST,
"§e§l新人礼包"));
g.setItem(18, mkItem(Material.BOOK,
"§e§l任务中心"));
g.setItem(18, mkItem(Material.EMERALD_BLOCK,
"§a§l每日签到",
"§7签到获取积分和惊喜奖励"));
g.setItem(20, mkItem(Material.BOOK,
"§6工单系统",
"§7提交bug、求助、举报"));
if (p.isOp() || isAdmin(p)) {
g.setItem(20, mkItem(Material.CHEST,
g.setItem(22, mkItem(Material.CHEST,
"§6垃圾回收站",
"§7查看和取回清理的物品"));
}
if (isAdmin(p)) {
g.setItem(22, mkItem(Material.REDSTONE_BLOCK,
g.setItem(24, mkItem(
Material.REDSTONE_BLOCK,
"§c§l管理员面板"));
}
p.openInventory(g);
@@ -107,6 +110,70 @@ public class GUIManager {
}
// 在 openGiftStages 方法之前添加:
public void openGiftStages(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_GIFT_STAGES);
fillBg(g);
QuestTracker qt =
plugin.getQuestTracker();
if (qt == null) {
g.setItem(22, mkItem(
Material.BARRIER,
"§7任务系统未加载"));
p.openInventory(g);
return;
}
List<QuestTracker.QuestFile> quests =
qt.getQuests("新人任务");
String playerName = p.getName();
int max = quests.size();
for (int i = 0;
i < max && i < 9; i++) {
QuestTracker.QuestFile qf =
quests.get(i);
boolean completed =
qt.isStageCompleted(
playerName, qf);
boolean claimed =
qt.hasClaimed(
playerName, qf);
Material mat;
String status;
if (completed && claimed) {
mat = Material.LIME_WOOL;
status = "§a已领取";
} else if (completed) {
mat = Material.YELLOW_WOOL;
status = "§e可领取";
} else {
mat = Material.RED_WOOL;
status = "§c未达标";
}
List<String> lore =
new ArrayList<>();
lore.add("§7" + status);
for (String cond :
qf.conditions) {
String progress =
qt.getProgress(
playerName, cond);
lore.add("§7" + progress);
}
g.setItem(10 + i, mkItem(mat,
"§e" + qf.displayName,
lore.toArray(
new String[0])));
}
g.setItem(26, mkItem(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
/**
* 获取任务列表(带真实完成状态)
@@ -408,72 +475,27 @@ public class GUIManager {
g.setItem(10, mkItem(Material.BOOK,
"§e§l新人任务",
"§7完成新手阶段获取奖励"));
g.setItem(12, mkItem(Material.COMPASS,
g.setItem(12, mkItem(Material.MAP,
"§a§l群系打卡",
"§7在不同群系打卡完成任务",
"§7打卡范围: 32格去重"));
g.setItem(14, mkItem(Material.COMPASS,
"§b§l主线任务",
"§7查看主线任务列表"));
g.setItem(14, mkItem(Material.PAPER,
g.setItem(16, mkItem(Material.PAPER,
"§a§l支线任务",
"§7查看支线任务列表"));
g.setItem(16, mkItem(Material.EMERALD,
"§d§l每日签到",
"§7签到获取积分"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
g.setItem(18, mkItem(Material.BOOK,
"§6工单系统",
"§7提交bug、求助、举报"));
g.setItem(20, mkItem(Material.EMERALD,
"§a§l每日签到",
"§7签到获取积分和奖励"));
g.setItem(22, mkItem(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openGiftStages(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_GIFT_STAGES);
fillBg(g);
QuestTracker qt = plugin.getQuestTracker();
if (qt == null) {
g.setItem(22, mkItem(Material.BARRIER,
"§7任务系统未加载"));
p.openInventory(g);
return;
}
List<QuestTracker.QuestFile> quests =
qt.getQuests("新人任务");
String playerName = p.getName();
int max = quests.size();
for (int i = 0; i < max && i < 9; i++) {
QuestTracker.QuestFile qf = quests.get(i);
boolean completed =
qt.isStageCompleted(playerName, qf);
boolean claimed =
qt.hasClaimed(playerName, qf);
Material mat;
String status;
if (completed && claimed) {
mat = Material.LIME_WOOL;
status = "§a已领取";
} else if (completed) {
mat = Material.YELLOW_WOOL;
status = "§e可领取";
} else {
mat = Material.RED_WOOL;
status = "§c未达标";
}
// 显示进度
List<String> lore = new ArrayList<>();
lore.add("§7" + status);
for (String cond : qf.conditions) {
String progress =
qt.getProgress(playerName, cond);
lore.add("§7" + progress);
}
g.setItem(10 + i, mkItem(mat,
"§e" + qf.displayName,
lore.toArray(new String[0])));
}
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openTaskList(Player p, String folderName) {
@@ -552,6 +574,7 @@ public class GUIManager {
slot++;
}
g.setItem(53, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
+26 -63
View File
@@ -230,27 +230,26 @@ public class GarbageManager {
}
public void saveItem(ItemStack stack) {
{
try {
String data =
stackToBase64(stack);
if (data.isEmpty()) return;
PreparedStatement ps = db.prepareStatement(
"INSERT INTO items "
+ "(item_data, amount, "
+ "round, create_time) "
+ "VALUES (?,?,?,?)");
ps.setString(1, data);
ps.setInt(2, stack.getAmount());
ps.setInt(3, currentRound);
ps.setLong(4,
System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}}
try {
String data = stackToBase64(stack);
if (data.isEmpty()) return;
PreparedStatement ps = db.prepareStatement(
"INSERT INTO items "
+ "(item_data, amount, "
+ "round, create_time) "
+ "VALUES (?,?,?,?)");
ps.setString(1, data);
ps.setInt(2, stack.getAmount());
ps.setInt(3, currentRound);
ps.setLong(4,
System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private int deleteOldItems(int beforeRound) {
try {
@@ -344,11 +343,9 @@ public class GarbageManager {
}
public void openRecyclePage(Player p, int page) {
Inventory g = Bukkit.createInventory(
null, 54, "§6§l垃圾回收站");
// 获取全部存储物品
List<Map<String, Object>> allItems =
getStoredItems(999);
int total = allItems.size();
@@ -381,38 +378,6 @@ public class GarbageManager {
.get("amount")).intValue();
ItemStack stack = base64ToStack(data);
if (stack != null) {
try {
byte[] bytes =
stack.serializeAsBytes();
String b64 = java.util.Base64
.getEncoder()
.encodeToString(bytes);
int len = b64.length();
String f = len > 15
? b64.substring(10, 15) : "短";
String m = len >= 5
? b64.substring(len / 2,
Math.min(len / 2 + 5, len))
: "短";
String b = len > 10
? b64.substring(len - 10, len - 5)
: "短";
plugin.getLogger().info(
"[垃圾站加载] ID#" + id
+ " " + stack.getType()
+ " x" + amount
+ " base64[" + len + "]"
+ " 前=" + f
+ " 中=" + m
+ " 后=" + b);
} catch (Exception ex) {
plugin.getLogger().warning(
"[垃圾站加载] 序列化异常: "
+ ex.getMessage());
}
}
if (stack != null) {
stack.setAmount(amount);
ItemMeta im =
stack.getItemMeta();
@@ -429,10 +394,7 @@ public class GarbageManager {
slot++;
}
// 45-48 格:额外投放区(空位)
// 52-53 格:额外投放区(空位)
// 49 格:翻页-上一页
// 49 格:上一页
if (page > 0) {
g.setItem(49, mk(Material.ARROW,
"§e上一页 §7("
@@ -451,9 +413,8 @@ public class GarbageManager {
"§7当前页: §f" + (page + 1)
+ "/" + totalPages));
// 51 格:翻页-下一页
boolean canNext = total >= pageSize
&& page < totalPages - 1;
// 51 格:下一页
boolean canNext = page < totalPages - 1;
if (canNext) {
g.setItem(51, mk(Material.ARROW,
"§e下一页 §7("
@@ -463,13 +424,15 @@ public class GarbageManager {
g.setItem(51, mk(Material.ARROW,
"§7已是最后一页"));
}
// 52格:返回按钮
// 52 格:返回
g.setItem(52, mk(Material.ARROW,
"§7返回",
"§7返回主菜单"));
p.openInventory(g);
}
public int getRecyclePage(String name) {
return recyclePages
.getOrDefault(name, 0);
+1 -1
View File
@@ -22,7 +22,7 @@ public class GiftManager {
public GiftManager(Main plugin) {
this.plugin = plugin;
this.giftFolder = new File(
plugin.getDataFolder(), "新人礼包");
plugin.getDataFolder(), "任务面板");
}
public void loadStages() {
File diff suppressed because it is too large Load Diff
+827 -20
View File
@@ -10,6 +10,7 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
@@ -178,20 +179,111 @@ public class QuestTracker {
BIOME_CATEGORY.put("minecraft:warped_forest", "下界");
BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界");
BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界");
// 岩岸/沙滩
BIOME_CATEGORY.put("minecraft:stony_shore",
"石岸");
// 海岸
BIOME_CATEGORY.put("minecraft:mushroom_fields",
"蘑菇岛");
// 苍白之园
BIOME_CATEGORY.put(
"minecraft:pale_garden", "森林");
// 末地小型岛屿
BIOME_CATEGORY.put(
"minecraft:small_end_islands", "末地");
// 主世界结构归类
BIOME_CATEGORY.put("minecraft:village", "平原");
BIOME_CATEGORY.put(
"minecraft:desert_pyramid", "沙漠");
BIOME_CATEGORY.put(
"minecraft:jungle_pyramid", "森林");
BIOME_CATEGORY.put(
"minecraft:swamp_hut", "沼泽");
// 末地群系
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 String[]
ALL_CATEGORIES = {
"海洋", "平原", "森林", "沙漠",
"恶地", "雪原", "沙砾", "沼泽",
"河流", "石岸", "蘑菇岛", "地下",
"下界", "末地"
};
// ===== 结构映射 =====
private static final Map<String, String>
STRUCTURE_CATEGORY =
new HashMap<>();
static {
// 主世界结构
STRUCTURE_CATEGORY.put(
"minecraft:village", "村庄");
STRUCTURE_CATEGORY.put(
"minecraft:desert_pyramid",
"沙漠");
STRUCTURE_CATEGORY.put(
"minecraft:jungle_pyramid",
"森林");
STRUCTURE_CATEGORY.put(
"minecraft:swamp_hut", "沼泽");
STRUCTURE_CATEGORY.put(
"minecraft:igloo", "雪原");
STRUCTURE_CATEGORY.put(
"minecraft:mansion", "森林");
STRUCTURE_CATEGORY.put(
"minecraft:pillager_outpost",
"平原");
STRUCTURE_CATEGORY.put(
"minecraft:mineshaft", "地下");
STRUCTURE_CATEGORY.put(
"minecraft:stronghold", "地下");
STRUCTURE_CATEGORY.put(
"minecraft:ancient_city",
"地下");
STRUCTURE_CATEGORY.put(
"minecraft:trail_ruins",
"平原");
STRUCTURE_CATEGORY.put(
"minecraft:trial_chambers",
"地下");
STRUCTURE_CATEGORY.put(
"minecraft:ocean_ruin", "海洋");
STRUCTURE_CATEGORY.put(
"minecraft:shipwreck", "海洋");
STRUCTURE_CATEGORY.put(
"minecraft:buried_treasure",
"海洋");
STRUCTURE_CATEGORY.put(
"minecraft:ruined_portal",
"平原");
STRUCTURE_CATEGORY.put(
"minecraft:fossil", "平原");
STRUCTURE_CATEGORY.put(
"minecraft:ancient_city",
"地下");
// 下界结构
STRUCTURE_CATEGORY.put(
"minecraft:nether_fortress",
"下界");
STRUCTURE_CATEGORY.put(
"minecraft:bastion_remnant",
"下界");
STRUCTURE_CATEGORY.put(
"minecraft:nether_fossil",
"下界");
STRUCTURE_CATEGORY.put(
"minecraft:basalt_pillars",
"下界");
}
// 所有群系分类的条件关键词映射
private static final Map<String, String>
@@ -214,6 +306,10 @@ public class QuestTracker {
CATEGORY_KEYWORDS.put("地狱", "下界");
CATEGORY_KEYWORDS.put("下届", "下界");
CATEGORY_KEYWORDS.put("主岛", "末地");
CATEGORY_KEYWORDS.put("石岸", "石岸");
CATEGORY_KEYWORDS.put("沙滩", "石岸");
CATEGORY_KEYWORDS.put("海岸", "石岸");
}
/**
@@ -224,25 +320,53 @@ public class QuestTracker {
*/
public String mapBiomeCategory(
String rawKey, String worldName) {
// 下界维度优先用世界名
// 优先用世界名判断维度
if (worldName != null) {
String w = worldName.toLowerCase();
String w =
worldName.toLowerCase();
if (w.contains("nether"))
return "下界";
if (w.contains("end"))
return "末地";
}
// 用群系映射表
if (rawKey != null) {
String lower =
rawKey.toLowerCase();
// 带 minecraft: 前缀的直接查
String cat =
BIOME_CATEGORY.get(
rawKey.toLowerCase());
if (cat != null) return cat;
BIOME_CATEGORY.get(lower);
if (cat != null)
return cat;
// 不带前缀的尝试补全
String prefixed =
"minecraft:" + lower;
cat = BIOME_CATEGORY.get(prefixed);
if (cat != null)
return cat;
// 模糊匹配群系名
for (Map.Entry<String, String> entry
: BIOME_CATEGORY.entrySet()) {
if (entry.getKey()
.contains(lower)
|| lower.contains(
entry.getKey()
.replace(
"minecraft:",
""))) {
return entry.getValue();
}
}
}
return "主世界";
}
// ========== 解析为区块 ==========
private List<Section> parseIntoSections(
@@ -1611,22 +1735,68 @@ public class QuestTracker {
+ ": §e" + cur
+ "/" + needed;
}
if (key.contains("村民")
&& key.contains("交易")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int cur = getCounter(
player, "VILLAGER_TRADE");
return "§7村民交易: §e"
+ cur + "/" + needed;
}
}
// ===== 击杀泛解析(支持中文列表) =====
// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等
if (key.contains("击杀") && key.contains("只")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
String mobPart = key;
java.util.regex.Matcher killM =
java.util.regex.Pattern
.compile("击杀\\d+只(.+)")
.matcher(key);
if (killM.find()) {
mobPart = killM.group(1).trim();
}
String[] mobNames = mobPart
.split("[、,,,]+");
int totalKills = 0;
StringBuilder names = new StringBuilder();
for (String name : mobNames) {
String trimmed = name.trim();
if (trimmed.isEmpty()) continue;
String entityType =
chineseMobToEntity(trimmed);
if (!entityType.isEmpty()) {
totalKills += getCounter(
player,
"KILL:" + entityType);
if (names.length() > 0)
names.append("/");
names.append(trimmed);
}
}
return "§7击杀" + names + ": §e"
+ totalKills + "/" + needed;
}
// 单一KILL:xxx:N 格式(原有逻辑保留)
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;
String[] p = key.split(":");
if (p.length >= 3) {
int needed = parseInt(p[2]);
int cur = getCounter(player,
"KILL:" + p[1]);
return "§7击杀" + p[1] + ": §e"
+ cur + "/" + needed;
}
}
if (key.startsWith("KILL_PLAYER")) {
String[] parts = key.split(":");
int needed = parts.length >= 2
@@ -1739,10 +1909,219 @@ public class QuestTracker {
+ done + "/"
+ quests.size();
}
// ===== 泛解析:击杀类进度 =====
if (key.contains("击杀") || key.contains("猎杀")
|| key.contains("消灭")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
List<String> mobTypes = extractMobTypes(key);
if (!mobTypes.isEmpty()) {
int total = 0;
StringBuilder names =
new StringBuilder();
for (int i = 0;
i < mobTypes.size(); i++) {
total += getCounter(player,
"KILL:" + mobTypes.get(i));
if (i > 0) names.append("/");
names.append(reverseMobName(
mobTypes.get(i)));
}
return "§7击杀" + names + ": §e"
+ total + "/" + needed;
}
}
// ===== 泛解析:村民交易进度 =====
if (key.contains("交易")
&& (key.contains("村民")
|| key.contains("与村民"))) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int cur = getCounter(player, "TRADE");
return "§7村民交易: §e"
+ cur + "/" + needed;
}
// ===== 泛解析:采集/挖矿进度 =====
if (key.contains("采集")
|| key.contains("挖")) {
int needed =
parseNumberFromText(key);
if (needed <= 0) needed = 1;
List<String> matTypes =
extractMaterialTypes(key);
if (!matTypes.isEmpty()) {
int total = 0;
StringBuilder names =
new StringBuilder();
for (int i = 0;
i < matTypes.size(); i++) {
total += getCounter(
player,
"MINE:" + matTypes
.get(i));
if (i > 0)
names.append("/");
names.append(
translateMaterialName(
matTypes
.get(i)));
}
return "§7采集" + names
+ ": §e" + total
+ "/" + needed;
}
}
// ===== 泛解析:合成进度 =====
if (key.contains("合成") || key.contains("制作")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int cur = getCounter(player, "CRAFT");
return "§7合成: §e" + cur + "/" + needed;
}
// ===== 泛解析:钓鱼进度 =====
if (key.contains("钓鱼") || key.contains("钓")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
int cur = getCounter(player, "FISH");
return "§7钓鱼: §e" + cur + "/" + needed;
}
return "§7" + key;
}
private String translateMaterialName(
String name) {
switch (name) {
case "COAL_ORE":
return "煤矿石";
case "DEEPSLATE_COAL_ORE":
return "深层煤矿石";
case "IRON_ORE":
return "铁矿石";
case "DEEPSLATE_IRON_ORE":
return "深层铁矿石";
case "COPPER_ORE":
return "铜矿石";
case "DEEPSLATE_COPPER_ORE":
return "深层铜矿石";
case "GOLD_ORE":
return "金矿石";
case "DEEPSLATE_GOLD_ORE":
return "深层金矿石";
case "DIAMOND_ORE":
return "钻石矿石";
case "DEEPSLATE_DIAMOND_ORE":
return "深层钻石矿石";
case "EMERALD_ORE":
return "绿宝石矿石";
case "DEEPSLATE_EMERALD_ORE":
return "深层绿宝石矿石";
case "LAPIS_ORE":
return "青金石矿石";
case "DEEPSLATE_LAPIS_ORE":
return "深层青金石矿石";
case "REDSTONE_ORE":
return "红石矿石";
case "DEEPSLATE_REDSTONE_ORE":
return "深层红石矿石";
case "NETHER_GOLD_ORE":
return "下界金矿石";
case "NETHER_QUARTZ_ORE":
return "下界石英矿石";
case "ANCIENT_DEBRIS":
return "远古残骸";
case "COAL":
return "煤炭";
case "RAW_IRON":
return "粗铁";
case "RAW_GOLD":
return "粗金";
case "RAW_COPPER":
return "粗铜";
case "DIAMOND":
return "钻石";
case "EMERALD":
return "绿宝石";
case "LAPIS_LAZULI":
return "青金石";
case "REDSTONE":
return "红石";
case "QUARTZ":
return "石英";
default:
return name;
}
}
/**
* 中文物种名 → Bukkit EntityType 名
* 支持击杀泛解析的模糊匹配
*/
private String chineseMobToEntity(String cn) {
String n = cn.toLowerCase().trim();
// 直接别名表
Map<String, String> mobMap = new HashMap<>();
mobMap.put("骷髅", "SKELETON");
mobMap.put("僵尸", "ZOMBIE");
mobMap.put("苦力怕", "CREEPER");
mobMap.put("蜘蛛", "SPIDER");
mobMap.put("洞穴蜘蛛", "CAVE_SPIDER");
mobMap.put("小白", "SKELETON");
mobMap.put("末影人", "ENDERMAN");
mobMap.put("enderman", "ENDERMAN");
mobMap.put("猪灵", "PIGLIN");
mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE");
mobMap.put("恶魂", "GHAST");
mobMap.put("凋灵骷髅", "WITHER_SKELETON");
mobMap.put("烈焰人", "BLAZE");
mobMap.put("史莱姆", "SLIME");
mobMap.put("岩浆怪", "MAGMA_CUBE");
mobMap.put("女巫", "WITCH");
mobMap.put("蠹虫", "SILVERFISH");
mobMap.put("潜影贝", "SHULKER");
mobMap.put("守卫者", "GUARDIAN");
mobMap.put("远古守卫者", "ELDER_GUARDIAN");
mobMap.put("溺尸", "DROWNED");
mobMap.put("僵尸村民", "ZOMBIE_VILLAGER");
mobMap.put("尸壳", "HUSK");
mobMap.put("流浪者", "STRAY");
mobMap.put("幻翼", "PHANTOM");
mobMap.put("掠夺者", "PILLAGER");
mobMap.put("卫道士", "VINDICATOR");
mobMap.put("唤魔者", "EVOKER");
mobMap.put("恼鬼", "VEX");
mobMap.put("劫掠兽", "RAVAGER");
mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY");
mobMap.put("凋灵", "WITHER");
mobMap.put("末影龙", "ENDER_DRAGON");
// 精确匹配
if (mobMap.containsKey(n)) {
return mobMap.get(n);
}
// 模糊匹配(包含关系)
for (Map.Entry<String, String> en
: mobMap.entrySet()) {
if (n.contains(en.getKey())
|| en.getKey().contains(n)) {
return en.getValue();
}
}
// 尝试直接用英文名
try {
return org.bukkit.entity.EntityType
.valueOf(n.toUpperCase()
.replace(" ", "_"))
.name();
} catch (Exception e) {
return "";
}
}
public List<String> checkInBiome(
String player,
@@ -1776,6 +2155,23 @@ public class QuestTracker {
String category =
mapBiomeCategory(
rawBiomeKey, worldName);
// 结构检测(覆盖纯生物群系分类)
// 地下检测:Y低于表面则归类为地下
// 覆盖废弃矿井/试炼密室/要塞/远古城市等
try {
org.bukkit.World uw =
Bukkit.getWorld(worldName);
if (uw != null) {
int surfaceY = uw
.getHighestBlockYAt(
(int) Math.floor(x),
(int) Math.floor(z));
if (y < surfaceY - 8) {
category = "地下";
}
}
} catch (Exception ignored) {}
// 记录总打卡
int allCur = getCounter(
@@ -1851,6 +2247,344 @@ public class QuestTracker {
return matched;
}
/*
* 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录
*/
public String checkInAtPosition(
String player, String worldName,
double x, double y, double z) {
List<double[]> history =
loadCheckInPositions(player);
for (double[] pos : history) {
double dist = Math.sqrt(
Math.pow(x - pos[0], 2)
+ Math.pow(y - pos[1], 2)
+ Math.pow(z - pos[2], 2));
if (dist <= 32) {
return "§c距离上次打卡点太近("
+ String.format(
"%.0f", dist)
+ "格)";
}
}
// ===== 获取群系和分类 =====
String biome = "minecraft:plains";
String category = "平原";
try {
org.bukkit.World w =
Bukkit.getWorld(worldName);
if (w != null) {
org.bukkit.Location loc =
new org.bukkit.Location(
w,
(int) Math.floor(x),
(int) Math.floor(y),
(int) Math.floor(z));
biome = loc.getBlock()
.getBiome().getKey()
.toString();
category = mapBiomeCategory(
biome, worldName);
int surfaceY = w
.getHighestBlockYAt(
(int) Math.floor(x),
(int) Math.floor(z));
if (y < surfaceY - 8) {
category = "地下";
}
if ("下界".equals(category)
|| w.getEnvironment()
== org.bukkit.World
.Environment.NETHER) {
category = "下界";
} else if ("末地".equals(category)
|| w.getEnvironment()
== org.bukkit.World
.Environment.THE_END) {
category = "末地";
}
}
} catch (Exception ex) {
plugin.getLogger().warning(
"[QuestTracker] 群系读取失败: "
+ ex.getMessage());
}
plugin.getLogger().info(
"[QuestTracker] 打卡: "
+ player
+ " world=" + worldName
+ " biome=" + biome
+ " cat=" + category
+ " pos="
+ String.format(
"%.0f,%.0f,%.0f",
x, y, z));
saveCheckInPosition(
player, x, y, z);
recordCheckIn(
player, biome, category,
worldName, x, y, z);
List<String> matched =
checkInBiome(
player, biome,
worldName, x, y, z);
StringBuilder sb = new StringBuilder();
sb.append("§a打卡成功!群系: §e")
.append(category);
if (!matched.isEmpty()) {
sb.append("\n§a完成任务条件:");
for (String m : matched) {
String qn =
findQuestName(m);
sb.append("\n§7• ");
if (qn != null) {
sb.append("§e[")
.append(qn)
.append("§7] §f");
}
sb.append(m);
}
}
return sb.toString();
}
/**
* 根据条件文本查找所属任务名
*/
private String findQuestName(
String condition) {
for (String cat : Arrays.asList(
"新人任务",
"主线任务",
"支线任务")) {
for (QuestFile qf :
getQuests(cat)) {
if (qf.conditions.contains(
condition)) {
return qf.displayName;
}
}
}
return null;
}
/**
* 加载玩家所有历史打卡坐标
* 格式: x1,y1,z1;x2,y2,z2;...
*/
private List<double[]> loadCheckInPositions(
String player) {
List<double[]> list = new ArrayList<>();
Object val = storage.getCondition(
player, "checkin_all_pos");
if (val == null) return list;
String data = val.toString();
if (data.isEmpty()) return list;
String[] records = data.split(";");
for (String r : records) {
String[] parts = r.split(",");
if (parts.length >= 3) {
try {
list.add(new double[]{
Double.parseDouble(parts[0]),
Double.parseDouble(parts[1]),
Double.parseDouble(parts[2])});
} catch (NumberFormatException e) {
// 跳过损坏数据
}
}
}
return list;
}
/**
* 保存打卡坐标(追加到已有记录)
*/
private void saveCheckInPosition(
String player, double x,
double y, double z) {
List<double[]> list =
loadCheckInPositions(player);
list.add(new double[]{x, y, z});
StringBuilder sb = new StringBuilder();
for (double[] pos : list) {
if (sb.length() > 0) sb.append(";");
sb.append(pos[0]).append(",")
.append(pos[1]).append(",")
.append(pos[2]);
}
storage.setConditionRaw(
player,
"checkin_all_pos",
sb.toString());
}
// ========== 中文生物名 → EntityType 映射 ==========
private static final Map<String, String>
MOB_NAME_MAP = new HashMap<>();
static {
MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER");
MOB_NAME_MAP.put("骷髅", "SKELETON");
MOB_NAME_MAP.put("僵尸", "ZOMBIE");
MOB_NAME_MAP.put("苦力怕", "CREEPER");
MOB_NAME_MAP.put("蜘蛛", "SPIDER");
MOB_NAME_MAP.put("末影人", "ENDERMAN");
MOB_NAME_MAP.put("女巫", "WITCH");
MOB_NAME_MAP.put("烈焰人", "BLAZE");
MOB_NAME_MAP.put("僵尸猪灵",
"ZOMBIFIED_PIGLIN");
MOB_NAME_MAP.put("猪灵", "PIGLIN");
MOB_NAME_MAP.put("恶魂", "GHAST");
MOB_NAME_MAP.put("凋灵骷髅",
"WITHER_SKELETON");
MOB_NAME_MAP.put("史莱姆", "SLIME");
MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE");
MOB_NAME_MAP.put("溺尸", "DROWNED");
MOB_NAME_MAP.put("尸壳", "HUSK");
MOB_NAME_MAP.put("流髑", "STRAY");
MOB_NAME_MAP.put("幻翼", "PHANTOM");
MOB_NAME_MAP.put("守卫者", "GUARDIAN");
MOB_NAME_MAP.put("远古守卫者",
"ELDER_GUARDIAN");
MOB_NAME_MAP.put("掠夺者", "PILLAGER");
MOB_NAME_MAP.put("卫道士", "VINDICATOR");
MOB_NAME_MAP.put("唤魔者", "EVOKER");
MOB_NAME_MAP.put("恼鬼", "VEX");
MOB_NAME_MAP.put("劫掠兽", "RAVAGER");
MOB_NAME_MAP.put("潜影贝", "SHULKER");
MOB_NAME_MAP.put("末影螨", "ENDERMITE");
MOB_NAME_MAP.put("凋灵", "WITHER");
MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON");
MOB_NAME_MAP.put("监守者", "WARDEN");
MOB_NAME_MAP.put("嗅探兽", "SNIFFER");
MOB_NAME_MAP.put("僵尸村民",
"ZOMBIE_VILLAGER");
}
/**
* 从条件文本中提取所有匹配的生物类型
* 按名称长度降序匹配,避免短名误匹配
*/
private List<String> extractMobTypes(
String text) {
List<String> types = new ArrayList<>();
List<Map.Entry<String, String>> sorted =
new ArrayList<>(
MOB_NAME_MAP.entrySet());
sorted.sort((a, b) ->
b.getKey().length()
- a.getKey().length());
String remaining = text;
for (Map.Entry<String, String> entry
: sorted) {
if (remaining.contains(
entry.getKey())) {
if (!types.contains(
entry.getValue())) {
types.add(entry.getValue());
}
remaining = remaining.replace(
entry.getKey(), "");
}
}
return types;
}
/**
* EntityType → 中文名
*/
private String reverseMobName(
String entityType) {
for (Map.Entry<String, String> entry
: MOB_NAME_MAP.entrySet()) {
if (entry.getValue()
.equals(entityType)) {
return entry.getKey();
}
}
return entityType;
}
/**
* 从条件文本中提取矿物名
* "采集任意矿物(煤炭除外)1组"
* → [COAL_ORE, IRON_ORE, GOLD_ORE, ...]
*/
private List<String> extractMaterialTypes(
String text) {
List<String> types = new ArrayList<>();
// 排除列表
List<String> excludes =
new ArrayList<>();
if (text.contains("煤炭除外")) {
excludes.add("COAL");
excludes.add("COAL_ORE");
excludes.add("DEEPSLATE_COAL_ORE");
}
// 通配符:"矿物" "矿石"
if (text.contains("矿物")
|| text.contains("矿石")) {
String[] ores = {
"COAL_ORE",
"DEEPSLATE_COAL_ORE",
"IRON_ORE",
"DEEPSLATE_IRON_ORE",
"COPPER_ORE",
"DEEPSLATE_COPPER_ORE",
"GOLD_ORE",
"DEEPSLATE_GOLD_ORE",
"DIAMOND_ORE",
"DEEPSLATE_DIAMOND_ORE",
"EMERALD_ORE",
"DEEPSLATE_EMERALD_ORE",
"LAPIS_ORE",
"DEEPSLATE_LAPIS_ORE",
"REDSTONE_ORE",
"DEEPSLATE_REDSTONE_ORE",
"NETHER_GOLD_ORE",
"NETHER_QUARTZ_ORE",
"ANCIENT_DEBRIS"
};
for (String ore : ores) {
if (!excludes.contains(ore)) {
types.add(ore);
}
}
return types;
}
// 单个矿物名
if (text.contains("煤炭"))
types.add("COAL");
if (text.contains("铁矿")
|| text.contains("铁锭"))
types.add("RAW_IRON");
if (text.contains("金矿")
|| text.contains("金锭"))
types.add("RAW_GOLD");
if (text.contains("钻石"))
types.add("DIAMOND");
if (text.contains("绿宝石"))
types.add("EMERALD");
if (text.contains("青金石"))
types.add("LAPIS_LAZULI");
if (text.contains("红石"))
types.add("REDSTONE");
if (text.contains("铜"))
types.add("RAW_COPPER");
if (text.contains("下界石英")
|| text.contains("石英"))
types.add("QUARTZ");
return types;
}
/**
* 从条件文本中提取目标群系分类
@@ -2108,6 +2842,16 @@ public class QuestTracker {
}
}
if (key.contains("村民")
&& key.contains("交易")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
return getCounter(
player, "VILLAGER_TRADE")
>= needed;
}
if (key.startsWith("KILL_PLAYER")) {
String[] p = key.split(":");
int n = p.length >= 2
@@ -2200,6 +2944,63 @@ public class QuestTracker {
}
return true;
}
// ===== 泛解析:击杀类中文条件 =====
if (key.contains("击杀") || key.contains("猎杀")
|| key.contains("消灭")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
List<String> mobTypes = extractMobTypes(key);
if (!mobTypes.isEmpty()) {
int total = 0;
for (String mobType : mobTypes) {
total += getCounter(player,
"KILL:" + mobType);
}
return total >= needed;
}
}
// ===== 泛解析:村民交易条件 =====
if (key.contains("交易")
&& (key.contains("村民")
|| key.contains("与村民"))) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
return getCounter(player, "TRADE")
>= needed;
}
// ===== 泛解析:采集/挖矿类条件 =====
if (key.contains("采集") || key.contains("挖")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
List<String> matTypes =
extractMaterialTypes(key);
if (!matTypes.isEmpty()) {
int total = 0;
for (String mat : matTypes) {
total += getCounter(player,
"MINE:" + mat);
}
return total >= needed;
}
}
// ===== 泛解析:合成类条件 =====
if (key.contains("合成") || key.contains("制作")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
return getCounter(player, "CRAFT")
>= needed;
}
// ===== 泛解析:钓鱼类条件 =====
if (key.contains("钓鱼") || key.contains("钓")) {
int needed = parseNumberFromText(key);
if (needed <= 0) needed = 1;
return getCounter(player, "FISH")
>= needed;
}
return false;
}
@@ -2271,6 +3072,12 @@ public class QuestTracker {
return 0;
}
public void onVillagerTrade(String player) {
String key = "VILLAGER_TRADE";
int cur = getCounter(player, key);
setCounter(player, key, cur + 1);
}
private long getCounterLong(
String player, String key) {
Object val = storage.getCondition(
@@ -4,30 +4,34 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
import org.bukkit.event.player
.PlayerResourcePackStatusEvent;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import java.util.concurrent
.ConcurrentHashMap;
public class RadioDownloadListener
implements Listener {
private final Main plugin;
private final Logger log;
private final Map<UUID, Integer> retryCount =
new ConcurrentHashMap<>();
private final Map<UUID, Integer> totalAttempts =
new ConcurrentHashMap<>();
private static final int MAX_RETRY = 3;
// 每位玩家最多总共尝试5次,防死循环
private static final int MAX_TOTAL = 5;
private final java.util.logging.Logger log;
private final ConcurrentHashMap<UUID, Integer>
failCount = new ConcurrentHashMap<>();
private final ConcurrentHashMap<UUID, Boolean>
blocked = new ConcurrentHashMap<>();
private static final int MAX_FAILED = 3;
public RadioDownloadListener(Main plugin) {
this.plugin = plugin;
this.log = plugin.getLogger();
}
public void onPlayerQuit(UUID uuid) {
failCount.remove(uuid);
blocked.remove(uuid);
}
@EventHandler
@@ -35,84 +39,142 @@ public class RadioDownloadListener
PlayerResourcePackStatusEvent e) {
Player p = e.getPlayer();
UUID uuid = p.getUniqueId();
String status = e.getStatus().name();
String status =
e.getStatus().name();
log.info("[Radio] " + p.getName()
+ " 资源包: " + status);
log.info("[Radio] === 资源包状态 ===");
log.info("[Radio] 玩家: "
+ p.getName());
log.info("[Radio] UUID: " + uuid);
log.info("[Radio] 状态: "
+ status);
log.info("[Radio] 原始状态枚举: "
+ e.getStatus().getClass()
.getSimpleName()
+ "."
+ e.getStatus().name());
if (blocked.containsKey(uuid)) {
log.info("[Radio] 已熔断,跳过: "
+ p.getName());
return;
}
switch (status) {
case "SUCCESS":
log.info("[Radio] " + p.getName()
+ " 资源包加载成功");
retryCount.remove(uuid);
totalAttempts.remove(uuid);
log.info("[Radio] "
+ p.getName()
+ " ✓ 资源包加载成功");
failCount.remove(uuid);
blocked.remove(uuid);
break;
case "FAILED_DOWNLOAD":
int retry = retryCount
.getOrDefault(uuid, 0) + 1;
int total = totalAttempts
.getOrDefault(uuid, 0) + 1;
retryCount.put(uuid, retry);
totalAttempts.put(uuid, total);
log.warning("[Radio] " + p.getName()
+ " 下载失败 (重试"
+ retry + "/" + MAX_RETRY
+ ", 总计" + total
+ "/" + MAX_TOTAL + ")");
if (total >= MAX_TOTAL) {
p.sendMessage(
"§7[Radio] §f资源包加载失败,跳过");
retryCount.remove(uuid);
totalAttempts.remove(uuid);
break;
}
if (retry > MAX_RETRY) {
retryCount.put(uuid, 0);
Bukkit.getScheduler()
.runTaskLater(plugin,
() -> {
if (p.isOnline()
&& plugin
.radio
!= null) {
plugin.radio
.sendResourcePack(
p);
}
}, 100L);
break;
}
p.sendMessage(
"§e[Radio] §f下载失败,重试中...");
Bukkit.getScheduler()
.runTaskLater(plugin,
() -> {
if (p.isOnline()
&& plugin.radio
!= null) {
plugin.radio
.sendResourcePack(
p);
}
}, 60L);
case "ACCEPTED":
log.info("[Radio] "
+ p.getName()
+ " 客户端已接受,"
+ "等待下载...");
break;
case "DECLINED":
log.info("[Radio] " + p.getName()
+ " 拒绝下载");
retryCount.remove(uuid);
totalAttempts.remove(uuid);
log.warning("[Radio] "
+ p.getName()
+ " ✗ 拒绝了资源包");
log.warning("[Radio] "
+ "可能原因: "
+ "1.客户端设置禁用资源包"
+ " 2.已缓存拒绝记录"
+ " 3.SSL证书不匹配");
handleFailure(p, uuid);
break;
case "FAILED_DOWNLOAD":
log.warning("[Radio] "
+ p.getName()
+ " ✗ 下载失败");
log.warning("[Radio] "
+ "可能原因: "
+ "1.URL无法访问"
+ " 2.SSL证书错误"
+ " 3.网络超时"
+ " 4.文件不存在(404)");
handleFailure(p, uuid);
break;
default:
retryCount.remove(uuid);
totalAttempts.remove(uuid);
log.info("[Radio] "
+ p.getName()
+ " 未知状态: "
+ status);
break;
}
}
}
private void handleFailure(
Player p, UUID uuid) {
int failNum = failCount
.getOrDefault(uuid, 0) + 1;
failCount.put(uuid, failNum);
log.warning("[Radio] "
+ p.getName()
+ " 失败次数: "
+ failNum + "/" + MAX_FAILED);
if (failNum >= MAX_FAILED) {
blocked.put(uuid, true);
log.severe("[Radio] "
+ p.getName()
+ " 已达最大失败次数,"
+ "触发踢出");
Bukkit.getScheduler()
.runTaskLater(plugin,
() -> {
Player kp =
Bukkit
.getPlayer(
uuid);
if (kp != null
&& kp
.isOnline()) {
kp.kickPlayer(
"§c资源包"
+ "加载失败"
+ "("
+ MAX_FAILED
+ "次),"
+ "请联系管理员");
}
}, 1L);
return;
}
p.sendMessage(
"§e§l[Radio] 下载失败,"
+ "重试... ("
+ failNum + "/"
+ MAX_FAILED + ")");
// 5秒后重试
Bukkit.getScheduler()
.runTaskLater(plugin,
() -> {
if (p.isOnline()
&& plugin
.radio
!= null) {
plugin.getLogger()
.info(
"[Radio] "
+ "第"
+ failNum
+ "次重试: "
+ p
.getName());
plugin.radio
.sendResourcePack(
p);
}
}, 100L);
}
}
+158 -85
View File
@@ -328,7 +328,7 @@ public class RadioManager {
("{\n \"pack\": {\n"
+ " \"pack_format\": 54,\n"
+ " \"description\":"
+ " \"Sdf1 Radio\"\n }\n}")
+ " \"为保证服务器正常运行,请勿拒绝资源包!\"\n }\n}")
.getBytes("UTF-8"));
} catch (Exception e) {
// ignore
@@ -412,15 +412,24 @@ public class RadioManager {
*/
public void sendResourcePack(Player p) {
UUID uuid = p.getUniqueId();
if (pendingPack.contains(uuid)) return;
if (pendingPack.contains(uuid)) {
plugin.getLogger().info(
"[Radio] 跳过(已等待): "
+ p.getName());
return;
}
pendingPack.add(uuid);
Bukkit.getScheduler().runTaskLater(
plugin, () -> {
Bukkit.getScheduler()
.runTaskLater(plugin, () -> {
pendingPack.remove(uuid);
}, 200L);
String url = getResourcePackUrl();
if (url == null || url.isEmpty()) {
plugin.getLogger().warning(
"[Radio] URL为空,"
+ "跳过发送: "
+ p.getName());
pendingPack.remove(uuid);
return;
}
@@ -429,98 +438,85 @@ public class RadioManager {
plugin.getDataFolder(),
"radiopack.zip");
if (!zip.exists()) {
plugin.getLogger().warning(
"[Radio] radiopack.zip不存在,"
+ "跳过: " + p.getName());
pendingPack.remove(uuid);
return;
}
// 异步:下载远程zip → 算hash → 比对本地hash
final Player fp = p;
final String finalUrl = url + "?t="
+ System.currentTimeMillis();
try {
byte[] data =
Files.readAllBytes(zip.toPath());
String hash = sha1(data);
long fileSize = zip.length();
Bukkit.getScheduler()
.runTaskAsynchronously(plugin, () -> {
try {
// 1. 本地hash
byte[] localData =
Files.readAllBytes(
zip.toPath());
String localHash =
sha1(localData);
plugin.getLogger().info(
"[Radio] === 发送资源包 ===");
plugin.getLogger().info(
"[Radio] 玩家: " + p.getName());
plugin.getLogger().info(
"[Radio] URL: " + url);
plugin.getLogger().info(
"[Radio] Hash: " + hash);
plugin.getLogger().info(
"[Radio] 文件大小: "
+ fileSize + " bytes ("
+ String.format("%.1f",
fileSize / 1024.0)
+ " KB)");
plugin.getLogger().info(
"[Radio] 客户端版本: "
+ p.getProtocolVersion());
// 2. 远程hash
byte[] remoteData =
downloadBytes(finalUrl);
String remoteHash =
sha1(remoteData);
sendPackWithHash(p, url, hash);
// 3. 比对
plugin.getLogger().info(
"[Radio] 本地hash: "
+ localHash);
plugin.getLogger().info(
"[Radio] 远程hash: "
+ remoteHash);
if (!localHash.equals(remoteHash)) {
plugin.getLogger().warning(
"[Radio] hash不一致,"
+ "跳过发送资源包");
plugin.getLogger().warning(
"[Radio] 请更新远程"
+ "radiopack.zip");
Bukkit.getScheduler()
.runTask(plugin, () -> {
fp.sendMessage(
"§7[Radio] §f资源包"
+ "版本不一致,"
+ "跳过安装");
});
return;
}
// 4. hash一致,发送给客户端
Bukkit.getScheduler()
.runTask(plugin, () -> {
sendPackWithHash(
fp, finalUrl,
remoteHash);
});
} catch (Exception e) {
plugin.getLogger().warning(
"[Radio] 校验失败: "
+ e.getMessage());
}
});
} catch (Exception e) {
plugin.getLogger().severe(
"[Radio] 发送异常: "
+ p.getName());
e.printStackTrace();
pendingPack.remove(uuid);
}
}
private void sendPackWithHash(
Player p, String url, String hash) {
UUID uuid = UUID.randomUUID();
// Paper 1.21: setResourcePack(UUID, String, String, Component, boolean)
private void sendPackWithHash(
Player p,
String url,
String hash) {
UUID uuid = UUID.fromString(
"a1b2c3d4-e5f6-7890-abcd-"
+ "ef1234567890");
try {
Object component = net.kyori.adventure
.text.Component.text("Sdf1 Radio");
Object component =
net.kyori.adventure.text
.Component.text(
"§d§l服务器需要自定义资源包,请您点击'继续'由客户端自动下载即可");
p.getClass().getMethod(
"setResourcePack",
UUID.class,
String.class,
String.class,
net.kyori.adventure.text
net.kyori.adventure
.text
.Component.class,
boolean.class)
.invoke(p, uuid, url,
hash, component, false);
.invoke(
p, uuid, url,
hash, component,
true);
plugin.getLogger().info(
"[Radio] 发送成功: "
+ p.getName());
+ p.getName()
+ " url="
+ url);
return;
} catch (Exception ignored) {
}
// 降级:旧版 String 签名
try {
p.getClass().getMethod(
"setResourcePack",
@@ -529,8 +525,11 @@ public class RadioManager {
String.class,
String.class,
boolean.class)
.invoke(p, uuid, url,
hash, "Sdf1 Radio", false);
.invoke(
p, uuid, url,
hash,
"Sdf1 Radio",
true);
plugin.getLogger().info(
"[Radio] 旧版签名成功: "
+ p.getName());
@@ -538,14 +537,15 @@ public class RadioManager {
} catch (Exception ignored) {
}
// 降级3参
try {
p.getClass().getMethod(
"setResourcePack",
UUID.class,
String.class,
String.class)
.invoke(p, uuid, url, hash);
.invoke(
p, uuid, url,
hash);
plugin.getLogger().info(
"[Radio] 3参签名成功: "
+ p.getName());
@@ -692,19 +692,86 @@ public class RadioManager {
}
public String getResourcePackUrl() {
// 有外部配置 → 随机生成URL
if (externalUrl != null
&& !externalUrl.isEmpty()) {
plugin.getLogger().info(
"[Radio] 使用外部地址: "
+ externalUrl);
return externalUrl;
&& !externalUrl.trim().isEmpty()) {
return generateRandomUrl(externalUrl);
}
plugin.getLogger().warning(
"[Radio] 未配置外部地址,"
+ "跳过资源包");
return null;
// 无配置 → 用内置HTTP
int port = 8080;
if (plugin.getConfig2() != null) {
port = plugin.getConfig2().httpPort;
}
return "http://127.0.0.1:"
+ port + "/radiopack.zip";
}
/**
* 从配置的域名列表中随机选一个
* 生成随机、带时间戳的完整资源包URL
*
* 规则:
* *.example.com
* → 随机子域+时间戳
* 例: a3f7k2.example.com/pack.zip?t=xxx&v=b9c1e4
*
* cdn.example.com (无*)
* → 直接使用+时间戳
* 例: cdn.example.com/pack.zip?t=xxx
*/
private String generateRandomUrl(
String raw) {
String[] parts = raw.split("\\|");
String pick =
parts[(int)(Math.random()
* parts.length)].trim();
// 去掉协议 https:// http://
String cleaned = pick
.replaceFirst("^https?://", "");
// 去掉路径 /path/a?v=1
if (cleaned.contains("/")) {
cleaned = cleaned.substring(
0, cleaned.indexOf('/'));
}
// 随机子域
String hex = Long.toHexString(
Double.doubleToLongBits(
Math.random()));
String rand = hex.length() > 6
? hex.substring(2, 8) : hex;
rand = rand.replaceAll(
"[^a-z0-9]", "x");
String host;
if (cleaned.startsWith("*")) {
host = rand
+ cleaned.substring(1);
} else {
host = cleaned;
}
String ts = String.valueOf(
System.currentTimeMillis());
String token = Integer.toHexString(
(int)(Math.random()
* 0xFFFFFF));
String url = "https://" + host
+ "/radiopack.zip"
+ "?t=" + ts
+ "&v=" + token;
plugin.getLogger().info(
"[Radio] 生成URL: "
+ pick + " → " + url);
return url;
}
// ========== 全服广播 ==========
public void startMainRadio() {
stopMainRadio();
@@ -847,6 +914,12 @@ public class RadioManager {
}
}
// 在 init() 或 registerEvents 中保存引用:
// this.listener = new RadioDownloadListener(plugin);
private RadioDownloadListener radioListener;
// ========== 重载 ==========
public void reload() {
stopMainRadio();
-137
View File
@@ -1,137 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
public class AFKManager {
private final Map<UUID, Long> lastAction = new HashMap<>();
private final Map<UUID, Integer> lastStage = new HashMap<>();
private final ConfigManager config;
private final JavaPlugin plugin;
public AFKManager(ConfigManager config, JavaPlugin plugin) {
this.config = config;
this.plugin = plugin;
}
public void startCheck() {
new BukkitRunnable() {
public void run() { tick(); }
}.runTaskTimer(plugin, 20L, 20L);
}
private void tick() {
if (!config.afkEnabled) return;
long now = System.currentTimeMillis();
for (Player p : Bukkit.getOnlinePlayers()) {
String name = p.getName();
if (!isLoggedIn(name)) continue;
if (config.getAfkWhitelist().contains(name)) continue;
UUID uuid = p.getUniqueId();
if (!lastAction.containsKey(uuid)) {
lastAction.put(uuid, now);
lastStage.put(uuid, 0);
continue;
}
long last = lastAction.get(uuid);
long idle = (now - last) / 1000;
int timeout = config.afkTimeout;
long remain = timeout - idle;
// 已超时:踢出
if (remain <= 0) {
p.kickPlayer("§c§l你已挂机 " + fmtSec(idle) + ",被踢出");
lastAction.remove(uuid);
lastStage.remove(uuid);
continue;
}
int stage = lastStage.getOrDefault(uuid, 0);
// === 阶段3:最后10秒 ===
// 刷屏倒计时 + 大标题 + 每秒音效
if (remain <= 10) {
if (stage < 3) {
lastStage.put(uuid, 3);
}
p.sendTitle("§e§l挂机了 " + fmtSec(idle),
"§c还剩 " + remain + " 秒踢出",
0, 22, 3);
playSound(p);
continue;
}
// === 阶段2:1分钟~10秒 ===
// 持续音效(每秒)+ 标题,聊天仅1次
if (remain <= 60) {
if (stage < 2) {
lastStage.put(uuid, 2);
p.sendMessage("§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" + fmtSec(idle) + "§f,还剩 §c" + fmtSec(remain) + "§f踢出");
}
p.sendTitle("§e§l挂机了 " + fmtSec(idle),
"§c还剩 " + fmtSec(remain) + " 踢出",
5, 25, 5);
playSound(p);
continue;
}
// === 阶段1:3分钟提醒 ===
// 仅1次:聊天 + 标题 + 3次音效循环
if (remain <= 180 && stage < 1) {
lastStage.put(uuid, 1);
p.sendMessage("§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" + fmtSec(idle) + "§f,还剩 §c" + fmtSec(remain) + "§f踢出");
p.sendTitle("§e§l挂机了 " + fmtSec(idle),
"§f还剩 " + fmtSec(remain) + " 踢出",
10, 60, 20);
for (int i = 0; i < 3; i++) {
final int delay = i * 10;
Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (p.isOnline()) playSound(p);
}, delay);
}
}
// 超过3分钟:重置阶段
if (remain > 180) {
lastStage.remove(uuid);
}
}
}
private void playSound(Player p) {
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.79f);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 0.79f);
}
public void recordAction(UUID uuid) {
lastAction.put(uuid, System.currentTimeMillis());
lastStage.put(uuid, 0);
}
public void remove(UUID uuid) {
lastAction.remove(uuid);
lastStage.remove(uuid);
}
private boolean isLoggedIn(String name) {
return ((Main) plugin).getLoggedIn().contains(name);
}
private String fmtSec(long sec) {
if (sec < 60) return sec + "秒";
long m = sec / 60;
long s = sec % 60;
return m + "分" + (s > 0 ? s + "秒" : "");
}
}
-146
View File
@@ -1,146 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class AccountRequestManager {
public enum ApprovalMode { AUTO, MANUAL, HYBRID }
public static class Request {
public final int id;
public final String requester;
public final String target;
public final String ip;
public final long time;
public String status;
public String denyReason;
public Request(int id, String requester, String target, String ip) {
this.id = id;
this.requester = requester;
this.target = target;
this.ip = ip;
this.time = System.currentTimeMillis();
this.status = "PENDING";
this.denyReason = "";
}
}
private final Main plugin;
private final Map<Integer, Request> requests = new ConcurrentHashMap<>();
private final Map<String, Integer> pendingByPlayer = new ConcurrentHashMap<>();
private int nextId = 1;
private ApprovalMode mode = ApprovalMode.MANUAL;
private int autoDelayMinutes = 60;
public AccountRequestManager(Main plugin) {
this.plugin = plugin;
}
public ApprovalMode getMode() { return mode; }
public void setMode(String m) {
try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); }
catch (Exception e) { this.mode = ApprovalMode.MANUAL; }
}
public void setAutoDelayMinutes(int min) {
this.autoDelayMinutes = min;
}
public Request createRequest(String requester, String target, String ip) {
int id = nextId++;
Request req = new Request(id, requester, target, ip);
requests.put(id, req);
pendingByPlayer.put(requester, id);
if (mode == ApprovalMode.AUTO) {
scheduleAutoApprove(id);
} else if (mode == ApprovalMode.HYBRID) {
String targetIp = (String) plugin.getDb().getField(target, "ip_address");
if (ip != null && ip.equals(targetIp)) {
scheduleAutoApprove(id);
}
}
return req;
}
private void scheduleAutoApprove(int id) {
long delayTicks = (long) autoDelayMinutes * 60L * 20L;
Request req = requests.get(id);
if (req == null) return;
String requesterName = req.requester;
String targetName = req.target;
plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过");
Bukkit.getScheduler().runTaskLater(plugin, () -> {
Request r = requests.get(id);
if (r != null && "PENDING".equals(r.status)) {
approveRequest(id);
plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过");
Player requester = Bukkit.getPlayer(requesterName);
if (requester != null && requester.isOnline()) {
requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!");
}
}
}, delayTicks);
}
public boolean approveRequest(int id) {
Request req = requests.get(id);
if (req == null || !"PENDING".equals(req.status)) return false;
req.status = "APPROVED";
plugin.getDb().deleteUser(req.target);
pendingByPlayer.remove(req.requester);
// 清背包+传送出生点+踢出
Player target = Bukkit.getPlayer(req.target);
if (target != null && target.isOnline()) {
target.getInventory().clear();
target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]);
target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]);
target.teleport(target.getWorld().getSpawnLocation());
target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册");
}
return true;
}
public boolean denyRequest(int id, String reason) {
Request req = requests.get(id);
if (req == null || !"PENDING".equals(req.status)) return false;
req.status = "DENIED";
req.denyReason = reason;
pendingByPlayer.remove(req.requester);
return true;
}
public List<Request> getPendingRequests() {
List<Request> list = new ArrayList<>();
for (Request r : requests.values()) {
if ("PENDING".equals(r.status)) list.add(r);
}
return list;
}
public Request getRequest(int id) {
return requests.get(id);
}
public boolean hasPending(String player) {
return pendingByPlayer.containsKey(player);
}
public void cancelPending(String player) {
Integer id = pendingByPlayer.remove(player);
if (id != null) {
Request r = requests.get(id);
if (r != null) r.status = "CANCELLED";
}
}
}
-594
View File
@@ -1,594 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ChatFilterManager {
private final JavaPlugin plugin;
private final List<String> whitelistUrls =
new ArrayList<>();
private final Set<String> whitelistPlayers =
new TreeSet<>(
String.CASE_INSENSITIVE_ORDER);
private final Map<Integer, String>
punishmentRules =
new LinkedHashMap<>();
private final Map<Integer, Integer>
punishmentDurations =
new LinkedHashMap<>();
private final Map<String, Integer>
violationCount =
new ConcurrentHashMap<>();
private final Map<String, Long>
mutedPlayers =
new ConcurrentHashMap<>();
private final Map<String, String> messages =
new LinkedHashMap<>();
private boolean notifyAdmin = true;
private boolean notifyAll = false;
private int muteDuration = 300;
private static final Pattern URL_PATTERN =
Pattern.compile(
"(?i)(?:https?://\\S+)"
+ "|(?:(?:[a-zA-Z0-9]"
+ "(?:[a-zA-Z0-9\\-]*"
+ "[a-zA-Z0-9])?\\.)+"
+ "[a-zA-Z]{2,})"
+ "(?:/\\S*)?");
private static final Pattern DOT_VARIANTS =
Pattern.compile(
"[\uff0e\u3002\u2025\u2026\u00b7]");
private static final Pattern COLOR_CODES =
Pattern.compile(
"\u00a7[0-9a-fk-orA-FK-OR]");
private static final Set<String> FILE_EXT;
static {
Set<String> s = new HashSet<>(
Arrays.asList(
"txt", "yml", "yaml",
"json", "xml", "java",
"class", "jar", "png",
"jpg", "jpeg", "gif",
"bmp", "svg", "ico",
"webp", "mp3", "mp4",
"avi", "mkv", "wav",
"flac", "ogg", "zip",
"rar", "7z", "tar",
"gz", "cfg", "conf",
"ini", "log", "db",
"sql", "sh", "bat",
"html", "htm", "css",
"js", "md", "pdf",
"dll", "so", "exe",
"bin", "c", "cpp",
"py", "rb", "go",
"rs", "kt", "lua",
"php", "asp", "jsp"));
FILE_EXT = Collections.unmodifiableSet(s);
}
public ChatFilterManager(JavaPlugin plugin) {
this.plugin = plugin;
}
// ========== 配置加载 ==========
public void loadConfig() {
whitelistUrls.clear();
whitelistPlayers.clear();
punishmentRules.clear();
punishmentDurations.clear();
messages.clear();
File f = new File(
plugin.getDataFolder(), "chat.txt");
if (!f.exists()) writeDefaultFile(f);
try (BufferedReader r =
new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8))) {
String section = "";
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("\uFEFF"))
line = line.substring(1);
String t = line.trim();
if (t.isEmpty()
|| t.startsWith("#"))
continue;
if (t.equals("白名单")
|| t.equals("白名单:")) {
section = "whitelist";
continue;
}
if (t.equals("处罚规则")
|| t.equals("处罚规则:")) {
section = "punishment";
continue;
}
if (t.equals("白名单玩家")
|| t.equals("白名单玩家:")) {
section = "players";
continue;
}
if (t.contains(":")) {
String[] kv = t.split(":", 2);
String k = kv[0].trim();
String v = kv[1].trim();
if (k.equals("通知管理员")) {
notifyAdmin = parseBool(v);
continue;
}
if (k.equals("全服通报")) {
notifyAll = parseBool(v);
continue;
}
if (k.equals("禁言时长")) {
try {
muteDuration =
Integer.parseInt(v);
} catch (Exception ignored) {
}
continue;
}
}
switch (section) {
case "whitelist":
whitelistUrls.add(
t.toLowerCase());
break;
case "punishment":
String[] parts = t.split(":");
if (parts.length >= 2) {
try {
int cnt =
Integer.parseInt(
parts[0]
.trim());
String type =
parts[1].trim()
.toLowerCase();
int dur =
parts.length >= 3
? Integer.parseInt(
parts[2]
.trim())
: muteDuration;
punishmentRules.put(
cnt, type);
punishmentDurations.put(
cnt, dur);
} catch (Exception ignored) {
}
}
break;
case "players":
whitelistPlayers.add(t);
break;
}
}
} catch (Exception e) {
plugin.getLogger().severe(
"[Sdf1_chat] 配置加载失败: "
+ e.getMessage());
}
// 从消息.txt读取chat_开头的消息
loadMessages();
}
private void loadMessages() {
File msgFile = new File(
plugin.getDataFolder(),
"消息.txt");
if (!msgFile.exists()) return;
try (BufferedReader mr =
new BufferedReader(
new InputStreamReader(
new FileInputStream(
msgFile),
StandardCharsets.UTF_8))) {
String mline;
while ((mline = mr.readLine()) != null) {
mline = mline.trim();
if (mline.isEmpty()
|| mline.startsWith("#"))
continue;
// 支持 = 和 : 两种分隔符
int eqIdx = mline.indexOf('=');
int coIdx = mline.indexOf(':');
int splitIdx = -1;
if (eqIdx >= 0 && coIdx >= 0) {
splitIdx = Math.min(eqIdx, coIdx);
} else if (eqIdx >= 0) {
splitIdx = eqIdx;
} else if (coIdx >= 0) {
splitIdx = coIdx;
}
if (splitIdx < 0) continue;
String k = mline.substring(0,
splitIdx).trim();
String v = mline.substring(
splitIdx + 1).trim();
if (k.startsWith("chat_")) {
messages.put(k, v);
}
}
} catch (Exception ignored) {
}
}
private void writeDefaultFile(File f) {
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# Sdf1_chat 聊天过滤配置");
pw.println("通知管理员: true");
pw.println("全服通报: false");
pw.println("禁言时长: 300");
pw.println();
pw.println("白名单:");
pw.println("*.minecraft.net");
pw.println("baidu.com");
pw.println("github.com");
pw.println();
pw.println("处罚规则:");
pw.println("1:warn:0");
pw.println("2:warn:0");
pw.println("3:mute:300");
pw.println("5:mute:600");
pw.println("8:kick:0");
pw.println("10:ban:0");
pw.println();
pw.println("白名单玩家:");
} catch (IOException ignored) {
}
}
public void saveConfig() {
File f = new File(
plugin.getDataFolder(), "chat.txt");
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.println("# Sdf1_chat 聊天过滤配置");
pw.println("通知管理员: " + notifyAdmin);
pw.println("全服通报: " + notifyAll);
pw.println("禁言时长: " + muteDuration);
pw.println();
pw.println("白名单:");
for (String u : whitelistUrls)
pw.println(u);
pw.println();
pw.println("处罚规则:");
for (Map.Entry<Integer, String> entry
: punishmentRules.entrySet()) {
int dur = punishmentDurations
.getOrDefault(entry.getKey(),
muteDuration);
pw.println(entry.getKey() + ":"
+ entry.getValue()
+ ":" + dur);
}
pw.println();
pw.println("白名单玩家:");
for (String p : whitelistPlayers)
pw.println(p);
} catch (IOException e) {
plugin.getLogger().severe(
"[Sdf1_chat] 保存失败: "
+ e.getMessage());
}
}
// ========== 消息 ==========
public String msg(String key,
String... args) {
String template = messages.getOrDefault(
key, key);
for (int i = 0; i < args.length - 1;
i += 2) {
template = template.replace(
"{" + args[i] + "}",
args[i + 1]);
}
return template;
}
// ========== URL检测 ==========
private String normalizeMessage(String msg) {
msg = COLOR_CODES.matcher(msg)
.replaceAll("");
msg = DOT_VARIANTS.matcher(msg)
.replaceAll(".");
return msg;
}
public List<String> extractUrls(
String message) {
String normalized =
normalizeMessage(message);
List<String> urls = new ArrayList<>();
Matcher m = URL_PATTERN
.matcher(normalized);
while (m.find()) {
String url = m.group().toLowerCase();
url = url.replaceAll(
"[\\s.,;!?,。;!?、)》」』】"
+ "\\]\\[\\((]+$",
"");
if (!url.isEmpty()) urls.add(url);
}
return urls;
}
public boolean isWhitelisted(String url) {
String clean = url
.replaceAll("^https?://", "")
.toLowerCase();
int colon = clean.indexOf(':');
int slash = clean.indexOf('/');
if (colon > 0 && (slash < 0
|| colon < slash)) {
clean = clean.substring(0, colon);
}
slash = clean.indexOf('/');
if (slash > 0) {
clean = clean.substring(0, slash);
}
for (String entry : whitelistUrls) {
String e = entry.toLowerCase().trim();
if (e.startsWith("*.")) {
String suffix = e.substring(1);
if (clean.endsWith(suffix)
|| clean.equals(
suffix.substring(1)))
return true;
} else {
if (clean.equals(e)
|| clean.endsWith(
"." + e))
return true;
}
}
return false;
}
public boolean isLikelyDomain(String url) {
int lastDot = url.lastIndexOf('.');
if (lastDot < 0
|| lastDot >= url.length() - 1)
return false;
String ext = url.substring(lastDot + 1)
.toLowerCase();
return !FILE_EXT.contains(ext);
}
// ========== 玩家操作 ==========
public boolean isMuted(String name) {
Long expiry = mutedPlayers.get(name);
if (expiry == null) return false;
if (System.currentTimeMillis()
>= expiry) {
mutedPlayers.remove(name);
return false;
}
return true;
}
public String fmtDuration(int seconds) {
if (seconds <= 0) return "0秒";
int day = seconds / 86400;
int hr = (seconds % 86400) / 3600;
int min = (seconds % 3600) / 60;
int sec = seconds % 60;
StringBuilder sb = new StringBuilder();
if (day > 0) sb.append(day).append("天");
if (hr > 0)
sb.append(hr).append("小时");
if (min > 0)
sb.append(min).append("分");
if (sec > 0 || sb.length() == 0)
sb.append(sec).append("秒");
return sb.toString();
}
public void applyPunishment(Player player,
int violation) {
String type = "warn";
int duration = muteDuration;
int maxRule = 0;
for (Map.Entry<Integer, String> entry
: punishmentRules.entrySet()) {
if (violation >= entry.getKey()
&& entry.getKey() > maxRule) {
maxRule = entry.getKey();
type = entry.getValue();
duration = punishmentDurations
.getOrDefault(
entry.getKey(),
muteDuration);
}
}
if (type.equals("mute")) {
if (isMuted(player.getName()))
return;
mutedPlayers.put(player.getName(),
System.currentTimeMillis()
+ (long) duration
* 1000L);
}
final String fType = type;
final int fDur = duration;
final int fV = violation;
final String fName = player.getName();
Bukkit.getScheduler().runTask(plugin,
() -> {
Player p = Bukkit.getPlayer(fName);
if (p == null) return;
switch (fType) {
case "warn":
// 警告不处罚,只提示
break;
case "mute":
p.sendMessage(
msg("chat_muted"));
break;
case "kick":
p.kickPlayer(
msg("chat_muted"));
break;
case "ban":
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"ban " + fName
+ " §c多次发送违规链接");
break;
case "banip":
InetSocketAddress addr =
p.getAddress();
if (addr != null) {
String ip = addr
.getAddress()
.getHostAddress();
Bukkit.dispatchCommand(
Bukkit.getConsoleSender(),
"ban-ip " + ip
+ " §c发送违规链接");
}
break;
}
});
}
public void unmutePlayer(String name) {
mutedPlayers.remove(name);
}
public void resetPlayer(String name) {
mutedPlayers.remove(name);
violationCount.remove(name);
}
public boolean isPlayerWhitelisted(
String name) {
return whitelistPlayers.contains(name);
}
public boolean isNotifyAdmin() {
return notifyAdmin;
}
public boolean isNotifyAll() {
return notifyAll;
}
public int getViolationCount(String name) {
return violationCount
.getOrDefault(name, 0);
}
public void incrementViolation(
String name) {
violationCount.put(name,
getViolationCount(name) + 1);
}
public List<String> getWhitelistUrls() {
return whitelistUrls;
}
public Set<String> getWhitelistPlayers() {
return whitelistPlayers;
}
public Map<Integer, String>
getPunishmentRules() {
return punishmentRules;
}
public Map<Integer, Integer>
getPunishmentDurations() {
return punishmentDurations;
}
public Map<String, Long>
getMutedPlayers() {
return mutedPlayers;
}
public void setNotifyAdmin(boolean v) {
notifyAdmin = v;
}
public void setNotifyAll(boolean v) {
notifyAll = v;
}
public void setMuteDuration(int v) {
muteDuration = v;
}
public void addUrl(String url) {
whitelistUrls.add(
url.toLowerCase().trim());
saveConfig();
}
public boolean removeUrl(String url) {
boolean r = whitelistUrls.remove(
url.toLowerCase().trim());
saveConfig();
return r;
}
public void addWhitelistPlayer(
String name) {
whitelistPlayers.add(name);
saveConfig();
}
public boolean removeWhitelistPlayer(
String name) {
boolean r =
whitelistPlayers.remove(name);
saveConfig();
return r;
}
public void cleanExpired() {
long now = System.currentTimeMillis();
mutedPlayers.entrySet().removeIf(
e -> e.getValue() <= now);
}
private boolean parseBool(String v) {
String s = v.toLowerCase().trim();
return s.equals("true")
|| s.equals("on")
|| s.equals("yes")
|| s.equals("1")
|| s.contains("开启")
|| s.contains("启用")
|| s.contains("是");
}
}
-823
View File
@@ -1,823 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.Map;
public class ChatInputManager {
public enum InputType {
NONE,
CHANGE_PWD_STEP1,
CHANGE_PWD_STEP2,
SET_EMAIL,
SET_EMAIL_VERIFY,
ADMIN_AUTH,
ADMIN_SET_POINTS,
ADMIN_SET_PWD,
ADMIN_DELETE_CONFIRM,
ADMIN_SET_AFK_TIME,
SMTP_CONFIG,
SMTP_VERIFY,
BACK_CHECKIN,
INVITE_INPUT_CODE,
TICKET_TITLE,
TICKET_DESC,
TICKET_SCORE,
TICKET_REJECT,
TICKET_ASSIGN,
TICKET_INVITE_PROVIDER,
TICKET_REMOVE_PROVIDER,
TICKET_COMPLETE,
TICKET_REPORT_ISSUE,
TICKET_REPLY,
TICKET_ADMIN_TYPE,
TICKET_ADMIN_TITLE,
TICKET_ADMIN_DESC,
TICKET_ADMIN_PROVIDER
}
public static class PlayerState {
public InputType type = InputType.NONE;
public String targetPlayer = "";
public String ticketType = "";
public String ticketTitle = "";
public String ticketDesc = "";
public int ticketId = 0;
public int smtpStep = 0;
public int scoreValue = 0;
}
private final Map<String, PlayerState> states =
new HashMap<>();
private Main plugin;
public PlayerState getState(Player p) {
return states.computeIfAbsent(
p.getName(),
k -> new PlayerState());
}
public void reset(Player p) {
states.remove(p.getName());
}
public void handleInput(Player p, String msg,
Main mainPlugin) {
if (this.plugin == null)
this.plugin = mainPlugin;
PlayerState state = getState(p);
ConfigManager config =
mainPlugin.getConfig2();
DatabaseManager db = mainPlugin.getDb();
switch (state.type) {
case CHANGE_PWD_STEP1: {
String name = p.getName();
String salt = (String) db.getField(
name, "password_salt");
String hash =
PasswordUtils.hash(msg, salt);
boolean mainOk =
db.checkPassword(name, hash);
if (mainOk) {
state.type =
InputType.CHANGE_PWD_STEP2;
state.ticketTitle = "main";
p.sendMessage(
"§e请输入新密码:");
return;
}
boolean tempOk =
db.checkPasswordOrTemp(
name, hash);
if (tempOk) {
state.type =
InputType.CHANGE_PWD_STEP2;
state.ticketTitle = "temp";
p.sendMessage(
"§e请输入新密码:");
return;
}
p.sendMessage(config.msg(
"password_wrong"));
return;
}
case CHANGE_PWD_STEP2: {
String name = p.getName();
if (!PasswordUtils.validate(msg)) {
p.sendMessage(config.msg(
"password_format_error"));
reset(p);
return;
}
String oldHash =
(String) db.getField(name,
"password_hash");
String oldSalt =
(String) db.getField(name,
"password_salt");
String newSalt =
PasswordUtils.generateSalt();
String newHash =
PasswordUtils.hash(
msg, newSalt);
if (newHash.equals(oldHash)) {
p.sendMessage(config.msg(
"password_same"));
reset(p);
return;
}
mainPlugin.recordPasswordChange(
name, oldHash, oldSalt);
db.setField(name,
"password_hash", newHash);
db.setField(name,
"password_salt", newSalt);
mainPlugin
.getNeedsPasswordChange()
.remove(name);
p.sendMessage(config.msg(
"password_changed"));
reset(p);
return;
}
case SET_EMAIL: {
if (!msg.contains("@")
|| !msg.contains(".")) {
p.sendMessage(config.msg(
"email_format_error"));
reset(p);
return;
}
String oldEmail = (String)
db.getField(p.getName(),
"email");
String emailAddr = msg.trim();
// 已绑定邮箱:验证登录密码
if (oldEmail != null
&& !oldEmail.isEmpty()) {
state.targetPlayer = emailAddr;
state.type =
InputType.SET_EMAIL_VERIFY;
state.ticketTitle =
"need_pwd";
p.sendMessage(
"§e您已绑定邮箱: "
+ oldEmail);
p.sendMessage(
"§e请验证登录密码以确认修改:");
return;
}
// 未绑定邮箱:直接发验证码
String code = String.valueOf(
(int) (Math.random()
* 900000
+ 100000));
state.targetPlayer = emailAddr;
state.ticketTitle = code;
state.type =
InputType.SET_EMAIL_VERIFY;
final String to = emailAddr;
final String c = code;
final Player fp = p;
Bukkit.getScheduler()
.runTaskAsynchronously(
mainPlugin, () -> {
boolean sent = mainPlugin
.getEmail()
.sendVerifyCode(
to, c);
Bukkit.getScheduler()
.runTask(mainPlugin,
() -> {
if (sent) {
fp.sendMessage(
"§a验证码已发送到 "
+ to);
fp.sendMessage(
"§e输入6位验证码(0取消):");
} else {
fp.sendMessage(
"§c发送失败");
reset(fp);
}
});
});
return;
}
case SET_EMAIL_VERIFY: {
if ("0".equals(msg.trim())
|| "取消".equals(
msg.trim())) {
p.sendMessage(
"§c邮箱设置已取消");
reset(p);
return;
}
String expected =
state.ticketTitle;
if (msg.trim().equals(expected)) {
db.setField(p.getName(),
"email",
state.targetPlayer);
p.sendMessage(
"§a邮箱已设置: "
+ state.targetPlayer);
reset(p);
} else {
p.sendMessage(
"§c验证码错误(0取消):");
}
return;
}
case ADMIN_AUTH: {
if (msg.equals(
config.adminPassword)) {
Bukkit.getScheduler().runTask(
mainPlugin, () ->
mainPlugin
.getGui()
.openAdmin(p));
} else {
p.sendMessage(
"§c管理密码错误");
}
reset(p);
return;
}
case ADMIN_SET_POINTS: {
try {
int pts = Integer.parseInt(
msg.trim());
db.setField(
state.targetPlayer,
"points", pts);
p.sendMessage("§a已将 "
+ state.targetPlayer
+ " 积分设为 " + pts);
} catch (
NumberFormatException e) {
p.sendMessage(
"§c请输入有效数字");
}
String tgt =
state.targetPlayer;
Bukkit.getScheduler().runTask(
mainPlugin, () ->
mainPlugin
.getGui()
.openUserDetail(
p, tgt));
reset(p);
return;
}
case ADMIN_SET_PWD: {
if (!PasswordUtils.validate(msg)) {
p.sendMessage(config.msg(
"password_format_error"));
reset(p);
return;
}
String tgt = state.targetPlayer;
String salt =
PasswordUtils
.generateSalt();
String hash =
PasswordUtils.hash(
msg, salt);
db.setField(tgt,
"temp_password", hash);
db.setField(tgt, "temp_pw_expire",
System.currentTimeMillis()
+ 300000L);
db.setField(tgt,
"temp_pw_used", 0);
p.sendMessage("§a已为 " + tgt
+ " 设置临时密码");
Player tp =
Bukkit.getPlayer(tgt);
if (tp != null && tp.isOnline())
tp.kickPlayer(
"§c管理员已设置临时密码,"
+ "请使用临时密码登录");
// 邮件通知
String emailAddr = (String)
db.getField(tgt, "email");
if (emailAddr != null
&& !emailAddr.isEmpty()) {
String time =
new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date());
String body = "玩家 " + tgt
+ ":\n您的密码已于 "
+ time
+ " 被管理员重置为临时密码。\n"
+ "如果是本人操作请忽略。\n"
+ "如非本人操作请立即联系管理员。";
final String to = emailAddr;
final String fBody = body;
final String fName = tgt;
Bukkit.getScheduler()
.runTaskAsynchronously(
mainPlugin, () ->
mainPlugin.getEmail()
.sendTempPassword(
to,
fName,
fBody));
}
reset(p);
return;
}
case ADMIN_DELETE_CONFIRM: {
if (msg.equals(
state.targetPlayer)) {
db.deleteUser(
state.targetPlayer);
p.sendMessage("§a已删除 "
+ state.targetPlayer);
Player tgt =
Bukkit.getPlayer(
state.targetPlayer);
if (tgt != null
&& tgt.isOnline())
tgt.kickPlayer(
"§c账号已被删除");
} else {
p.sendMessage(
"§c输入不匹配,已取消");
}
reset(p);
return;
}
case ADMIN_SET_AFK_TIME: {
try {
int minutes =
Integer.parseInt(
msg.trim());
config.afkTimeout =
minutes * 60;
config.saveSettings();
p.sendMessage(
"§a挂机时长已设为 "
+ minutes
+ " 分钟");
} catch (
NumberFormatException e) {
p.sendMessage(
"§c请输入有效数字");
}
reset(p);
return;
}
case SMTP_CONFIG: {
String m = msg.trim();
if ("000".equals(m)
|| "取消取消取消"
.equals(m)) {
p.sendMessage(
"§cSMTP配置已取消");
reset(p);
return;
}
if ("取消".equals(m)) {
p.sendMessage(
"§cSMTP配置已取消");
reset(p);
return;
}
int step = state.smtpStep;
if (step <= 1) {
if (!"0".equals(m))
config.setSmtp(
"smtp地址", m);
state.smtpStep = 2;
p.sendMessage("§e当前端口: "
+ config.getSmtp(
"smtp端口"));
p.sendMessage(
"§e输入端口(0跳过):");
} else if (step == 2) {
if (!"0".equals(m))
config.setSmtp(
"smtp端口", m);
state.smtpStep = 3;
p.sendMessage(
"§e输入发件邮箱:");
} else if (step == 3) {
if (!"0".equals(m))
config.setSmtp(
"smtp账号", m);
state.smtpStep = 4;
p.sendMessage(
"§e输入SMTP密码(0跳过):");
} else if (step == 4) {
if (!"0".equals(m))
config.setSmtp(
"smtp密码", m);
state.smtpStep = 5;
p.sendMessage(
"§e输入接收邮箱(留空发到发件邮箱):");
} else if (step == 5) {
String recv = m;
if (recv.isEmpty()
|| "0".equals(recv))
recv = config.getSmtp(
"smtp账号");
config.setSmtp(
"验证码接收邮箱", recv);
if (!config
.canSendSmtpVerify()) {
p.sendMessage(
"§c发送间隔不足1分钟");
reset(p);
return;
}
String code = config
.generateSmtpVerifyCode();
String addr = config.getSmtp(
"smtp地址");
String acct = config.getSmtp(
"smtp账号");
if (acct.isEmpty()
|| addr.isEmpty()) {
p.sendMessage(
"§c地址或账号为空");
reset(p);
return;
}
config.recordSmtpSendTime();
final String fRecv = recv;
final String fCode = code;
final Player fp = p;
Bukkit.getScheduler()
.runTaskAsynchronously(
mainPlugin, () -> {
Bukkit.getScheduler()
.runTask(
mainPlugin,
() -> fp.sendMessage(
"§e正在发送验证码..."));
boolean sent =
mainPlugin.getEmail()
.sendVerifyCode(
fRecv,
fCode);
final boolean fSent = sent;
Bukkit.getScheduler()
.runTask(
mainPlugin,
() -> {
if (fSent) {
state.type =
InputType
.SMTP_VERIFY;
state.smtpStep = 0;
fp.sendMessage(
"§a验证码已发送到 "
+ fRecv);
fp.sendMessage(
"§e输入6位验证码(0取消):");
} else {
fp.sendMessage(
"§c发送失败,查看控制台");
reset(fp);
}
});
});
}
return;
}
case SMTP_VERIFY: {
if ("0".equals(msg.trim())
|| "取消".equals(
msg.trim())) {
p.sendMessage(
"§cSMTP配置已取消");
reset(p);
return;
}
if (config.checkSmtpVerifyCode(
msg.trim())) {
config.saveSmtp();
config.clearSmtpVerifyCode();
String addr = config.getSmtp(
"smtp地址");
String port = config.getSmtp(
"smtp端口");
String acct = config.getSmtp(
"smtp账号");
String recv = config.getSmtp(
"验证码接收邮箱");
boolean ssl = config.smtpSsl;
p.sendMessage("§a§l=== SMTP配置完成 ===");
p.sendMessage("§7地址: §f" + addr);
p.sendMessage("§7端口: §f" + port);
p.sendMessage("§7账号: §f" + acct);
p.sendMessage("§7接收: §f" + recv);
p.sendMessage("§7加密: §f" + ssl);
p.sendMessage("§a§l====================");
reset(p);
} else {
p.sendMessage(
"§c验证码错误(0取消):");
}
return;
}
case BACK_CHECKIN: {
int cost = 20;
if (!mainPlugin.getDb()
.deductPoints(
p.getName(), cost)) {
p.sendMessage(
"§c积分不足,补签需 "
+ cost + " 积分");
reset(p);
return;
}
p.sendMessage(
"§a补签成功!扣除 "
+ cost + " 积分。"
+ "日期: " + msg);
reset(p);
return;
}
case INVITE_INPUT_CODE: {
String code = msg.trim();
String myName = p.getName();
String myCode =
(String) db.getField(
myName,
"invite_code");
if (code.isEmpty()) {
p.sendMessage(
"§c邀请码不能为空");
reset(p);
return;
}
if (code.equals(myCode)) {
p.sendMessage(
"§c不能用自己邀请码");
reset(p);
return;
}
boolean found = false;
for (Map<String, Object> u :
db.getAllUsers()) {
String uc = (String)
u.getOrDefault(
"invite_code", "");
if (code.equals(uc)) {
String inv = (String)
u.get("player_name");
db.setField(myName,
"invited_by", inv);
db.addPoints(inv, 50);
p.sendMessage(
"§a邀请成功!邀请人: "
+ inv);
Player ip =
Bukkit.getPlayer(inv);
if (ip != null
&& ip.isOnline())
ip.sendMessage("§a"
+ myName
+ " 使用了您的邀请码");
found = true;
break;
}
}
if (!found)
p.sendMessage(
"§c邀请码不存在");
reset(p);
return;
}
case TICKET_TITLE: {
state.ticketTitle = msg;
state.type =
InputType.TICKET_DESC;
p.sendMessage(
"§e请输入工单详细描述:");
return;
}
case TICKET_DESC: {
String tt = state.ticketType;
String tl = state.ticketTitle;
reset(p);
mainPlugin.getTicket()
.submitTicket(
p, tt, tl, msg);
return;
}
case TICKET_SCORE: {
int tid = state.ticketId;
try {
int sc = Integer.parseInt(
msg.trim());
reset(p);
mainPlugin.getTicket()
.confirmAndScore(
p, tid, sc);
} catch (
NumberFormatException e) {
reset(p);
p.sendMessage(
"§c请输入数字1-5");
}
return;
}
case TICKET_REJECT: {
int tid = state.ticketId;
reset(p);
mainPlugin.getTicket()
.rejectTicket(
p, tid, msg);
return;
}
case TICKET_ASSIGN: {
int tid = state.ticketId;
String[] parts = msg.trim()
.split("\\s+");
String prov = parts[0];
int price = 0;
if (parts.length >= 2) {
try {
price = Integer.parseInt(
parts[1]);
} catch (
NumberFormatException e) {
p.sendMessage(
"§c价格格式错误");
}
}
reset(p);
mainPlugin.getTicket()
.assignToProvider(
p, tid, prov,
price);
final int fTid = tid;
Bukkit.getScheduler().runTask(
mainPlugin, () ->
mainPlugin
.getTicket()
.openDetail(
p, fTid));
return;
}
case TICKET_INVITE_PROVIDER: {
String tgt = msg.trim();
reset(p);
if (!db.userExists(tgt)) {
p.sendMessage(
"§c玩家不存在");
return;
}
db.addServiceProvider(
tgt, "waiter");
p.sendMessage("§a已邀请 " + tgt
+ " 成为服务商");
return;
}
case TICKET_REMOVE_PROVIDER: {
String tgt = msg.trim();
reset(p);
if (!db.isServiceProvider(tgt)) {
p.sendMessage("§c" + tgt
+ " 不是服务商");
return;
}
db.removeServiceProvider(tgt);
p.sendMessage("§a已移除 " + tgt);
return;
}
case TICKET_COMPLETE: {
try {
int tid = Integer.parseInt(
msg.trim());
mainPlugin.getTicket()
.completeAsProvider(
p, tid);
} catch (
NumberFormatException e) {
p.sendMessage(
"§c请输入有效工单号");
}
reset(p);
return;
}
case TICKET_REPORT_ISSUE: {
String report = msg.trim();
reset(p);
p.sendMessage("§e异常已上报");
String tag = config.adminTag;
for (Player op :
Bukkit.getOnlinePlayers()) {
if (op.getScoreboardTags()
.contains(tag)) {
op.sendMessage(
"§c§l[工单异常] §f"
+ p.getName()
+ ": " + report);
}
}
return;
}
case TICKET_REPLY: {
int replyTid = state.ticketId;
reset(p);
mainPlugin.getTicket()
.replyTicket(
p, replyTid, msg);
return;
}
case TICKET_ADMIN_TYPE: {
state.ticketType = msg.trim();
state.type =
InputType
.TICKET_ADMIN_TITLE;
p.sendMessage(
"§e请输入工单标题:");
return;
}
case TICKET_ADMIN_TITLE: {
state.ticketTitle = msg;
state.type =
InputType
.TICKET_ADMIN_DESC;
p.sendMessage(
"§e请输入工单描述:");
return;
}
case TICKET_ADMIN_DESC: {
state.targetPlayer = msg;
state.type =
InputType
.TICKET_ADMIN_PROVIDER;
p.sendMessage(
"§e输入服务商名和价格:");
p.sendMessage(
"§7例: playerA 20");
return;
}
case TICKET_ADMIN_PROVIDER: {
String[] parts2 = msg.trim()
.split("\\s+");
String prov2 = parts2[0];
int price2 = 0;
if (parts2.length >= 2) {
try {
price2 = Integer.parseInt(
parts2[1]);
} catch (
NumberFormatException e) {
}
}
String t2 = state.ticketType;
String ti2 = state.ticketTitle;
String d2 = state.targetPlayer;
reset(p);
mainPlugin.getTicket()
.adminCreateAndAssign(
p, t2, ti2,
d2, prov2, price2);
return;
}
default:
reset(p);
}
}
}
-104
View File
@@ -1,104 +0,0 @@
package Sdf1_login;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CheckInManager {
private final Main plugin;
public CheckInManager(Main plugin) {
this.plugin = plugin;
}
public boolean isCheckedInToday(String name) {
String last = (String) plugin.getDb()
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
return today.equals(last);
}
public String checkIn(Player p) {
String name = p.getName();
if (isCheckedInToday(name)) {
return plugin.getConfig2().msg("checkin_already");
}
int streak = ((Number) plugin.getDb()
.getField(name, "checkin_streak")).intValue();
String lastDate = (String) plugin.getDb()
.getField(name, "last_checkin_date");
String today = new SimpleDateFormat("yyyy-MM-dd")
.format(new Date());
// 检查是否连续
if (lastDate != null && !lastDate.isEmpty()) {
try {
java.util.Date lastD = new SimpleDateFormat(
"yyyy-MM-dd").parse(lastDate);
java.util.Date todayD = new SimpleDateFormat(
"yyyy-MM-dd").parse(today);
long diff = todayD.getTime() - lastD.getTime();
if (diff > 86400000L * 2) {
streak = 0; // 断签
} else if (diff > 86400000L) {
streak++; // 连续
}
} catch (Exception ignored) {}
}
if (streak == 0) streak = 1;
else streak++;
double multi = 1.0;
if (streak >= 21) multi = 1.5;
else if (streak >= 14) multi = 1.3;
else if (streak >= 7) multi = 1.1;
int points = (int) (1 * multi);
plugin.getDb().addPoints(name, points);
plugin.getDb().setField(
name, "checkin_streak", streak);
plugin.getDb().setField(
name, "last_checkin_date", today);
int total = ((Number) plugin.getDb()
.getField(name, "total_checkin_days"))
.intValue();
plugin.getDb().setField(
name, "total_checkin_days", total + 1);
return plugin.getConfig2().msg("checkin_success",
"points", String.valueOf(points),
"streak", String.valueOf(streak),
"multi", String.valueOf(multi));
}
public String backCheckIn(Player p, String dateStr) {
String name = p.getName();
if (!plugin.getDb().deductPoints(name, 10)) {
return plugin.getConfig2().msg("backcheckin_no_point");
}
try {
java.util.Date target = new SimpleDateFormat(
"yyyy-MM-dd").parse(dateStr);
java.util.Date now = new SimpleDateFormat(
"yyyy-MM-dd").parse(
new SimpleDateFormat("yyyy-MM-dd")
.format(new Date()));
long diff = now.getTime() - target.getTime();
if (diff < 0 || diff > 3 * 86400000L) {
plugin.getDb().addPoints(name, 10); // 退还
return plugin.getConfig2().msg(
"backcheckin_expired");
}
plugin.getDb().addPoints(name, 1); // 补签也给1分
return plugin.getConfig2().msg("backcheckin_success");
} catch (Exception e) {
plugin.getDb().addPoints(name, 10);
return "§c[Sdf1_login] §f日期格式错误,请用 yyyy-MM-dd";
}
}
}
-451
View File
@@ -1,451 +0,0 @@
package Sdf1_login;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static java.lang.Double.parseDouble;
public class ConfigManager {
private final File dataFolder;
private final Map<String, String> messages = new LinkedHashMap<>();
private final Map<String, String> smtpSettings = new LinkedHashMap<>();
public String adminTag = "admin";
public String adminPassword = "qweasd";
public int maxAccountsPerIP = 3;
public String approvalMode = "manual";
public int autoApproveDelayMinutes = 30;
public int loginTimeout = 60;
public boolean afkEnabled = false;
public int afkTimeout = 300;
public boolean smtpSsl = true;
public String smtpVerifyCode = "";
public long smtpVerifyExpire = 0;
public long smtpLastSendTime = 0;
public String smtpConfiguringPlayer = "";
public ConfigManager(File dataFolder) {
this.dataFolder = dataFolder;
}
// ==================== 消息 ====================
public void loadMessages() {
messages.clear();
File file = new File(dataFolder, "消息.txt");
if (!file.exists()) createDefaultMessages(file);
loadMap(file, messages);
}
public String msg(String key) {
String val = messages.getOrDefault(key, key);
val = val.replace("<b>", "&l");
val = val.replace("</b>", "&r");
val = val.replace("<br>", "\n");
val = val.replace("\\n", "\n");
// &颜色码 转换为 §颜色码
val = val.replaceAll(
"&([0-9a-fk-orA-FK-OR])", "§$1");
return val;
}
private void createDefaultMessages(File f) {
List<String> L = new ArrayList<>();
L.add("# ==========================================");
L.add("# Sdf1_login 消息配置文件");
L.add("# ==========================================");
L.add("# 颜色代码使用 § 符号");
L.add("# §0黑色 §1深蓝 §2深绿 §3深青");
L.add("# §4深红 §5深紫 §6金色 §7灰色");
L.add("# §8深灰 §9蓝色 §a绿色 §b浅蓝");
L.add("# §c红色 §d粉色 §e黄色 §f白色");
L.add("# §l粗体 §n下划线 §o斜体 §r重置");
L.add("#");
L.add("# 占位符用 {xxx} 表示,运行时自动替换");
L.add("# 支持 <b>粗体</b> 标签");
L.add("# 支持 \\n 或 <br> 换行");
L.add("# 修改后执行 /sdf1_login reload 重载");
L.add("# ==========================================");
L.add("");
// --- 登录/注册 ---
L.add("# ---- 登录注册相关 ----");
L.add("# login_timeout: 登录超时被踢出时的提示");
L.add("login_timeout=§c登录超时,请重新登录");
L.add("# not_registered: 未注册玩家收到的提示");
L.add("not_registered=§e您尚未注册,请使用 /reg <密码>");
L.add("# not_logged_in: 已注册但未登录的提示");
L.add("not_logged_in=§e您尚未登录,请使用 /login <密码>");
L.add("# already_registered: 已注册玩家再次注册时的提示");
L.add("already_registered=§c您已注册过账号");
L.add("# already_logged_in: 已登录玩家再次登录时的提示");
L.add("already_logged_in=§c您已登录");
L.add("# reg_success: 注册成功提示");
L.add("reg_success=§a注册成功!欢迎来到服务器");
L.add("# login_success: 登录成功提示");
L.add("login_success=§a登录成功!");
L.add("# login_failed: 密码错误提示");
L.add("login_failed=§c密码错误,请重试");
L.add("");
// --- 密码 ---
L.add("# ---- 密码相关 ----");
L.add("# password_wrong: 旧密码验证失败");
L.add("password_wrong=§c密码错误");
L.add("# password_changed: 密码修改成功");
L.add("password_changed=§a密码修改成功!");
L.add("# password_format_error: 密码格式不符合要求");
L.add("password_format_error=§c密码需6位以上,含大小写字母和数字");
L.add("# password_same: 新密码与旧密码相同");
L.add("password_same=§c新密码不能与旧密码相同");
L.add("# reset_no_email: 找回密码时未绑定邮箱");
L.add("reset_no_email=§c您未绑定邮箱,无法找回密码");
L.add("# reset_sent: 临时密码已发送到邮箱");
L.add("reset_sent=§a临时密码已发送到您的邮箱,请查收");
L.add("");
// --- 签到积分 ---
L.add("# ---- 签到积分相关 ----");
L.add("# checkin_already: 今日已签到");
L.add("checkin_already=§c您今日已签到");
L.add("# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率");
L.add("checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi}");
L.add("# points_insufficient: 积分不足");
L.add("points_insufficient=§c积分不足,当前: {points}");
L.add("# points_purchase_success: 积分购买成功");
L.add("points_purchase_success=§a购买成功!消耗 {count} 积分");
L.add("");
// --- 挂机 ---
L.add("# ---- 挂机检测相关 ----");
L.add("# afk_set_enabled: 挂机检测开启");
L.add("afk_set_enabled=§a挂机检测已开启");
L.add("# afk_set_disabled: 挂机检测关闭");
L.add("afk_set_disabled=§c挂机检测已关闭");
L.add("# afk_kick: 因挂机被踢出");
L.add("afk_kick=§c因挂机过久被踢出");
L.add("");
// --- 邮箱 ---
L.add("# ---- 邮箱相关 ----");
L.add("# email_set: 邮箱设置成功");
L.add("email_set=§a邮箱设置成功:{email}");
L.add("# email_format_error: 邮箱格式错误");
L.add("email_format_error=§c邮箱格式不正确");
L.add("");
// --- 邀请 ---
L.add("# ---- 邀请相关 ----");
L.add("# invite_code_generated: 邀请码生成");
L.add("invite_code_generated=§a邀请码:{code}");
L.add("# invite_used: 使用邀请码成功");
L.add("invite_used=§a邀请成功,邀请人:{player}");
L.add("# invite_self: 使用自己的邀请码");
L.add("invite_self=§c不能使用自己的邀请码");
L.add("# invite_not_found: 邀请码不存在");
L.add("invite_not_found=§c邀请码不存在");
L.add("# invite_referral_bonus: 被邀请人签到后邀请人获得奖励");
L.add("invite_referral_bonus=§a邀请奖励!获得 {points} 积分");
L.add("");
// --- 礼包 ---
L.add("# ---- 新人礼包相关 ----");
L.add("# gift_not_ready: 礼包条件未满足");
L.add("gift_not_ready=§c礼包条件未满足");
L.add("# gift_claimed: 礼包领取成功");
L.add("gift_claimed=§a礼包领取成功!");
L.add("");
// --- 审批/IP ---
L.add("# ---- 审批与IP限制 ----");
L.add("# need_approval: 注册需审批");
L.add("need_approval=§e注册需审批 (工单#{id})");
L.add("# ip_max_accounts: IP注册已达上限");
L.add("ip_max_accounts=§cIP注册已达上限");
L.add("");
// --- 管理员 ---
L.add("# ---- 管理员操作 ----");
L.add("# admin_delete_confirm: 管理员删除账号确认提示");
L.add("admin_delete_confirm=§c请在聊天中输入玩家名确认删除");
L.add("");
// --- 聊天过滤 ---
L.add("# ---- 聊天过滤 ----");
L.add("# chat_muted: 禁言玩家尝试聊天");
L.add("chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天");
L.add("# chat_url_blocked: 发送违规链接被拦截");
L.add("# {url}=检测到的链接地址");
L.add("chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url}");
L.add("# chat_url_violation: 当前违规次数");
L.add("# {count}=当前违规次数");
L.add("chat_url_violation=§7当前违规次数: §e{count}");
L.add("# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数");
L.add("chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次)");
L.add("# chat_url_broadcast: 全服通报,{player}=玩家名");
L.add("chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚");
L.add("");
// --- 通用 ---
L.add("# ---- 通用 ----");
L.add("# 未知参数: 未知命令或权限不足时的提示");
L.add("未知参数=§c未知参数或权限不足");
L.add("# only_player: 仅玩家可用的命令");
L.add("# only_player=§c此命令仅玩家可用");
writeLines(f, L);
}
// ==================== SMTP ====================
public void loadSmtp() {
smtpSettings.clear();
File file = new File(dataFolder, "SMTP设置.txt");
if (!file.exists()) createDefaultSmtp(file);
loadMap(file, smtpSettings);
}
private double parseDouble(String s, double def) {
try { return Double.parseDouble(s.trim()); }
catch (Exception e) { return def; }
}
public String getSmtp(String key) {
return smtpSettings.getOrDefault(key, "");
}
/** 获取挂机白名单列表 */
public List<String> getAfkWhitelist() {
List<String> list = new ArrayList<>();
File file = new File(dataFolder, "挂机白名单.txt");
if (!file.exists()) {
try {
file.createNewFile();
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(file),
StandardCharsets.UTF_8))) {
w.write("# 每行一个玩家名,前面加 # 为注释");
w.newLine();
w.write("# 以下玩家不会被挂机踢出");
w.newLine();
}
} catch (IOException ignored) {}
}
try (BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(file),
StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
list.add(line);
}
}
} catch (IOException ignored) {}
return list;
}
/** 设置SMTP单项(聊天输入用) */
public void setSmtp(String key, String value) {
smtpSettings.put(key, value);
}
/** 将当前SMTP设置写回文件 */
public void saveSmtp() {
File file = new File(dataFolder, "SMTP设置.txt");
List<String> L = new ArrayList<>();
L.add("# ===== SMTP 邮件服务器配置 =====");
for (Map.Entry<String, String> e : smtpSettings.entrySet()) {
L.add(e.getKey() + "=" + e.getValue());
}
writeLines(file, L);
}
private void createDefaultSmtp(File f) {
List<String> L = new ArrayList<>();
L.add("# ===== SMTP 邮件服务器配置 =====");
L.add("smtp地址=smtp.example.com");
L.add("smtp端口=465");
L.add("smtp账号=your_email@example.com");
L.add("smtp密码=");
L.add("发件人名称=Sdf1_login");
L.add("smtp加密=true");
writeLines(f, L);
}
// ==================== 插件设置 ====================
public void loadSettings() {
File file = new File(dataFolder, "插件设置.txt");
if (!file.exists()) createDefaultSettings(file);
Map<String, String> m = new LinkedHashMap<>();
loadMap(file, m);
adminTag = m.getOrDefault("管理标签", adminTag);
adminPassword = m.getOrDefault("管理密码", adminPassword);
maxAccountsPerIP = parseInt(
m.getOrDefault("每IP最大账号数", "3"), 3);
approvalMode = m.getOrDefault("审批模式", approvalMode);
autoApproveDelayMinutes = parseInt(
m.getOrDefault("自动审批延迟分钟", "30"), 30);
loginTimeout = parseInt(
m.getOrDefault("登录超时秒数", "60"), 60);
afkEnabled = Boolean.parseBoolean(
m.getOrDefault("挂机踢出", "false"));
afkTimeout = parseInt(
m.getOrDefault("挂机超时秒数", "300"), 300);
smtpSsl = Boolean.parseBoolean(
m.getOrDefault("smtp加密", "true"));
providerPointsPerScore = parseDouble(
m.getOrDefault("服务商积分倍率", "2.0"),
2.0);
baseEconomyReward = parseInt(
m.getOrDefault("基础经济奖励", "100"),
100);
providerEconomyPerScore = parseDouble(
m.getOrDefault("经济奖励每评分", "50.0"),
50.0);
requesterPointsReward = parseInt(
m.getOrDefault("报单人积分奖励", "10"),
10);
}
public void saveSettings() {
File file = new File(dataFolder, "插件设置.txt");
List<String> L = new ArrayList<>();
L.add("# ===== Sdf1_login 插件设置 =====");
L.add("管理标签=" + adminTag);
L.add("管理密码=" + adminPassword);
L.add("每IP最大账号数=" + maxAccountsPerIP);
L.add("审批模式=" + approvalMode);
L.add("自动审批延迟分钟=" + autoApproveDelayMinutes);
L.add("登录超时秒数=" + loginTimeout);
L.add("挂机踢出=" + afkEnabled);
L.add("挂机超时秒数=" + afkTimeout);
// 垃圾站设置
L.add("# ---- 垃圾站 ----");
L.add("垃圾站_启用: "
+ garbageEnabled);
L.add("垃圾站_清理间隔秒: "
+ garbageInterval);
L.add("垃圾站_保留轮数: "
+ garbageMaxRounds);
writeLines(file, L);
}
public double providerPointsPerScore = 2.0;
public int baseEconomyReward = 100;
public double providerEconomyPerScore = 50.0;
public int requesterPointsReward = 10;
public boolean garbageEnabled = true;
public int garbageInterval = 300;
public int garbageMaxRounds = 1;
private void createDefaultSettings(File f) {
List<String> L = new ArrayList<>();
L.add("# ===== Sdf1_login 插件设置 =====");
L.add("管理标签=admin");
L.add("管理密码=qweasd");
L.add("每IP最大账号数=3");
L.add("审批模式=manual");
L.add("自动审批延迟分钟=30");
L.add("登录超时秒数=60");
L.add("挂机踢出=false");
L.add("挂机超时秒数=300");
L.add("服务商积分倍率=" + providerPointsPerScore);
L.add("基础经济奖励=" + baseEconomyReward);
L.add("经济奖励每评分=" + providerEconomyPerScore);
L.add("报单人积分奖励=" + requesterPointsReward);
writeLines(f, L);
}
// ==================== 通用读写 ====================
private void loadMap(File file, Map<String, String> map) {
try (BufferedReader r = new BufferedReader(
new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
int eq = line.indexOf('=');
if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim());
}
} catch (IOException e) {
System.err.println("[Sdf1_login] 读取失败: " + file.getName());
}
}
private void writeLines(File file, List<String> lines) {
try (BufferedWriter w = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
for (String l : lines) { w.write(l); w.newLine(); }
} catch (IOException e) {
System.err.println("[Sdf1_login] 写入失败: " + file.getName());
}
}
private int parseInt(String s, int def) {
try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; }
}
/**
* 生成6位验证码(字母+数字)
*/
public String generateSmtpVerifyCode() {
String chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789";
StringBuilder sb = new StringBuilder();
java.util.Random r = new java.util.Random();
for (int i = 0; i < 6; i++) {
sb.append(chars.charAt(r.nextInt(
chars.length())));
}
smtpVerifyCode = sb.toString();
smtpVerifyExpire =
System.currentTimeMillis() + 300000L;
return smtpVerifyCode;
}
/**
* 检查验证码是否有效
*/
public boolean checkSmtpVerifyCode(String code) {
if (System.currentTimeMillis() > smtpVerifyExpire) {
smtpVerifyCode = "";
return false;
}
return smtpVerifyCode.equalsIgnoreCase(code);
}
/**
* 清除验证码
*/
public void clearSmtpVerifyCode() {
smtpVerifyCode = "";
smtpVerifyExpire = 0;
}
/**
* 检查发送间隔是否>=1分钟
*/
public boolean canSendSmtpVerify() {
return System.currentTimeMillis()
- smtpLastSendTime >= 60000L;
}
public void recordSmtpSendTime() {
smtpLastSendTime = System.currentTimeMillis();
}
}
File diff suppressed because it is too large Load Diff
-210
View File
@@ -1,210 +0,0 @@
package Sdf1_login;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Base64;
import java.util.logging.Logger;
public class EmailManager {
private final ConfigManager config;
private final Logger logger;
/**
* 发送SMTP验证码
*/
public boolean sendVerifyCode(String to, String code) {
if (!isConfigured()) {
plugin.getLogger().severe(
"[Sdf1_login] SMTP未配置完整,"
+ "请检查SMTP设置.txt");
return false;
}
String subject =
"[Sdf1_login] SMTP配置验证码";
String body = "您的SMTP配置验证码是: "
+ code + "\n"
+ "验证码5分钟内有效。\n"
+ "如非本人操作请忽略此邮件。";
try {
return sendEmail(to, subject, body);
} catch (Exception e) {
plugin.getLogger().severe(
"[Sdf1_login] 邮件发送异常: "
+ e.getClass().getSimpleName()
+ ": " + e.getMessage());
if (e.getCause() != null) {
plugin.getLogger().severe(
"[Sdf1_login] 根因: "
+ e.getCause().getMessage());
}
return false;
}
}
public EmailManager(ConfigManager config) {
this.config = config;
this.logger = Logger.getLogger("Sdf1_login");
}
/**
* 检查SMTP是否已配置
*/
public boolean isConfigured() {
String host = config.getSmtp("smtp地址");
String user = config.getSmtp("发件邮箱");
String pass = config.getSmtp("授权码");
return host != null && !host.isEmpty()
&& user != null && !user.isEmpty()
&& pass != null && !pass.isEmpty();
}
/**
* 发送验证码,返回是否成功发起
*/
public boolean sendVerifyCode(String to, String player, String code) {
if (!isConfigured()) {
logger.warning("[Sdf1_login] SMTP未配置,无法发送验证码到 " + to);
return false;
}
logger.info("[Sdf1_login] 发送验证码到 " + to + " (玩家: " + player + ")");
return sendEmailSync(to, "Sdf1_login 验证码",
"玩家 " + player + " 的验证码: " + code);
}
/**
* 发送临时密码,返回是否成功发起
*/
public boolean sendTempPassword(String to, String player, String tempPwd) {
if (!isConfigured()) {
logger.warning("[Sdf1_login] SMTP未配置,无法发送临时密码到 " + to);
return false;
}
logger.info("[Sdf1_login] 发送临时密码到 " + to + " (玩家: " + player + ")");
return sendEmailSync(to, "Sdf1_login 临时密码",
"玩家 " + player + " 的临时密码: " + tempPwd + "\n请尽快登录并修改密码!");
}
private boolean sendEmailSync(String to, String subject, String body) {
final boolean[] result = {false};
Thread t = new Thread(() -> {
try {
result[0] = sendEmail(to, subject, body);
} catch (Exception e) {
logger.severe("[Sdf1_login] 邮件线程异常: " + e.getMessage());
}
}, "Sdf1_login-SMTP");
t.setDaemon(true);
t.start();
try { t.join(15000); } catch (InterruptedException ignored) {}
if (t.isAlive()) {
t.interrupt();
logger.severe("[Sdf1_login] SMTP超时(15秒)");
return false;
}
return result[0];
}
private boolean sendEmail(String to, String subject, String body) {
String host = config.getSmtp("smtp地址");
String portStr = config.getSmtp("smtp端口");
String user = config.getSmtp("发件邮箱");
String pass = config.getSmtp("授权码");
String fromName = config.getSmtp("发件人名");
int port;
try { port = Integer.parseInt(portStr); } catch (Exception e) { port = 465; }
try {
String encSubject = "=?UTF-8?B?" + Base64.getEncoder().encodeToString(subject.getBytes("UTF-8")) + "?=";
String encBody = Base64.getEncoder().encodeToString(body.getBytes("UTF-8"));
if (port == 465) {
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.setSoTimeout(10000);
doSmtp(socket, host, user, pass, to, encSubject, encBody, fromName);
socket.close();
} else {
Socket plainSocket = new Socket(host, port);
plainSocket.setSoTimeout(10000);
BufferedReader preReader = new BufferedReader(new InputStreamReader(plainSocket.getInputStream(), "UTF-8"));
PrintWriter preWriter = new PrintWriter(plainSocket.getOutputStream(), true);
checkResponse(preReader, "220");
sendLine(preWriter, "EHLO " + host);
checkResponse(preReader, "250");
sendLine(preWriter, "STARTTLS");
checkResponse(preReader, "220");
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket) factory.createSocket(plainSocket, host, port, true);
sslSocket.startHandshake();
doSmtp(sslSocket, host, user, pass, to, encSubject, encBody, fromName);
sslSocket.close();
}
logger.info("[Sdf1_login] 邮件发送成功 -> " + to);
return true;
} catch (Exception e) {
logger.severe("[Sdf1_login] 邮件异常: " + e.getMessage());
return false;
}
}
private void doSmtp(SSLSocket socket, String host, String user, String pass,
String to, String subject, String body, String fromName) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
OutputStream rawOut = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new java.io.BufferedOutputStream(rawOut), true);
checkResponse(reader, "220");
sendLine(writer, "EHLO " + host);
checkResponse(reader, "250");
sendLine(writer, "AUTH LOGIN");
checkResponse(reader, "334");
sendLine(writer, Base64.getEncoder().encodeToString(user.getBytes("UTF-8")));
checkResponse(reader, "334");
sendLine(writer, Base64.getEncoder().encodeToString(pass.getBytes("UTF-8")));
checkResponse(reader, "235");
sendLine(writer, "MAIL FROM:<" + user + ">");
checkResponse(reader, "250");
sendLine(writer, "RCPT TO:<" + to + ">");
checkResponse(reader, "250");
sendLine(writer, "DATA");
checkResponse(reader, "354");
sendLine(writer, "From: " + fromName + " <" + user + ">");
sendLine(writer, "To: <" + to + ">");
sendLine(writer, "Reply-To: <" + user + ">");
sendLine(writer, "Subject: " + subject);
sendLine(writer, "MIME-Version: 1.0");
sendLine(writer, "Content-Type: text/plain; charset=UTF-8");
sendLine(writer, "Content-Transfer-Encoding: base64");
sendLine(writer, "");
sendLine(writer, body);
sendLine(writer, ".");
checkResponse(reader, "250");
sendLine(writer, "QUIT");
}
private void sendLine(PrintWriter writer, String line) {
writer.print(line + "\r\n");
writer.flush();
}
private void checkResponse(BufferedReader reader, String expected) throws Exception {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
if (line.length() >= 4 && Character.isDigit(line.charAt(0)) && line.charAt(3) == ' ') break;
}
String resp = sb.toString().trim();
if (!resp.startsWith(expected)) {
if (resp.startsWith("535")) logger.severe("[Sdf1_login] SMTP 535 认证失败");
throw new Exception("SMTP响应异常: 期望 " + expected + ",实际: " + resp);
}
}
}
-861
View File
@@ -1,861 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.*;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.*;
public class GUIManager {
private final Main plugin;
public static final String T_MAIN =
"§6§l登录面板";
public static final String T_MY_INFO =
"§a§l我的信息";
public static final String T_ADMIN =
"§c§l管理员面板";
public static final String T_USER_MGMT =
"§e§l用户管理";
public static final String T_INVITE =
"§b§l邀请数据";
public static final String T_TASK_CENTER = "§d§l任务中心";
public static final String T_GIFT_STAGES = "§d§l新人任务";
public GUIManager(Main plugin) {
this.plugin = plugin;
}
public void openMain(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_MAIN);
fillBg(g);
g.setItem(10, mkItem(Material.PLAYER_HEAD,
"§a§l我的信息"));
g.setItem(12, mkItem(Material.PAPER,
"§b§l邀请数据"));
g.setItem(14, mkItem(Material.EMERALD,
"§d§l积分商城"));
g.setItem(16, mkItem(Material.CHEST,
"§e§l任务中心"));
g.setItem(18, mkItem(Material.BOOK,
"§6工单系统",
"§7提交bug、求助、举报"));
if (p.isOp() || isAdmin(p)) {
g.setItem(20, mkItem(Material.CHEST,
"§6垃圾回收站",
"§7查看和取回清理的物品"));
}
if (isAdmin(p)) {
g.setItem(22, mkItem(Material.REDSTONE_BLOCK,
"§c§l管理员面板"));
}
p.openInventory(g);
}
public void openMyInfo(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_MY_INFO);
fillBg(g);
Map<String, Object> user = plugin.getDb()
.getUser(p.getName());
boolean checkedIn = plugin.getCheckIn()
.isCheckedInToday(p.getName());
Material checkMat = checkedIn
? Material.EMERALD : Material.COAL;
String checkName = checkedIn
? "§a已签到" : "§7点击签到";
int streak = ((Number) user.getOrDefault(
"checkin_streak", 0)).intValue();
int points = ((Number) user.getOrDefault(
"points", 0)).intValue();
int totalDays = ((Number) user.getOrDefault(
"total_checkin_days", 0)).intValue();
String email = (String) user.getOrDefault(
"email", "");
g.setItem(4, mkItem(Material.NAME_TAG,
"§e" + p.getName()));
g.setItem(10, mkItem(checkMat, checkName,
"§7连续签到: " + streak + "天",
"§7累计签到: " + totalDays + "天",
"§7当前积分: " + points));
g.setItem(11, mkItem(Material.PAPER,
"§e修改密码", "§7双击修改密码"));
g.setItem(12, mkItem(Material.CLOCK,
"§e补签", "§7消耗10积分补签3天内"));
g.setItem(13, mkItem(Material.ENDER_PEARL,
"§e邀请码",
"§7你的邀请码: "
+ user.getOrDefault(
"invite_code", "无"),
"§7点击生成/查看"));
g.setItem(14, mkItem(Material.BOOK,
"§e邮箱: "
+ (email.isEmpty()
? "未绑定" : email),
"§7点击绑定邮箱"));
g.setItem(15, mkItem(Material.EMERALD_BLOCK,
"§a§l积分商城",
"§7当前: " + points + "积分"));
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
/**
* 打开账号删除选择面板
* 展示该玩家IP下所有账号,供选择删除
*/
public void openAccountDelete(Player p) {
String ip = plugin.getPlayerIP(p);
if (ip == null) {
p.sendMessage("§c无法获取你的IP地址");
return;
}
List<Map<String, Object>> accounts =
plugin.getDb().getAccountDetailsByIP(ip);
// 54格大箱子
org.bukkit.inventory.Inventory g =
org.bukkit.Bukkit.createInventory(
null, 54,
"§c§l选择要删除的账号");
// 填充玻璃面板
org.bukkit.inventory.ItemStack glass =
new org.bukkit.inventory.ItemStack(
org.bukkit.Material
.RED_STAINED_GLASS_PANE);
org.bukkit.inventory.meta.ItemMeta gm =
glass.getItemMeta();
if (gm != null) {
gm.setDisplayName(" ");
glass.setItemMeta(gm);
}
for (int i = 0; i < 54; i++) {
g.setItem(i, glass);
}
// 展示账号列表(从0号位开始放)
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm");
int slot = 0;
for (Map<String, Object> acc : accounts) {
if (slot >= 45) break;
String accName = (String) acc
.get("player_name");
long regTime = ((Number) acc
.get("register_time")).longValue();
String dateStr = regTime > 0
? sdf.format(
new java.util.Date(regTime))
: "未知";
// 当前登录的账号用不同颜色标记
boolean isCurrent =
accName.equalsIgnoreCase(
p.getName());
org.bukkit.inventory.ItemStack item =
new org.bukkit.inventory.ItemStack(
isCurrent
? org.bukkit.Material
.RED_WOOL
: org.bukkit.Material
.ORANGE_WOOL);
org.bukkit.inventory.meta.ItemMeta im =
item.getItemMeta();
if (im != null) {
im.setDisplayName(
(isCurrent ? "§c" : "§e")
+ accName);
im.setLore(java.util.Arrays.asList(
"§7注册时间: " + dateStr,
"§7IP: " + ip,
isCurrent
? "§c§l(当前账号)"
: "§a点击选择删除此账号"));
item.setItemMeta(im);
}
g.setItem(slot, item);
slot++;
}
// 49号位:返回
org.bukkit.inventory.ItemStack back =
new org.bukkit.inventory.ItemStack(
org.bukkit.Material.ARROW);
org.bukkit.inventory.meta.ItemMeta backMeta =
back.getItemMeta();
if (backMeta != null) {
backMeta.setDisplayName("§7返回");
back.setItemMeta(backMeta);
}
g.setItem(49, back);
p.openInventory(g);
}
/**
* 打开删除确认面板
*/
public void openDeleteConfirm(Player p,
String targetName) {
org.bukkit.inventory.Inventory g =
org.bukkit.Bukkit.createInventory(
null, 27,
"§c§l确认删除: " + targetName);
org.bukkit.inventory.ItemStack glass =
new org.bukkit.inventory.ItemStack(
org.bukkit.Material
.GRAY_STAINED_GLASS_PANE);
org.bukkit.inventory.meta.ItemMeta gm =
glass.getItemMeta();
if (gm != null) {
gm.setDisplayName(" ");
glass.setItemMeta(gm);
}
for (int i = 0; i < 27; i++) {
g.setItem(i, glass);
}
// 11号位:确认删除
org.bukkit.inventory.ItemStack confirm =
new org.bukkit.inventory.ItemStack(
org.bukkit.Material.LIME_WOOL);
org.bukkit.inventory.meta.ItemMeta cm =
confirm.getItemMeta();
if (cm != null) {
cm.setDisplayName("§a§l确认删除");
cm.setLore(java.util.Arrays.asList(
"§7删除账号: §c" + targetName,
"§c此操作不可撤销!"));
confirm.setItemMeta(cm);
}
g.setItem(11, confirm);
// 15号位:取消
org.bukkit.inventory.ItemStack cancel =
new org.bukkit.inventory.ItemStack(
org.bukkit.Material.RED_WOOL);
org.bukkit.inventory.meta.ItemMeta cml =
cancel.getItemMeta();
if (cml != null) {
cml.setDisplayName("§c§l取消");
cancel.setItemMeta(cml);
}
g.setItem(15, cancel);
p.openInventory(g);
}
public void openShopAdmin(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, "§c§l商城管理");
ItemStack glass = new ItemStack(
Material.GRAY_STAINED_GLASS_PANE);
ItemMeta glm = glass.getItemMeta();
if (glm != null) {
glm.setDisplayName(" ");
glass.setItemMeta(glm);
}
for (int i = 0; i < 54; i++)
g.setItem(i, glass);
g.setItem(49, mkItem(Material.ARROW,
"§7返回商城"));
g.setItem(50, mkItem(Material.EMERALD,
"§a添加商品",
"§7格式: /sdf1_login shopadd <名称> <价格> <命令>"));
g.setItem(51, mkItem(Material.BARRIER,
"§c删除商品",
"§7格式: /sdf1_login shopdel <序号>"));
p.openInventory(g);
}
public void openAdmin(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_ADMIN);
fillBg(g);
g.setItem(4, mkItem(Material.NAME_TAG,
"§e§l管理员面板"));
g.setItem(10, mkItem(Material.REDSTONE,
"§6SMTP配置",
"§7配置邮件服务器"));
g.setItem(11, mkItem(Material.ARMOR_STAND,
"§a用户管理",
"§7管理玩家数据"));
g.setItem(12, mkItem(Material.PLAYER_HEAD,
"§a§l我的信息"));
g.setItem(14, mkItem(Material.EMERALD,
"§d§l积分商城"));
boolean afkOn = plugin.getConfig2().afkEnabled;
int afkMin = plugin.getConfig2().afkTimeout / 60;
g.setItem(16, mkItem(
afkOn ? Material.REDSTONE_TORCH
: Material.TORCH,
afkOn ? "§a§l挂机踢出: 开启"
: "§c§l挂机踢出: 关闭",
"§7左键: 切换开关",
"§7右键: 设置时长",
"§7当前: " + afkMin + "分钟"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openInvite(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_INVITE);
fillBg(g);
String code = (String) plugin.getDb()
.getField(p.getName(), "invite_code");
if (code == null || code.isEmpty()) {
code = plugin.getInvite().generateCode(p);
}
int monthly = ((Number) plugin.getDb()
.getField(p.getName(),
"monthly_invite_count"))
.intValue();
g.setItem(10, mkItem(Material.PAPER,
"§b§l我的邀请码",
"§7" + code,
"",
"§e点击刷新邀请码"));
g.setItem(12, mkItem(Material.PLAYER_HEAD,
"§a本月邀请",
"§7已邀请 §e" + monthly + " §7人"));
g.setItem(16, mkItem(Material.BOOK,
"§e输入邀请码",
"§7绑定他人的邀请码",
"",
"§e点击输入"));
g.setItem(26, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
private String getInviteRate(int count) {
if (count > 15) return "60%";
if (count > 10) return "35%";
if (count > 5) return "15%";
if (count > 0) return "10%";
return "0%";
}
public void openUserManagement(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_USER_MGMT);
fillBg(g);
List<Map<String, Object>> users = plugin.getDb()
.getAllUsers();
int slot = 0;
for (Map<String, Object> user : users) {
if (slot >= 45) break;
String name = (String) user.get("player_name");
int pts = ((Number) user.getOrDefault(
"points", 0)).intValue();
int stage = ((Number) user.getOrDefault(
"gift_stage", 0)).intValue();
g.setItem(slot, mkItem(Material.PLAYER_HEAD,
"§e" + name,
"§7积分: " + pts
+ " 礼包阶段: " + stage,
"§7双击管理"));
slot++;
}
g.setItem(53, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openTaskCenter(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, T_TASK_CENTER);
fillBg(g);
QuestTracker qt =
plugin.getQuestTracker();
String player = p.getName();
// 新人任务进度
List<QuestTracker.QuestFile> newQuests =
qt.getQuests("新人任务");
int newTotal = newQuests.size();
int newDone =
qt.getCompletedStageCount(
player, newQuests);
QuestTracker.QuestFile newCurrent =
qt.getCurrentStage(
player, newQuests);
String newInfo = newDone + "/" + newTotal;
List<String> newLore = new ArrayList<>();
newLore.add("§7进度: §e" + newInfo);
if (newCurrent != null) {
newLore.add("§7当前: §f"
+ newCurrent.displayName);
if (newCurrent.hasConditions()) {
List<String> unmet =
qt.getUnmetConditions(
player,
newCurrent.conditions);
if (!unmet.isEmpty()) {
for (String c : unmet) {
newLore.add("§7 "
+ qt.getProgress(
player, c));
}
} else {
newLore.add("§a✓ 条件已满足");
}
}
} else {
newLore.add("§a§l全部完成!");
}
g.setItem(10, mkItem(Material.BOOK,
"§e§l新人任务 §7["
+ newInfo + "]",
newLore.toArray(new String[0])));
// 主线任务
List<QuestTracker.QuestFile> mainQuests =
qt.getQuests("主线任务");
g.setItem(12, mkItem(Material.COMPASS,
"§b§l主线任务 §7["
+ qt.getCompletedStageCount(
player, mainQuests)
+ "/" + mainQuests.size()
+ "]",
"§7查看主线任务列表"));
// 支线任务
List<QuestTracker.QuestFile> sideQuests =
qt.getQuests("支线任务");
g.setItem(14, mkItem(Material.PAPER,
"§a§l支线任务 §7["
+ qt.getCompletedStageCount(
player, sideQuests)
+ "/" + sideQuests.size()
+ "]",
"§7查看支线任务列表"));
g.setItem(16, mkItem(Material.EMERALD,
"§d§l每日签到",
"§7签到获取积分"));
// 打卡上报按钮(slot 21)
g.setItem(21, mkItem(
Material.EMERALD,
"§a§l打卡上报",
"§7验证当前阶段任务完成情况",
"§7满足条件将自动标记完成"));
// 返回按钮(slot 26)
g.setItem(26, mkItem(
Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openGiftStages(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_GIFT_STAGES);
fillBg(g);
QuestTracker qt =
plugin.getQuestTracker();
List<QuestTracker.QuestFile> quests =
qt.getQuests("新人任务");
int total = quests.size();
int completed =
qt.getCompletedStageCount(
p.getName(), quests);
QuestTracker.QuestFile current =
qt.getCurrentStage(
p.getName(), quests);
g.setItem(4, mkItem(Material.BOOK,
"§e§l新人任务进度",
"§7已完成: §e" + completed
+ "/" + total,
current != null
? "§7当前: §f"
+ current.displayName
: "§a§l全部完成!"));
for (int i = 0;
i < quests.size() && i < 44; i++) {
QuestTracker.QuestFile qf =
quests.get(i);
boolean done =
qt.isStageCompleted(
p.getName(), qf);
boolean isCurrent =
(current != null
&& current == qf);
Material mat;
List<String> lore = new ArrayList<>();
if (done) {
mat = Material.LIME_WOOL;
} else if (isCurrent) {
mat = Material.YELLOW_WOOL;
} else {
mat = Material.RED_WOOL;
}
if (!qf.details.isEmpty()) {
lore.add("§7---- 任务详情 ----");
for (String d : qf.details) {
lore.add("§f" + d);
}
}
if (qf.hasConditions()
&& (done || isCurrent)) {
lore.add("");
for (String c : qf.conditions) {
if (qt.isOptional(c))
continue;
if (qt.isConditionMet(
p.getName(), c)) {
lore.add("§7 §a✓ "
+ qt.getProgress(
p.getName(), c));
} else {
lore.add("§7 §c✗ "
+ qt.getProgress(
p.getName(), c));
}
}
}
if (!qf.rewards.isEmpty()) {
lore.add("");
lore.add("§7---- 奖励 ----");
for (String r : qf.rewards) {
lore.add("§a" + r);
}
}
if (done
&& !qf.rewards.isEmpty()
&& !qt.hasClaimed(
p.getName(), qf)) {
lore.add("");
lore.add("§e§l点击领取奖励");
}
if (done && qt.hasClaimed(
p.getName(), qf)) {
lore.add("");
lore.add("§7已领取奖励");
}
g.setItem(10 + i, mkItem(mat,
"§e" + qf.displayName,
lore.toArray(new String[0])));
}
// 返回按钮
g.setItem(53, mkItem(
Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openTaskList(Player p,
String folderName) {
QuestTracker qt =
plugin.getQuestTracker();
List<QuestTracker.QuestFile> tasks =
qt.getQuests(folderName);
int size = Math.min(54,
Math.max(54,
((tasks.size() / 9) + 1) * 9));
Inventory g = Bukkit.createInventory(
null, size,
"§d§l" + folderName);
fillBg(g);
for (int i = 0;
i < tasks.size()
&& i < 45; i++) {
QuestTracker.QuestFile qf =
tasks.get(i);
boolean done =
qt.isStageCompleted(
p.getName(), qf);
Material mat = done
? Material.LIME_WOOL
: Material.BOOK;
List<String> lore = new ArrayList<>();
// 解锁条件
if (qf.hasConditions()) {
lore.add("§7──── 解锁条件 ────");
for (String c : qf.conditions) {
if (qt.isOptional(c))
continue;
if (qt.isConditionMet(
p.getName(), c)) {
lore.add("§a ✓ "
+ qt.getProgress(
p.getName(), c));
} else {
lore.add("§c ✗ "
+ qt.getProgress(
p.getName(), c));
}
}
}
// 任务详情
if (!qf.details.isEmpty()) {
lore.add("");
lore.add("§7──── 任务详情 ────");
for (String d : qf.details) {
lore.add("§f" + d);
}
}
// 任务奖励
if (!qf.rewards.isEmpty()) {
lore.add("");
lore.add("§7──── 任务奖励 ────");
for (String r : qf.rewards) {
lore.add("§a" + r);
}
}
g.setItem(i, mkItem(mat,
(done ? "§a✓ " : "§e")
+ qf.displayName,
lore.toArray(new String[0])));
}
if (tasks.isEmpty()) {
g.setItem(22, mkItem(
Material.BARRIER,
"§7暂无任务"));
}
g.setItem(size - 1, mkItem(
Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openUserDetail(Player p, String target) {
Inventory g = Bukkit.createInventory(
null, 27, "§e§l管理: " + target);
fillBg(g);
Map<String, Object> user = plugin.getDb()
.getUser(target);
int pts = ((Number) user.getOrDefault(
"points", 0)).intValue();
String email = (String) user.getOrDefault(
"email", "");
g.setItem(4, mkItem(Material.NAME_TAG,
"§e" + target));
g.setItem(10, mkItem(Material.EMERALD,
"§a积分: " + pts,
"§7点击设置积分"));
g.setItem(11, mkItem(Material.PAPER,
"§e邮箱: "
+ (email.isEmpty() ? "无" : email)));
g.setItem(12, mkItem(Material.BOOK,
"§e设置密码"));
g.setItem(16, mkItem(Material.BARRIER,
"§c删除用户", "§7二次验证删除"));
// 雪球菜单检测(slot 14)
g.setItem(14, mkItem(
Material.SNOWBALL,
"§e§l雪球菜单",
"§7点击为该玩家发放菜单雪球"));
g.setItem(16, mkItem(
Material.ENDER_EYE,
"§b§l打卡记录",
"§7查看该玩家的群系打卡记录"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openCheckInRecords(
Player p, String target) {
Inventory g = Bukkit.createInventory(
null, 54,
"§b§l打卡记录: " + target);
fillBg(g);
QuestTracker qt =
plugin.getQuestTracker();
List<String> records =
qt.getPlayerRecords(target);
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
int slot = 0;
for (int i = records.size() - 1;
i >= 0 && slot < 45; i--) {
String r = records.get(i);
String[] parts = r.split("\\|");
if (parts.length < 7) continue;
String world = parts[1];
String biome = parts[2];
String category = parts[3];
String x = parts[4];
String y = parts[5];
String z = parts[6];
String timeStr = "";
if (parts.length >= 8) {
try {
long ts = Long.parseLong(
parts[7]);
timeStr = sdf.format(
new java.util.Date(
ts));
} catch (Exception e) {
timeStr = parts[7];
}
}
g.setItem(slot, mkItem(
Material.PAPER,
"§e" + category + " - "
+ biome,
"§7时间: " + timeStr,
"§7世界: " + world,
"§7坐标: " + x
+ ", " + y
+ ", " + z));
slot++;
}
if (slot == 0) {
g.setItem(22, mkItem(
Material.BARRIER,
"§7暂无打卡记录"));
}
g.setItem(53, mkItem(
Material.ARROW, "§7返回"));
p.openInventory(g);
}
/**
* 打开账号请求面板
* 玩家通过 /oa 命令触发
*/
public void openAccountRequest(Player p) {
org.bukkit.inventory.Inventory g =
org.bukkit.Bukkit.createInventory(
null, 27,
"§e§l账号请求");
// 填充玻璃面板
org.bukkit.inventory.ItemStack glass =
new org.bukkit.inventory.ItemStack(
org.bukkit.Material
.YELLOW_STAINED_GLASS_PANE);
org.bukkit.inventory.meta.ItemMeta gm =
glass.getItemMeta();
if (gm != null) {
gm.setDisplayName(" ");
glass.setItemMeta(gm);
}
for (int i = 0; i < 27; i++) {
g.setItem(i, glass);
}
// 10号位:申请删除账号
g.setItem(10, makeItem(
org.bukkit.Material.BARRIER,
"§c§l申请删除账号",
"§7点击后确认删除您的账号",
"§7此操作不可撤销"));
// 13号位:找回密码
g.setItem(13, makeItem(
org.bukkit.Material.TRIPWIRE_HOOK,
"§e§l找回密码",
"§7通过绑定邮箱获取临时密码",
"§7需要先绑定邮箱"));
// 16号位:联系管理员
g.setItem(16, makeItem(
org.bukkit.Material.EMERALD,
"§a§l联系管理员",
"§7遇到问题?向管理员提交工单"));
// 22号位:返回主菜单
g.setItem(22, makeItem(
org.bukkit.Material.ARROW,
"§7返回"));
p.openInventory(g);
}
/**
* 创建物品的工具方法
*/
private org.bukkit.inventory.ItemStack makeItem(
org.bukkit.Material mat,
String displayName,
String... lore) {
org.bukkit.inventory.ItemStack item =
new org.bukkit.inventory.ItemStack(mat);
org.bukkit.inventory.meta.ItemMeta meta =
item.getItemMeta();
if (meta != null) {
meta.setDisplayName(displayName);
if (lore.length > 0) {
meta.setLore(
java.util.Arrays.asList(lore));
}
item.setItemMeta(meta);
}
return item;
}
private boolean isAdmin(Player p) {
return p.getScoreboardTags().contains(
plugin.getConfig2().adminTag);
}
private void fillBg(Inventory inv) {
ItemStack gl = mkItem(
Material.GRAY_STAINED_GLASS_PANE, " ");
for (int i = 0; i < inv.getSize(); i++)
inv.setItem(i, gl);
}
private ItemStack mkItem(Material mat, String name,
String... lore) {
ItemStack it = new ItemStack(mat);
ItemMeta im = it.getItemMeta();
if (im != null) {
im.setDisplayName(name);
if (lore.length > 0)
im.setLore(Arrays.asList(lore));
it.setItemMeta(im);
}
return it;
}
}
-568
View File
@@ -1,568 +0,0 @@
package Sdf1_login;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.*;
public class GarbageManager {
private final Main plugin;
private Connection db;
private int cleanupInterval = 1800;
private int maxRounds = 1;
private boolean enabled = true;
private int currentRound = 0;
private final Map<String, Integer>
recyclePages = new HashMap<>();
public GarbageManager(Main plugin) {
this.plugin = plugin;
}
public void init() {
try {
Class.forName("org.sqlite.JDBC");
File dbFile = new File(
plugin.getDataFolder(),
"garbage.db");
db = DriverManager.getConnection(
"jdbc:sqlite:"
+ dbFile.getAbsolutePath());
Statement st = db.createStatement();
st.execute("PRAGMA journal_mode=WAL");
st.execute("CREATE TABLE IF NOT EXISTS "
+ "items ("
+ "id INTEGER PRIMARY KEY "
+ "AUTOINCREMENT,"
+ "item_data TEXT NOT NULL,"
+ "amount INTEGER DEFAULT 1,"
+ "round INTEGER DEFAULT 0,"
+ "create_time INTEGER NOT NULL"
+ ")");
st.execute("CREATE TABLE IF NOT EXISTS "
+ "config ("
+ "key TEXT PRIMARY KEY,"
+ "value TEXT NOT NULL"
+ ")");
st.close();
} catch (Exception e) {
plugin.getLogger().severe(
"[Garbage] DB失败: "
+ e.getMessage());
}
}
public void loadConfig() {
// 从 ConfigManager 读取
cleanupInterval =
plugin.getConfig2()
.garbageInterval;
enabled =
plugin.getConfig2()
.garbageEnabled;
maxRounds =
plugin.getConfig2()
.garbageMaxRounds;
// 从数据库读取当前轮次
try {
String val = getConfigValue("round");
if (val != null)
currentRound =
Integer.parseInt(val);
} catch (Exception ignored) {
}
plugin.getLogger().info(
"[Garbage] 间隔: "
+ cleanupInterval + "秒, "
+ "轮次保留: "
+ maxRounds + "轮, "
+ "启用: " + enabled);
}
private String getConfigValue(String key) {
try {
PreparedStatement ps = db.prepareStatement(
"SELECT value FROM config "
+ "WHERE key=?");
ps.setString(1, key);
ResultSet rs = ps.executeQuery();
String val = rs.next()
? rs.getString("value") : null;
rs.close();
ps.close();
return val;
} catch (Exception e) {
return null;
}
}
private void setConfigValue(String key,
String value) {
try {
PreparedStatement ps = db.prepareStatement(
"INSERT OR REPLACE INTO config "
+ "(key, value) "
+ "VALUES (?, ?)");
ps.setString(1, key);
ps.setString(2, value);
ps.executeUpdate();
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void startAutoCleanup() {
if (!enabled) return;
new BukkitRunnable() {
int countdown = 0;
@Override
public void run() {
// 在线人数≤0时暂停
if (Bukkit.getOnlinePlayers()
.isEmpty()) {
countdown = 0;
return;
}
// 重新读取配置(支持热更)
cleanupInterval =
plugin.getConfig2()
.garbageInterval;
int remain =
cleanupInterval - countdown;
if (remain == 300)
broadcast("§e§l[垃圾站] §f距离清理还有 §e5分钟");
else if (remain == 180)
broadcast("§e§l[垃圾站] §f距离清理还有 §e3分钟");
else if (remain == 60)
broadcast("§e§l[垃圾站] §f距离清理还有 §e1分钟");
else if (remain == 30)
broadcast("§e§l[垃圾站] §f距离清理还有 §e30秒");
else if (remain == 10)
broadcast("§e§l[垃圾站] §f距离清理还有 §e10秒");
else if (remain <= 5 && remain > 0)
broadcast("§e§l[垃圾站] §c" + remain + "§f...");
else if (remain == 0) {
doCleanup();
countdown = 0;
return;
}
countdown++;
}
}.runTaskTimer(plugin, 20L, 20L);
}
private void doCleanup() {
// 删除过期轮次的物品
int deleteRound =
currentRound - maxRounds;
if (deleteRound >= 0) {
int deleted = deleteOldItems(
deleteRound);
if (deleted > 0) {
broadcast("§7[垃圾站] §f永久清理了 §e"
+ deleted + " §f个过期物品");
}
}
// 递增轮次
currentRound++;
setConfigValue("round",
String.valueOf(currentRound));
// 收集当前掉落物
int count = collectItems();
broadcast("§a§l[垃圾站] §f清理完成,"
+ "共清理 §e" + count
+ " §f个掉落物,存入回收站");
// 打印下次清理时间
long nextMs = System.currentTimeMillis()
+ (long) cleanupInterval * 1000L;
String nextTime =
new SimpleDateFormat("HH:mm:ss")
.format(new Date(nextMs));
broadcast("§e§l[垃圾站] §f下次清理时间: §a"
+ nextTime);
}
public int collectItems() {
int count = 0;
for (org.bukkit.World w :
Bukkit.getWorlds()) {
Collection<Item> items =
w.getEntitiesByClass(Item.class);
for (Item item : items) {
ItemStack stack =
item.getItemStack();
if (stack == null
|| stack.getType()
== Material.AIR)
continue;
saveItem(stack);
item.remove();
count++;
}
}
return count;
}
public void saveItem(ItemStack stack) {
{
try {
String data =
stackToBase64(stack);
if (data.isEmpty()) return;
PreparedStatement ps = db.prepareStatement(
"INSERT INTO items "
+ "(item_data, amount, "
+ "round, create_time) "
+ "VALUES (?,?,?,?)");
ps.setString(1, data);
ps.setInt(2, stack.getAmount());
ps.setInt(3, currentRound);
ps.setLong(4,
System.currentTimeMillis());
ps.executeUpdate();
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}}
private int deleteOldItems(int beforeRound) {
try {
PreparedStatement ps = db.prepareStatement(
"DELETE FROM items "
+ "WHERE round <= ?");
ps.setInt(1, beforeRound);
int rows = ps.executeUpdate();
ps.close();
return rows;
} catch (Exception e) {
return 0;
}
}
// ========== 序列化 ==========
private String stackToBase64(ItemStack stack) {
try {
Map<String, Object> map =
stack.serialize();
String json = new Gson().toJson(map);
return Base64.getEncoder()
.encodeToString(
json.getBytes(
StandardCharsets.UTF_8));
} catch (Exception e) {
return "";
}
}
private ItemStack base64ToStack(String base64) {
try {
byte[] bytes = Base64.getDecoder()
.decode(base64);
String json = new String(bytes,
StandardCharsets.UTF_8);
Type type = new TypeToken<
Map<String, Object>>() {}
.getType();
Map<String, Object> map =
new Gson().fromJson(json, type);
map = fixNumberTypes(map);
return ItemStack.deserialize(map);
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> fixNumberTypes(
Map<String, Object> map) {
Map<String, Object> result =
new LinkedHashMap<>();
for (Map.Entry<String, Object> e
: map.entrySet()) {
Object v = e.getValue();
if (v instanceof Double) {
double d = (Double) v;
if (d == (long) d) {
result.put(e.getKey(), (long) d);
} else {
result.put(e.getKey(), d);
}
} else if (v instanceof Map) {
result.put(e.getKey(),
fixNumberTypes(
(Map<String, Object>) v));
} else if (v instanceof List) {
List<Object> list =
new ArrayList<>();
for (Object item : (List<?>) v) {
if (item instanceof Map) {
list.add(fixNumberTypes(
(Map<String, Object>) item));
} else {
list.add(item);
}
}
result.put(e.getKey(), list);
} else {
result.put(e.getKey(), v);
}
}
return result;
}
// ========== 回收站GUI ==========
public void openRecycle(Player p) {
openRecyclePage(p, 0);
}
public void openRecyclePage(Player p, int page) {
Inventory g = Bukkit.createInventory(
null, 54, "§6§l垃圾回收站");
// 获取全部存储物品
List<Map<String, Object>> allItems =
getStoredItems(999);
int total = allItems.size();
int pageSize = 40;
int totalPages = Math.max(1,
(int) Math.ceil(
(double) total / pageSize));
// 修正页码
if (page < 0) page = 0;
if (page >= totalPages)
page = totalPages - 1;
recyclePages.put(p.getName(), page);
// 填充物品(0-39 格 = 第1页,40-79 = 第2页...)
int start = page * pageSize;
int end = Math.min(start + pageSize,
total);
int slot = 0;
for (int i = start; i < end; i++) {
if (slot >= 40) break;
Map<String, Object> item =
allItems.get(i);
int id = ((Number) item.get("id"))
.intValue();
String data = (String) item
.get("item_data");
int amount = ((Number) item
.get("amount")).intValue();
ItemStack stack = base64ToStack(data);
if (stack != null) {
stack.setAmount(amount);
ItemMeta im =
stack.getItemMeta();
if (im != null) {
List<String> lore =
new ArrayList<>();
lore.add("§7ID: #" + id);
lore.add("§a点击取出");
im.setLore(lore);
stack.setItemMeta(im);
}
g.setItem(slot, stack);
}
slot++;
}
// 45-48 格:额外投放区(空位)
// 52-53 格:额外投放区(空位)
// 49 格:翻页-上一页
if (page > 0) {
g.setItem(49, mk(Material.ARROW,
"§e上一页 §7("
+ page + "/"
+ totalPages + ")"));
} else {
g.setItem(49, mk(Material.ARROW,
"§e第一页 §7("
+ "1/" + totalPages + ")"));
}
// 50 格:统计信息
g.setItem(50, mk(Material.CHEST,
"§e回收站统计",
"§7总存量: §f" + total + " 个",
"§7当前页: §f" + (page + 1)
+ "/" + totalPages));
// 51 格:翻页-下一页
boolean canNext = total >= pageSize
&& page < totalPages - 1;
if (canNext) {
g.setItem(51, mk(Material.ARROW,
"§e下一页 §7("
+ (page + 2) + "/"
+ totalPages + ")"));
} else {
g.setItem(51, mk(Material.ARROW,
"§7已是最后一页"));
}
p.openInventory(g);
}
public int getRecyclePage(String name) {
return recyclePages
.getOrDefault(name, 0);
}
public boolean handleClick(Player p,
String title, int slot) {
if (!title.equals("§6§l垃圾回收站"))
return false;
// 49:上一页
if (slot == 49) {
int page = recyclePages
.getOrDefault(p.getName(), 0);
if (page > 0)
openRecyclePage(p, page - 1);
return true;
}
// 51:下一页
if (slot == 51) {
int page = recyclePages
.getOrDefault(p.getName(), 0);
openRecyclePage(p, page + 1);
return true;
}
// 50/52/53:不处理
if (slot >= 49 && slot <= 53)
return true;
return true;
}
private List<Map<String, Object>>
getStoredItems(int limit) {
List<Map<String, Object>> list =
new ArrayList<>();
try {
PreparedStatement ps =
db.prepareStatement(
"SELECT id, item_data, "
+ "amount "
+ "FROM items "
+ "ORDER BY "
+ "create_time "
+ "DESC LIMIT ?");
ps.setInt(1, limit);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map<String, Object> row =
new LinkedHashMap<>();
row.put("id",
rs.getInt("id"));
row.put("item_data",
rs.getString("item_data"));
row.put("amount",
rs.getInt("amount"));
list.add(row);
}
rs.close();
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
public boolean removeItem(int id) {
try {
PreparedStatement ps =
db.prepareStatement(
"DELETE FROM items "
+ "WHERE id=?");
ps.setInt(1, id);
int rows = ps.executeUpdate();
ps.close();
return rows > 0;
} catch (Exception e) {
return false;
}
}
public int getItemCount() {
try {
Statement st = db.createStatement();
ResultSet rs = st.executeQuery(
"SELECT COUNT(*) FROM items");
int count = 0;
if (rs.next())
count = rs.getInt(1);
rs.close();
st.close();
return count;
} catch (Exception e) {
return 0;
}
}
private void broadcast(String msg) {
// 同步打印到控制台
plugin.getLogger().info(
msg.replaceAll("§[0-9a-fk-orA-FK-OR]", ""));
// 同步打印到聊天栏
Bukkit.broadcastMessage(msg);
}
private ItemStack mk(Material mat,
String name, String... lore) {
ItemStack it = new ItemStack(mat);
ItemMeta im = it.getItemMeta();
if (im != null) {
im.setDisplayName(name);
if (lore.length > 0) {
List<String> list =
new ArrayList<>();
for (String l : lore)
if (!l.isEmpty()) list.add(l);
if (!list.isEmpty())
im.setLore(list);
}
it.setItemMeta(im);
}
return it;
}
public void close() {
try {
if (db != null && !db.isClosed())
db.close();
} catch (Exception ignored) {
}
}
}
-296
View File
@@ -1,296 +0,0 @@
package Sdf1_login;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class GiftManager {
private final File giftFolder;
private final Main plugin;
private final List<Map<String, Object>> stages =
new ArrayList<>();
public GiftManager(Main plugin) {
this.plugin = plugin;
this.giftFolder = new File(
plugin.getDataFolder(), "新人礼包");
}
public void loadStages() {
stages.clear();
if (!giftFolder.exists()) {
giftFolder.mkdirs();
writeDefaultStages();
}
File[] files = giftFolder.listFiles(
(dir, name) -> name.endsWith(".md"));
if (files == null) return;
Arrays.sort(files);
for (File f : files) {
stages.add(parseMarkdown(f));
}
}
private Map<String, Object> parseMarkdown(File f) {
Map<String, Object> stage = new LinkedHashMap<>();
stage.put("stage", stages.size() + 1);
stage.put("name",
f.getName().replace(".md", ""));
stage.put("rewards", new ArrayList<String>());
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
List<String> rewards = new ArrayList<>();
while ((line = r.readLine()) != null) {
if (line.contains("礼包内容")
|| line.contains("奖励")
|| line.contains("任务内容")) {
int colon = line.indexOf(':');
if (colon < 0)
colon = line.indexOf('\uff1a');
if (colon >= 0) {
rewards.add(
line.substring(colon + 1)
.trim());
}
}
}
stage.put("rewards", rewards);
r.close();
} catch (Exception e) {
e.printStackTrace();
}
return stage;
}
private void writeStage(String name, String content) {
File f = new File(giftFolder, name);
if (f.exists()) return;
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(f),
StandardCharsets.UTF_8))) {
pw.print(content);
} catch (IOException ignored) {}
}
private void writeDefaultStages() {
writeStage("第一阶段.md",
"# 第一阶段\n\n## 领取条件\n\n- 无前级条件\n\n## 礼包内容\n\n- 皮革套装 × 1\n- 木制武器工具套装 × 1\n");
writeStage("第二阶段.md",
"# 第二阶段\n\n## 领取条件\n\n- 在线时长>1小时\n\n## 礼包内容\n\n- 500 游戏币\n");
writeStage("第三阶段.md",
"# 第三阶段\n\n## 领取条件\n\n- 破坏100个方块\n- 在线>1天\n\n## 礼包内容\n\n- 1500 游戏币\n- 铁质工具套装 × 1\n- 铁质防具 × 1\n");
writeStage("第四阶段.md",
"# 第四阶段\n\n## 领取条件\n\n- 破坏/放置300方块\n- 在线≥7天\n\n## 礼包内容\n\n- 钻石套装 × 1\n- 2000 游戏币\n");
}
/** 从任意文件夹加载任务列表 */
public List<Map<String, Object>> loadTasksFromFolder(
File folder) {
List<Map<String, Object>> tasks =
new ArrayList<>();
if (!folder.exists() || !folder.isDirectory())
return tasks;
File[] files = folder.listFiles(
(dir, name) -> name.endsWith(".md"));
if (files == null) return tasks;
Arrays.sort(files);
for (File f : files) {
Map<String, Object> task =
new LinkedHashMap<>();
task.put("name",
f.getName().replace(".md", ""));
task.put("conditions",
new ArrayList<String>());
task.put("rewards",
new ArrayList<String>());
try {
BufferedReader r = new BufferedReader(
new InputStreamReader(
new FileInputStream(f),
StandardCharsets.UTF_8));
String line;
List<String> current = null;
while ((line = r.readLine()) != null) {
if (line.startsWith("# ")) {
task.put("name",
line.substring(2)
.trim());
} else if (line.contains("条件")
|| line.contains("领取条件")
|| line.contains("完成条件")) {
current = (List<String>) task
.get("conditions");
} else if (line.contains("内容")
|| line.contains("奖励")
|| line.contains("任务内容")
|| line.contains("礼包内容")) {
current = (List<String>) task
.get("rewards");
} else if (line.startsWith("- ")
&& current != null) {
current.add(line.substring(2)
.trim());
}
}
r.close();
} catch (Exception e) {
e.printStackTrace();
}
tasks.add(task);
}
return tasks;
}
public boolean canClaim(Player p, int stage) {
Map<String, Object> user = plugin.getDb()
.getUser(p.getName());
if (user.isEmpty()) return false;
int curStage = ((Number) user.getOrDefault(
"gift_stage", 0)).intValue();
if (stage > curStage + 1) return false;
if (stage < 1 || stage > stages.size())
return false;
String claimed = (String) user.getOrDefault(
"gift_claimed", "");
if (claimed.contains("[" + stage + "]"))
return false;
// debug 绕过:检查 tasks_completed
String completed = (String) user.getOrDefault(
"tasks_completed", "");
if (completed.contains("[" + stage + "]"))
return true;
// 正常条件检查
long dbTime = ((Number) user.getOrDefault(
"total_online_time", 0L)).longValue();
long sessionTime = 0;
Object lastObj = plugin.getDb().getField(
p.getName(), "last_online_check");
if (lastObj != null && plugin.getLoggedIn()
.contains(p.getName())) {
long last = ((Number) lastObj).longValue();
if (last > 0) {
sessionTime =
System.currentTimeMillis() - last;
}
}
long onlineTime = dbTime + sessionTime;
int broken = ((Number) user.getOrDefault(
"blocks_broken", 0)).intValue();
int placed = ((Number) user.getOrDefault(
"blocks_placed", 0)).intValue();
switch (stage) {
case 1: return true;
case 2: return onlineTime >= 3600000;
case 3: return broken >= 100
&& onlineTime >= 86400000L;
case 4: return (broken + placed) >= 300
&& onlineTime >= 604800000L;
default: return false;
}
}
public void claimReward(Player p, int stage) {
if (!canClaim(p, stage)) return;
Map<String, Object> user = plugin.getDb()
.getUser(p.getName());
String claimed = (String) user.getOrDefault(
"gift_claimed", "");
claimed += "[" + stage + "]";
plugin.getDb().setField(
p.getName(), "gift_claimed", claimed);
plugin.getDb().setField(
p.getName(), "gift_stage",
Math.max(stage,
((Number) user.getOrDefault(
"gift_stage", 0))
.intValue()));
if (stage == 1) {
giveItem(p, Material.LEATHER_HELMET);
giveItem(p, Material.LEATHER_CHESTPLATE);
giveItem(p, Material.LEATHER_LEGGINGS);
giveItem(p, Material.LEATHER_BOOTS);
giveItem(p, Material.WOODEN_SWORD);
giveItem(p, Material.WOODEN_PICKAXE);
giveItem(p, Material.WOODEN_AXE);
giveItem(p, Material.WOODEN_SHOVEL);
} else if (stage == 2) {
if (plugin.getEconomy() != null) {
double before = plugin.getEconomy()
.getBalance(p);
plugin.getEconomy()
.depositPlayer(p, 500);
double after = plugin.getEconomy()
.getBalance(p);
p.sendMessage("§a§l[礼包] §f游戏币: §e"
+ (long) before
+ "§f → §a" + (long) after);
}
} else if (stage == 3) {
if (plugin.getEconomy() != null) {
double before = plugin.getEconomy()
.getBalance(p);
plugin.getEconomy()
.depositPlayer(p, 1500);
double after = plugin.getEconomy()
.getBalance(p);
p.sendMessage("§a§l[礼包] §f游戏币: §e"
+ (long) before
+ "§f → §a" + (long) after);
}
giveItem(p, Material.IRON_PICKAXE);
giveItem(p, Material.IRON_AXE);
giveItem(p, Material.IRON_SWORD);
giveItem(p, Material.IRON_HELMET);
giveItem(p, Material.IRON_CHESTPLATE);
giveItem(p, Material.IRON_LEGGINGS);
giveItem(p, Material.IRON_BOOTS);
} else if (stage == 4) {
if (plugin.getEconomy() != null) {
double before = plugin.getEconomy()
.getBalance(p);
plugin.getEconomy()
.depositPlayer(p, 2000);
double after = plugin.getEconomy()
.getBalance(p);
p.sendMessage("§a§l[礼包] §f游戏币: §e"
+ (long) before
+ "§f → §a" + (long) after);
}
giveItem(p, Material.DIAMOND_HELMET);
giveItem(p, Material.DIAMOND_CHESTPLATE);
giveItem(p, Material.DIAMOND_LEGGINGS);
giveItem(p, Material.DIAMOND_BOOTS);
}
p.sendMessage(plugin.getConfig2().msg(
"gift_claimed"));
}
private void giveItem(Player p, Material mat) {
p.getInventory().addItem(new ItemStack(mat));
}
public List<Map<String, Object>> getStages() {
return stages;
}
public int getMaxStage() {
return stages.size();
}
}
-36
View File
@@ -1,36 +0,0 @@
package Sdf1_login;
import java.util.List;
public class IPGroupManager {
private final Main plugin;
private int maxAccounts;
public IPGroupManager(Main plugin, int maxAccounts) {
this.plugin = plugin;
this.maxAccounts = maxAccounts;
}
public int getMaxAccounts() {
return maxAccounts;
}
public void setMaxAccounts(int max) {
this.maxAccounts = max;
}
public List<String> getAccountsByIP(String ip) {
return plugin.getDb().getAccountsByIP(ip);
}
public int getAccountCount(String ip) {
if (ip == null) return 0;
return getAccountsByIP(ip).size();
}
public boolean canRegister(String ip) {
if (maxAccounts <= 0) return true;
return getAccountCount(ip) < maxAccounts;
}
}
-46
View File
@@ -1,46 +0,0 @@
package Sdf1_login;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
public class InventorySerializer {
public static String toBase64(ItemStack[] items) throws IOException {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
dataOutput.writeInt(items.length);
for (ItemStack item : items) {
dataOutput.writeObject(item);
}
dataOutput.close();
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
} catch (Exception e) {
throw new IOException("Unable to serialize item stacks.", e);
}
}
public static ItemStack[] fromBase64(String data) throws IOException {
if (data == null || data.isEmpty()) {
return new ItemStack[0];
}
try {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(data));
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
ItemStack[] items = new ItemStack[dataInput.readInt()];
for (int i = 0; i < items.length; i++) {
items[i] = (ItemStack) dataInput.readObject();
}
dataInput.close();
return items;
} catch (ClassNotFoundException e) {
throw new IOException("Unable to deserialize item stacks.", e);
}
}
}
-91
View File
@@ -1,91 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.text.SimpleDateFormat;
import java.util.*;
public class InviteManager {
private final Main plugin;
public InviteManager(Main plugin) {
this.plugin = plugin;
}
public String generateCode(Player p) {
String code = PasswordUtils.generateInviteCode();
plugin.getDb().setField(
p.getName(), "invite_code", code);
return code;
}
public void onInviteeCheckIn(String inviteeName) {
String invitedBy = (String) plugin.getDb()
.getField(inviteeName, "invited_by");
if (invitedBy == null || invitedBy.isEmpty()) return;
String month = new SimpleDateFormat("yyyy-MM")
.format(new Date());
String lastMonth = (String) plugin.getDb()
.getField(invitedBy, "invite_month");
if (!month.equals(lastMonth)) {
plugin.getDb().setField(
invitedBy, "invite_month", month);
plugin.getDb().setField(
invitedBy, "monthly_invite_count", 0);
}
int count = ((Number) plugin.getDb()
.getField(invitedBy,
"monthly_invite_count")).intValue();
plugin.getDb().setField(
invitedBy, "monthly_invite_count", count + 1);
double rate = 0.10;
if (count + 1 > 15) {
rate = 0.60;
} else if (count + 1 > 10) {
rate = 0.35;
} else if (count + 1 > 5) {
rate = 0.15;
}
int bonus = (int) (1 * rate);
if (bonus > 0) {
plugin.getDb().addPoints(invitedBy, bonus);
Player inviterPlayer = Bukkit
.getPlayerExact(invitedBy);
if (inviterPlayer != null
&& inviterPlayer.isOnline()) {
inviterPlayer.sendMessage(
plugin.getConfig2().msg(
"invite_referral_bonus",
"points",
String.valueOf(bonus)));
}
}
}
public List<Map<String, Object>> getAllInviteData() {
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> user
: plugin.getDb().getAllUsers()) {
String code = (String) user.getOrDefault(
"invite_code", "");
if (!code.isEmpty()) {
Map<String, Object> row = new LinkedHashMap<>();
row.put("player",
user.get("player_name"));
row.put("code", code);
row.put("monthly",
user.getOrDefault(
"monthly_invite_count", 0));
result.add(row);
}
}
return result;
}
}
-301
View File
@@ -1,301 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
public class LoginManager {
private final Main plugin;
public LoginManager(Main plugin) {
this.plugin = plugin;
}
public boolean handleRegister(Player p, String[] args) {
String name = p.getName();
if (plugin.getplugin.getDb()().userExists(name)) {
p.sendMessage(plugin.getConfig2().msg("already_registered"));
return true;
}
// ===== IP注册限制检查 =====
String ip = plugin.getPlayerIP(p);
if (ip != null && plugin.getConfig2().maxAccountsPerIP > 0) {
if (!plugin.getIPGroup().canRegister(ip)) {
int count = plugin.getIPGroup().getAccountCount(ip);
int max = plugin.getConfig2().maxAccountsPerIP;
p.sendMessage("§c[Sdf1_login] §f该IP注册已达上限(" + count + "/" + max + ")");
return true;
}
}
if (args.length < 1) {
p.sendMessage("§c用法: /reg <密码>");
p.sendMessage("§7密码要求: 6位以上,包含大小写字母和数字");
return true;
}
String pwd = args[0];
if (pwd.length() < 6) {
p.sendMessage("§c[Sdf1_login] §f密码长度不足6位");
return true;
}
if (!pwd.matches(".*[a-z].*") || !pwd.matches(".*[A-Z].*") || !pwd.matches(".*[0-9].*")) {
p.sendMessage("§c[Sdf1_login] §f密码必须包含大写字母、小写字母和数字");
return true;
}
String salt = PasswordUtils.generateSalt();
String hash = PasswordUtils.hash(pwd, salt);
plugin.getplugin.getDb()().createUser(name, hash, salt);
plugin.getLoggedIn().add(name);
plugin.getplugin.getDb()().setLoggedIn(name, true);
plugin.getplugin.getDb()().setField(name, "last_login_time", System.currentTimeMillis());
plugin.getplugin.getDb()().setField(name, "last_online_check", System.currentTimeMillis());
plugin.getplugin.getDb()().recordIP(name, ip);
plugin.getplugin.getDb()().setField(name, "register_time", System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
p.sendMessage(plugin.getConfig2().msg("reg_success"));
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.79f);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1.0f, 0.79f);
try {
org.bukkit.plugin.Plugin cy = org.bukkit.Bukkit.getPluginManager().getPlugin("CY_beibao");
if (cy != null && cy.isEnabled()) {
cy.getClass().getMethod("onSdf1Activation", String.class, int.class, int.class)
.invoke(cy, name, 9, 0);
}
} catch (Exception ignored) {
}
return true;
}
public boolean handleLogin(Player p,
String[] args) {
String name = p.getName();
if (plugin.getLoggedIn().contains(name)) {
p.sendMessage(plugin.getConfig2().msg(
"already_logged_in"));
return true;
}
if (!plugin.getplugin.getDb()().userExists(name)) {
p.sendMessage(plugin.getConfig2().msg(
"not_registered"));
return true;
}
if (args.length < 1) {
p.sendMessage("§c用法: /login <密码>");
return true;
}
String pwd = args[0];
// 诊断:显示账号信息
String salt = (String) plugin.getplugin.getDb()()
.getField(name, "password_salt");
String regType = (String) plugin.getplugin.getDb()()
.getField(name, "register_type");
String storedHash = (String) plugin.getplugin.getDb()()
.getField(name, "password_hash");
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 玩家: " + name);
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 注册类型: "
+ regType);
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 盐值: " + salt);
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 存储哈希: "
+ (storedHash != null
? storedHash.substring(0,
Math.min(16,
storedHash.length()))
+ "..."
: "null"));
if (salt == null) {
p.sendMessage(
"§c[Sdf1_login] §f数据异常,请联系管理员");
plugin.getLogger().severe(
"[Sdf1_login] 玩家 " + name
+ " 盐值为null");
return true;
}
String hash = PasswordUtils.hash(pwd, salt);
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 输入密码哈希: "
+ hash.substring(0,
Math.min(16, hash.length()))
+ "...");
// 检查是否匹配主密码
boolean matchMain =
plugin.getplugin.getDb()().checkPassword(name, hash);
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 主密码匹配: "
+ matchMain);
if (matchMain) {
// 主密码匹配 → 正常登录
plugin.getLoggedIn().add(name);
plugin.getplugin.getDb()().setLoggedIn(name, true);
plugin.getplugin.getDb()().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getplugin.getDb()().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
p.sendMessage(plugin.getConfig2().msg("login_success"));
plugin.getLogger().info(
"[Sdf1_login] " + name
+ " 主密码登录成功");
try {
org.bukkit.plugin.Plugin cy =
org.bukkit.Bukkit
.getPluginManager()
.getPlugin("CY_beibao");
if (cy != null && cy.isEnabled()) {
java.lang.reflect.Method m =
cy.getClass().getMethod(
"onSdf1Activation",
String.class,
int.class, int.class);
m.invoke(cy, name, 0, 0);
}
} catch (Exception ignored) {
}
return true;
}
// 主密码不匹配 → 检查临时密码
boolean matchTemp =
plugin.getplugin.getDb()().checkPasswordOrTemp(
name, hash);
plugin.getLogger().info(
"[Sdf1_login] [登录诊断] 临时密码匹配: "
+ matchTemp);
if (matchTemp) {
// 临时密码匹配 → 登录 + 强制改密
plugin.getLoggedIn().add(name);
plugin.getplugin.getDb()().setLoggedIn(name, true);
plugin.getplugin.getDb()().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getplugin.getDb()().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.getNeedsPasswordChange().add(name);
p.sendMessage(plugin.getConfig2().msg("login_success"));
p.sendMessage(
"§c§l[警告] §f您使用的是临时密码,"
+ "请尽快修改密码!");
p.sendMessage(
"§7用法: /sdf1_login pw");
plugin.getLogger().info(
"[Sdf1_login] " + name
+ " 临时密码登录成功,"
+ "强制要求改密");
try {
org.bukkit.plugin.Plugin cy =
org.bukkit.Bukkit
.getPluginManager()
.getPlugin("CY_beibao");
if (cy != null && cy.isEnabled()) {
java.lang.reflect.Method m =
cy.getClass().getMethod(
"onSdf1Activation",
String.class,
int.class, int.class);
m.invoke(cy, name, 0, 0);
}
} catch (Exception ignored) {
}
return true;
}
// 都不匹配 → 登录失败
plugin.getLogger().info(
"[Sdf1_login] " + name
+ " 密码验证失败");
p.sendMessage(plugin.getConfig2().msg(
"login_failed"));
return true;
}
public void handleReset(Player p) {
String name = p.getName();
String plugin.getEmail()Addr = (String) plugin.getDb().getField(
name, "plugin.getEmail()");
if (plugin.getEmail()Addr == null
|| plugin.getEmail()Addr.isEmpty()) {
p.sendMessage(
"§c未设置邮箱,无法重置密码");
return;
}
String tempPwd = PasswordUtils
.generateTempPassword();
String salt = (String) plugin.getDb().getField(
name, "password_salt");
String hash = PasswordUtils.hash(
tempPwd, salt);
plugin.getDb().setField(name,
"temp_password", hash);
plugin.getDb().setField(name,
"temp_pw_expire",
System.currentTimeMillis()
+ 3600000L);
plugin.getDb().setField(name,
"temp_pw_used", 0);
p.sendMessage("§e正在发送临时密码...");
final String to = plugin.getEmail()Addr;
final String fName = name;
final String pwd = tempPwd;
Bukkit.getScheduler()
.Bukkit.getScheduler().runTask(plugin, ...)Asynchronously(this,
() -> {
final boolean[] sent = {false};
Thread t = new Thread(() -> {
sent[0] = plugin.getEmail()
.sendTempPassword(
to, fName, pwd);
}, "Sdf1_login-reset");
t.setDaemon(true);
t.start();
try {
t.join(15000);
} catch (
InterruptedException ignored) {
}
if (t.isAlive()) {
t.interrupt();
Bukkit.getScheduler().Bukkit.getScheduler().runTask(plugin, ...)(
this, () ->
p.sendMessage(
"§c邮件发送超时"));
return;
}
Bukkit.getScheduler().Bukkit.getScheduler().runTask(plugin, ...)(
this, () -> {
if (sent[0]) {
p.sendMessage(
"§a临时密码已发送到 "
+ to);
} else {
p.sendMessage(
"§c邮件发送失败");
}
});
});
}
}
-3712
View File
File diff suppressed because it is too large Load Diff
-72
View File
@@ -1,72 +0,0 @@
package Sdf1_login;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
public class PasswordUtils {
private static final SecureRandom RANDOM =
new SecureRandom();
public static String generateSalt() {
byte[] salt = new byte[16];
RANDOM.nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}
public static String hash(String password, String salt) {
try {
MessageDigest md =
MessageDigest.getInstance("SHA-256");
md.update(Base64.getDecoder().decode(salt));
byte[] hash =
md.digest(password.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static boolean validate(String password) {
if (password == null) return false;
if (password.length() < 6 || password.length() > 20)
return false;
boolean hasUpper = false;
boolean hasLower = false;
boolean hasDigit = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) hasUpper = true;
if (Character.isLowerCase(c)) hasLower = true;
if (Character.isDigit(c)) hasDigit = true;
}
return hasUpper && hasLower && hasDigit;
}
public static String generateTempPassword() {
String chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder(8);
for (int i = 0; i < 8; i++) {
sb.append(chars.charAt(
RANDOM.nextInt(chars.length())));
}
return sb.toString();
}
public static String generateInviteCode() {
String chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
StringBuilder sb = new StringBuilder(6);
for (int i = 0; i < 6; i++) {
sb.append(chars.charAt(
RANDOM.nextInt(chars.length())));
}
return sb.toString();
}
public static String generateVerifyCode() {
int code = 100000 + RANDOM.nextInt(900000);
return String.valueOf(code);
}
}
-328
View File
@@ -1,328 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.lang.reflect.Method;
import java.util.*;
public class PointsManager {
private final Main plugin;
private Object cyInstance = null;
private Method cyActivationMethod = null;
private Method cyGetShopItemsMethod = null;
private final List<Map<String, Object>> cyItems =
new ArrayList<>();
public PointsManager(Main plugin) {
this.plugin = plugin;
discoverCY();
}
private void discoverCY() {
try {
org.bukkit.plugin.Plugin p = Bukkit
.getPluginManager()
.getPlugin("CY_beibao");
if (p != null && p.isEnabled()) {
cyInstance = p;
cyGetShopItemsMethod = p.getClass()
.getMethod("onSdf1GetShopItems");
cyActivationMethod = p.getClass()
.getMethod("onSdf1Activation",
String.class,
int.class, int.class);
plugin.getLogger().info(
"[Sdf1_login] CY_beibao 联控已连接");
}
} catch (Exception ignored) {}
}
// ===== CY 商品拉取 =====
private void refreshCYItems() {
cyItems.clear();
if (cyInstance == null
|| cyGetShopItemsMethod == null) return;
try {
Object result =
cyGetShopItemsMethod.invoke(cyInstance);
if (result == null) return;
String data = result.toString();
if (data.isEmpty()) return;
String[] items = data.split(";");
for (String itemStr : items) {
String[] p = itemStr.split("\\|");
if (p.length < 8) continue;
Map<String, Object> item =
new LinkedHashMap<>();
item.put("id", p[0]);
item.put("name", p[1]);
item.put("days",
Integer.parseInt(p[2]));
item.put("slots",
Integer.parseInt(p[3]));
item.put("price",
Integer.parseInt(p[4]));
item.put("stock",
Integer.parseInt(p[5]));
item.put("icon", p[6]);
item.put("lifetime",
"1".equals(p[7]));
cyItems.add(item);
}
} catch (Exception e) {
plugin.getLogger().warning(
"[Sdf1_login] 拉取CY商品失败: "
+ e.getMessage());
}
}
private void activateCY(String name,
int slots, int days) {
try {
if (cyInstance == null
|| cyActivationMethod == null) return;
cyActivationMethod.invoke(
cyInstance, name, slots, days);
} catch (Exception e) {
plugin.getLogger().warning(
"[Sdf1_login] CY激活失败: "
+ e.getMessage());
}
}
// ===== 积分查询 =====
public int getPoints(String name) {
return ((Number) plugin.getDb()
.getField(name, "points")).intValue();
}
// ===== 主面板 =====
public Inventory createShopGUI(Player p) {
int points = getPoints(p.getName());
Inventory inv = Bukkit.createInventory(
null, 54,
"§d§l积分商城 | 当前积分: " + points);
fillBg(inv);
inv.setItem(10, mkItem(Material.EMERALD,
"§a游戏币充值",
"§7消耗积分兑换游戏币",
"§7100积分 = $1000",
"",
"§e点击兑换"));
inv.setItem(12, mkItem(Material.ENDER_CHEST,
"§b背包空间",
"§7消耗积分兑换背包格子",
"§750积分 = 9格永久空间",
"",
"§e点击兑换"));
inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE,
"§e经验瓶",
"§7消耗积分兑换经验",
"§720积分 = 10级经验",
"",
"§e点击兑换"));
if (cyInstance != null) {
inv.setItem(31, mkItem(Material.CHEST,
"§6背包商城",
"§7来自CY_beibao的商品",
"",
"§e点击查看"));
}
inv.setItem(49, mkItem(Material.ARROW, "§7返回"));
return inv;
}
// ===== CY 商品面板 =====
public Inventory createCYShopGUI(Player p) {
refreshCYItems();
int points = getPoints(p.getName());
int size = Math.max(54,
((cyItems.size() / 9) + 1) * 9);
if (size > 54) size = 54;
Inventory inv = Bukkit.createInventory(
null, size,
"§d§lCY背包商城 | 当前积分: " + points);
fillBg(inv);
for (int i = 0; i < cyItems.size()
&& i < 45; i++) {
Map<String, Object> item = cyItems.get(i);
String name = (String) item.get("name");
int price = ((Number) item.get("price"))
.intValue();
int stock = ((Number) item.get("stock"))
.intValue();
int days = ((Number) item.get("days"))
.intValue();
int slots = ((Number) item.get("slots"))
.intValue();
boolean lifetime = (boolean) item
.get("lifetime");
List<String> lore = new ArrayList<>();
lore.add("§7价格: §e" + price + " 积分");
if (lifetime) {
lore.add("§7格子: §e终身 +" + slots);
} else {
lore.add("§7格子: §e+" + slots);
lore.add("§7时间: §e" + days + "天");
}
if (stock == 0) {
lore.add("§7库存: §a无限");
} else if (stock == -1) {
lore.add("§7库存: §c已下架");
} else {
lore.add("§7库存: §e" + stock);
}
lore.add("");
if (points >= price && stock != -1) {
lore.add("§e点击购买");
} else if (stock == -1) {
lore.add("§c已下架");
} else {
lore.add("§c积分不足");
}
inv.setItem(i, mkItem(Material.CHEST,
"§a" + name,
lore.toArray(new String[0])));
}
inv.setItem(size - 1,
mkItem(Material.ARROW, "§7返回"));
return inv;
}
// ===== 主面板点击 =====
public void handleClick(Player p, int slot) {
int points = getPoints(p.getName());
switch (slot) {
case 10:
if (points >= 100) {
plugin.getDb().deductPoints(
p.getName(), 100);
if (plugin.getEconomy() != null) {
plugin.getEconomy()
.depositPlayer(p, 1000);
}
p.sendMessage(plugin.getConfig2().msg(
"points_purchase_success",
"count", "100"));
} else {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"points",
String.valueOf(points)));
}
break;
case 12:
if (points >= 50) {
plugin.getDb().deductPoints(
p.getName(), 50);
activateCY(p.getName(), 9, 0);
p.sendMessage(plugin.getConfig2().msg(
"points_purchase_success",
"count", "50"));
} else {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"points",
String.valueOf(points)));
}
break;
case 14:
if (points >= 20) {
plugin.getDb().deductPoints(
p.getName(), 20);
p.setLevel(p.getLevel() + 10);
p.sendMessage(plugin.getConfig2().msg(
"points_purchase_success",
"count", "20"));
} else {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"points",
String.valueOf(points)));
}
break;
case 31:
if (cyInstance != null) {
p.openInventory(
createCYShopGUI(p));
}
break;
}
}
// ===== CY 商品面板点击 =====
public void handleCYClick(Player p, int slot) {
if (slot < 0 || slot >= cyItems.size()
|| slot >= 45) return;
Map<String, Object> item = cyItems.get(slot);
int price = ((Number) item.get("price"))
.intValue();
int stock = ((Number) item.get("stock"))
.intValue();
int days = ((Number) item.get("days"))
.intValue();
int slots = ((Number) item.get("slots"))
.intValue();
boolean lifetime = (boolean) item
.get("lifetime");
if (stock == -1) {
p.sendMessage(
"§c[Sdf1_login] §f该商品已下架");
return;
}
int points = getPoints(p.getName());
if (points < price) {
p.sendMessage(plugin.getConfig2().msg(
"points_insufficient",
"points",
String.valueOf(points)));
return;
}
plugin.getDb().deductPoints(p.getName(), price);
int actDays = lifetime ? 0 : days;
activateCY(p.getName(), slots, actDays);
String tl = lifetime
? "终身" : (days + "天");
p.sendMessage("§a[Sdf1_login] §f购买成功!获得 "
+ tl + " +" + slots + "格 背包空间");
}
// ===== 工具 =====
private void fillBg(Inventory inv) {
ItemStack gl = mkItem(
Material.GRAY_STAINED_GLASS_PANE, " ");
for (int i = 0; i < inv.getSize(); i++)
inv.setItem(i, gl);
}
private ItemStack mkItem(Material mat, String name,
String... lore) {
ItemStack it = new ItemStack(mat);
ItemMeta im = it.getItemMeta();
if (im != null) {
im.setDisplayName(name);
if (lore.length > 0)
im.setLore(Arrays.asList(lore));
it.setItemMeta(im);
}
return it;
}
}
-2493
View File
File diff suppressed because it is too large Load Diff
-113
View File
@@ -1,113 +0,0 @@
package Sdf1_login;
import java.io.*;
import java.nio.file.*;
import java.util.logging.Logger;
public class RadioConverter {
private final Logger logger;
private final boolean ffmpegAvailable;
public RadioConverter(Logger logger) {
this.logger = logger;
this.ffmpegAvailable = checkFfmpeg();
}
/**
* 转换音频文件为 ogg 格式
* 支持: mp3, wav, flac, ogg
*/
public boolean convert(File input,
File output) {
String name =
input.getName().toLowerCase();
// ogg 文件直接复制
if (name.endsWith(".ogg")) {
return copyFile(input, output);
}
// 有 ffmpeg 则转换
if (ffmpegAvailable) {
return convertFfmpeg(input, output);
}
// 无 ffmpeg 无法转换
logger.warning(
"[Radio] 无法转换: "
+ input.getName()
+ " (需要安装ffmpeg)");
return false;
}
private boolean copyFile(File src,
File dest) {
try {
dest.getParentFile().mkdirs();
Files.copy(src.toPath(),
dest.toPath(),
StandardCopyOption
.REPLACE_EXISTING);
return true;
} catch (Exception e) {
return false;
}
}
private boolean convertFfmpeg(
File input, File output) {
try {
output.getParentFile().mkdirs();
ProcessBuilder pb =
new ProcessBuilder(
"ffmpeg", "-y",
"-i",
input
.getAbsolutePath(),
"-c:a",
"libvorbis",
"-q:a", "5",
output
.getAbsolutePath());
pb.redirectErrorStream(true);
Process p = pb.start();
p.waitFor();
return output.exists();
} catch (Exception e) {
logger.warning(
"[Radio] ffmpeg转换失败: "
+ input.getName());
return false;
}
}
private boolean checkFfmpeg() {
try {
Process p =
new ProcessBuilder(
"ffmpeg",
"-version")
.start();
boolean ok = p.waitFor() == 0;
if (ok) {
logger.info(
"[Radio] ffmpeg可用");
} else {
logger.info(
"[Radio] ffmpeg不可用"
+ " (仅支持ogg文件)");
}
return ok;
} catch (Exception e) {
logger.info(
"[Radio] ffmpeg未安装"
+ " (仅支持ogg文件)");
return false;
}
}
public boolean isFfmpegAvailable() {
return ffmpegAvailable;
}
}
-563
View File
@@ -1,563 +0,0 @@
package Sdf1_login;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import java.util.zip.*;
public class RadioManager {
private final Main plugin;
private File radioDir;
private File oggDir;
private File packDir;
private BukkitTask mainRadioTask;
private BukkitTask loginRadioTask;
private final Map<String, Boolean>
loginPlayers =
new ConcurrentHashMap<>();
private int radioIndex = 0;
private RadioConverter converter;
private final List<File> mainFiles =
new ArrayList<>();
private final List<File> loginFiles =
new ArrayList<>();
public RadioManager(Main plugin) {
this.plugin = plugin;
this.radioDir = new File(
plugin.getDataFolder(),
"radio");
this.oggDir = new File(
plugin.getDataFolder(),
"radio/ogg");
this.packDir = new File(
plugin.getDataFolder(),
"radiopack");
radioDir.mkdirs();
oggDir.mkdirs();
packDir.mkdirs();
}
// ========== 初始化 ==========
public void init() {
converter = new RadioConverter(
plugin.getLogger());
convertAll();
buildResourcePack();
loadFiles();
plugin.getLogger().info(
"[Radio] 初始化完成: 全服="
+ mainFiles.size()
+ " 登录=" + loginFiles.size());
}
// ========== 格式转换 ==========
private void convertAll() {
// 根目录
convertDir(radioDir,
new File(oggDir, "radio"));
// login 目录
File loginSrc = new File(
radioDir, "login");
if (loginSrc.exists()) {
convertDir(loginSrc,
new File(oggDir,
"radio/login"));
}
// reg 目录
File regSrc = new File(
radioDir, "reg");
if (regSrc.exists()) {
convertDir(regSrc,
new File(oggDir,
"radio/reg"));
}
}
private void convertDir(File src,
File dest) {
dest.mkdirs();
File[] files = src.listFiles(
(d, n) -> {
String l =
n.toLowerCase();
return l.endsWith(".mp3")
|| l.endsWith(".wav")
|| l.endsWith(".flac")
|| l.endsWith(".ogg");
});
if (files == null) return;
for (File f : files) {
String oggName = f.getName()
.replaceAll(
"\\.(mp3|wav|flac"
+ "|ogg)$",
".ogg");
File oggFile =
new File(dest, oggName);
if (oggFile.exists()
&& oggFile.lastModified()
>= f.lastModified()) {
continue;
}
boolean ok = converter.convert(
f, oggFile);
if (ok) {
plugin.getLogger().info(
"[Radio] 转换成功: "
+ f.getName());
} else {
plugin.getLogger().warning(
"[Radio] 转换失败: "
+ f.getName());
}
}
}
// ========== 资源包 ==========
private void buildResourcePack() {
// 清理旧文件
deleteDir(packDir);
packDir.mkdirs();
List<File> allOggs = new ArrayList<>();
collectOggs(oggDir, allOggs);
if (allOggs.isEmpty()) {
plugin.getLogger().warning(
"[Radio] 无 ogg 文件,跳过资源包");
return;
}
// assets/sdf1radio/sounds/ 下面直接放文件
File soundsDir = new File(
packDir,
"assets/sdf1radio/sounds");
soundsDir.mkdirs();
// 构建 sounds.json
StringBuilder json =
new StringBuilder("{\n");
for (int i = 0;
i < allOggs.size(); i++) {
File ogg = allOggs.get(i);
// 简化路径:只取文件名(去掉所有目录前缀)
String simpleName =
ogg.getName()
.replace(".ogg", "");
// 检查是否有同名文件,加前缀区分
String soundKey = simpleName;
// 复制 ogg 到资源包
File dest =
new File(soundsDir,
simpleName + ".ogg");
try {
Files.copy(ogg.toPath(),
dest.toPath(),
StandardCopyOption
.REPLACE_EXISTING);
} catch (Exception e) {
// 忽略
}
// 写 JSON
json.append(" \"sdf1radio:")
.append(soundKey)
.append("\": {\n");
json.append(" \"sounds\": [{\n");
json.append(" \"name\": \"")
.append("sdf1radio/")
.append(soundKey)
.append("\",\n");
json.append(
" \"stream\": true\n");
json.append(" }]\n");
json.append(" }");
if (i < allOggs.size() - 1)
json.append(",");
json.append("\n");
}
json.append("}");
// 写 sounds.json
try {
Files.write(
new File(packDir,
"assets/sdf1radio/"
+ "sounds.json")
.toPath(),
json.toString()
.getBytes("UTF-8"));
} catch (Exception e) {
plugin.getLogger().warning(
"[Radio] sounds.json写入失败");
}
// 写 pack.mcmeta
try {
Files.write(
new File(packDir,
"pack.mcmeta")
.toPath(),
("{\n"
+ " \"pack\": {\n"
+ " \"pack_format\": 34,\n"
+ " \"description\": "
+ "\"Sdf1 Radio\"\n"
+ " }\n"
+ "}")
.getBytes("UTF-8"));
} catch (Exception e) {
// 忽略
}
// 打包 zip
try {
File zipFile = new File(
plugin.getDataFolder(),
"radiopack.zip");
packZip(packDir, zipFile);
plugin.getLogger().info(
"[Radio] 资源包生成: "
+ allOggs.size()
+ " 个音频");
} catch (Exception e) {
plugin.getLogger().warning(
"[Radio] zip打包失败: "
+ e.getMessage());
}
}
private void deleteDir(File dir) {
if (!dir.exists()) return;
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
if (f.isDirectory()) {
deleteDir(f);
} else {
f.delete();
}
}
}
dir.delete();
}
private void collectOggs(File dir,
List<File> list) {
File[] files = dir.listFiles();
if (files == null) return;
for (File f : files) {
if (f.isDirectory()) {
collectOggs(f, list);
} else if (f.getName()
.endsWith(".ogg")) {
list.add(f);
}
}
}
private String getRelativePath(
File base, File file) {
return base.toURI()
.relativize(file.toURI())
.getPath();
}
private void packZip(File root,
File zipFile) throws Exception {
try (ZipOutputStream zos =
new ZipOutputStream(
new FileOutputStream(
zipFile))) {
zipDir(root, root, zos);
}
}
private void zipDir(File root,
File current,
ZipOutputStream zos)
throws Exception {
File[] files = current.listFiles();
if (files == null) return;
for (File f : files) {
if (f.isDirectory()) {
zipDir(root, f, zos);
} else {
String path =
root.toURI()
.relativize(f.toURI())
.getPath();
zos.putNextEntry(
new ZipEntry(path));
zos.write(Files.readAllBytes(
f.toPath()));
zos.closeEntry();
}
}
}
// ========== 加载文件列表 ==========
private void loadFiles() {
mainFiles.clear();
loginFiles.clear();
File radioOgg = new File(
oggDir, "radio");
loadOggs(radioOgg, mainFiles);
File loginOgg = new File(
oggDir, "radio/login");
loadOggs(loginOgg, loginFiles);
File regOgg = new File(
oggDir, "radio/reg");
loadOggs(regOgg, loginFiles);
}
private void loadOggs(File dir,
List<File> list) {
File[] files = dir.listFiles(
(d, n) -> n.toLowerCase()
.endsWith(".ogg"));
if (files != null) {
Arrays.sort(files);
for (File f : files)
list.add(f);
}
}
// ========== 资源包发送 ==========
public void sendResourcePack(Player p) {
File zip = new File(
plugin.getDataFolder(),
"radiopack.zip");
if (!zip.exists()) return;
try {
byte[] data =
Files.readAllBytes(
zip.toPath());
java.security.MessageDigest md =
java.security.MessageDigest
.getInstance("SHA-1");
md.update(data);
byte[] hashBytes = md.digest();
StringBuilder sb =
new StringBuilder();
for (byte b : hashBytes)
sb.append(String.format(
"%02x", b));
String hash = sb.toString();
UUID uuid = UUID.randomUUID();
// 用反射兼容不同版本
try {
java.lang.reflect.Method m =
p.getClass().getMethod(
"setResourcePack",
UUID.class,
byte[].class,
String.class,
String.class,
boolean.class);
m.invoke(p, uuid, data,
hash,
"Sdf1 Radio",
false);
} catch (NoSuchMethodException ex) {
try {
java.lang.reflect.Method m2 =
p.getClass().getMethod(
"setResourcePack",
UUID.class,
byte[].class,
String.class);
m2.invoke(p, uuid,
data, hash);
} catch (Exception ex2) {
plugin.getLogger().warning(
"[Radio] 服务端不支持"
+ "发送资源包");
}
}
} catch (Exception e) {
}
}
// ========== 全服广播 ==========
public void startMainRadio() {
stopMainRadio();
if (mainFiles.isEmpty()) {
plugin.getLogger().warning(
"[Radio] radio/ 目录"
+ "无 .ogg 文件");
return;
}
plugin.getLogger().info(
"[Radio] 启动全服广播"
+ " (" + mainFiles.size()
+ " 个文件)");
mainRadioTask = new BukkitRunnable() {
@Override
public void run() {
playNext();
}
}.runTaskTimer(
plugin, 100L, 2400L);
}
private void playNext() {
if (mainFiles.isEmpty()) return;
File file = mainFiles.get(
radioIndex % mainFiles.size());
String name = file.getName()
.replace(".ogg", "");
String key = "sdf1radio:" + name;
for (Player p
: Bukkit.getOnlinePlayers()) {
p.playSound(
p.getLocation(),
key,
SoundCategory.RECORDS,
2.0f, 1.0f);
}
Bukkit.broadcastMessage(
"§6§l[广播] §f正在播放: §e"
+ name);
radioIndex++;
}
public void stopMainRadio() {
if (mainRadioTask != null) {
mainRadioTask.cancel();
mainRadioTask = null;
}
}
// ========== 登录阶段广播 ==========
public void startLoginRadio(
Player p) {
loginPlayers.put(p.getName(), true);
if (loginRadioTask != null) return;
if (loginFiles.isEmpty()) return;
loginRadioTask = new BukkitRunnable() {
@Override
public void run() {
if (loginPlayers.isEmpty()) {
cancel();
loginRadioTask = null;
return;
}
int idx = (int)
(System.currentTimeMillis()
/ 2400L
% loginFiles.size());
File file =
loginFiles.get(idx);
String rel =
getRelativePath(
oggDir, file)
.replace("\\", "/");
String key =
"sdf1radio:"
+ rel.replace(".ogg", "");
for (String name
: loginPlayers.keySet()) {
Player lp = Bukkit
.getPlayer(name);
if (lp != null
&& lp.isOnline()) {
lp.playSound(
lp.getLocation(),
key,
SoundCategory
.RECORDS,
1.5f, 1.0f);
}
}
}
}.runTaskTimer(
plugin, 0L, 24L);
}
public void stopLoginRadio(
Player p) {
loginPlayers.remove(p.getName());
if (loginPlayers.isEmpty()
&& loginRadioTask != null) {
loginRadioTask.cancel();
loginRadioTask = null;
}
}
public void stopAllLoginRadio() {
loginPlayers.clear();
if (loginRadioTask != null) {
loginRadioTask.cancel();
loginRadioTask = null;
}
}
// ========== 手动播放 ==========
public boolean playSpecific(
Player sender, String name) {
List<File> all = new ArrayList<>();
collectOggs(oggDir, all);
for (File f : all) {
String fn = f.getName()
.replace(".ogg", "");
if (fn.equalsIgnoreCase(name)) {
String key = "sdf1radio:" + name;
for (Player p
: Bukkit.getOnlinePlayers()) {
p.playSound(
p.getLocation(),
key,
SoundCategory.RECORDS,
2.0f, 1.0f);
}
Bukkit.broadcastMessage(
"§6§l[广播] §f正在播放: §e"
+ name);
return true;
}
}
sender.sendMessage("§c未找到: " + name);
return false;
}
// ========== 重载 ==========
public void reload() {
stopMainRadio();
stopAllLoginRadio();
convertAll();
buildResourcePack();
loadFiles();
// 不自动启动,由管理员手动启动
} }
-276
View File
@@ -1,276 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class RiskControlManager {
private final Main plugin;
// 玩家当前是否处于风控状态
private final Map<String, Boolean> riskActive = new ConcurrentHashMap<>();
// 密码验证连续错误次数
private final Map<String, Integer> passwordAttempts = new ConcurrentHashMap<>();
// 邮箱验证码连续错误次数
private final Map<String, Integer> emailAttempts = new ConcurrentHashMap<>();
// 风控触发时间(5分钟过期)
private final Map<String, Long> riskTriggerTime = new ConcurrentHashMap<>();
// 临时封禁到期时间
private final Map<String, Long> tempBanExpiry = new ConcurrentHashMap<>();
private static final long RISK_TIMEOUT = 5 * 60 * 1000L; // 5分钟
private static final int MAX_ATTEMPTS = 3;
private static final long TEMP_BAN_DURATION = 10 * 60 * 1000L; // 10分钟
public RiskControlManager(Main plugin) {
this.plugin = plugin;
}
/**
* 检查玩家是否被临时封禁
*/
public boolean isTempBanned(String name) {
Long expiry = tempBanExpiry.get(name);
if (expiry == null) return false;
if (System.currentTimeMillis() >= expiry) {
tempBanExpiry.remove(name);
return false;
}
return true;
}
/**
* 获取临时封禁剩余秒数
*/
public int getTempBanRemaining(String name) {
Long expiry = tempBanExpiry.get(name);
if (expiry == null) return 0;
return (int) ((expiry - System.currentTimeMillis()) / 1000);
}
/**
* 检查玩家是否需要风控验证
* 条件:IP变化 + 有背包数据(老玩家)
*/
public boolean shouldTrigger(String name, String currentIP) {
if (isTempBanned(name)) return false;
if (riskActive.getOrDefault(name, false)) return false;
// 获取上次记录的IP
String lastIP = (String) plugin.getDb().getField(name, "last_ip");
if (lastIP == null || lastIP.isEmpty()) return false; // 新玩家不触发
// IP相同不触发
if (lastIP.equals(currentIP)) return false;
// 有背包数据的老玩家 + IP变化 → 触发风控
String invData = (String) plugin.getDb().getField(name, "inventory_data");
if (invData != null && !invData.isEmpty()) {
return true;
}
return false;
}
/**
* 触发风控:冻结玩家,保存并隐藏背包
*/
public void trigger(Player p, String currentIP) {
String name = p.getName();
riskActive.put(name, true);
riskTriggerTime.put(name, System.currentTimeMillis());
passwordAttempts.put(name, 0);
emailAttempts.put(name, 0);
// 保存背包并冻结
plugin.hideInventory(p);
p.sendMessage("§c§l[风控系统] §f检测到异常登录!");
p.sendMessage("§e当前IP: §f" + currentIP);
p.sendMessage("§e请选择验证方式:");
p.sendMessage("§a1. §f密码验证 §7(输入: 密码 <你的密码>)");
p.sendMessage("§a2. §f邮箱验证 §7(输入: 邮箱)");
p.sendMessage("§7风控有效期: 5分钟");
}
/**
* 处理密码验证
*/
public boolean handlePassword(Player p, String input) {
String name = p.getName();
if (!riskActive.getOrDefault(name, false)) return false;
// 检查过期
if (isExpired(name)) { expireRisk(name); return false; }
int attempts = passwordAttempts.getOrDefault(name, 0) + 1;
passwordAttempts.put(name, attempts);
String salt = (String) plugin.getDb().getField(name, "password_salt");
if (salt == null) return false;
String hash = PasswordUtils.hash(input, salt);
if (plugin.getDb().checkPassword(name, hash)) {
passRisk(p);
return true;
}
if (attempts >= MAX_ATTEMPTS) {
failRisk(p);
} else {
p.sendMessage("§c§l[风控系统] §f密码错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会");
}
return true;
}
/**
* 发送邮箱验证码
*/
public void sendEmailCode(Player p) {
String name = p.getName();
if (!riskActive.getOrDefault(name, false)) return;
if (isExpired(name)) { expireRisk(name); return; }
String emailAddr = (String) plugin.getDb().getField(name, "email");
if (emailAddr == null || emailAddr.isEmpty()) {
p.sendMessage("§c§l[风控系统] §f你未绑定邮箱,无法使用邮箱验证");
return;
}
if (!plugin.getEmail().isConfigured()) {
p.sendMessage("§c§l[风控系统] §f邮件服务未配置,请使用密码验证");
return;
}
String code = PasswordUtils.generateVerifyCode();
plugin.getChatInput().setVerifyCode(p, code, "risk_control");
boolean sent = plugin.getEmail().sendVerifyCode(emailAddr, name, code);
if (sent) {
p.sendMessage("§a§l[风控系统] §f验证码已发送到 " + emailAddr);
p.sendMessage("§e请输入: 验证 <验证码>");
} else {
p.sendMessage("§c§l[风控系统] §f验证码发送失败,请使用密码验证");
}
}
/**
* 处理邮箱验证码
*/
public boolean handleEmailCode(Player p, String code) {
String name = p.getName();
if (!riskActive.getOrDefault(name, false)) return false;
if (isExpired(name)) { expireRisk(name); return false; }
int attempts = emailAttempts.getOrDefault(name, 0) + 1;
emailAttempts.put(name, attempts(p, code, "risk_control")) {
passRisk(p);
return true;
}
if (attempts >= MAX_ATTEMPTS) {
failRisk(p);
} else {
p.sendMessage("§c§l[风控系统] §f验证码);
if (plugin.getChatInput().checkVerifyCode错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会");
}
return true;
}
/**
* 验证通过:解冻,记录新IP
*/
private void passRisk(Player p) {
String name = p.getName();
riskActive.remove(name);
passwordAttempts.remove(name);
emailAttempts.remove(name);
riskTriggerTime.remove(name);
// 记录新IP
String ip = plugin.getPlayerIP(p);
if (ip != null) plugin.getDb().setField(name, "last_ip", ip);
plugin.recordIPLogin(p);
// 还原背包
plugin.restoreInventory(p);
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
p.sendMessage("§a§l[风控系统] §f验证通过!已解除风控限制");
p.playSound(p.getLocation(), org.bukkit.Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f);
}
/**
* 验证失败3次:临时封禁 + 邮件报警
*/
private void failRisk(Player p) {
String name = p.getName();
riskActive.remove(name);
passwordAttempts.remove(name);
emailAttempts.remove(name);
riskTriggerTime.remove(name);
// 临时封禁10分钟
tempBanExpiry.put(name, System.currentTimeMillis() + TEMP_BAN_DURATION);
p.sendMessage("§c§l[风控系统] §f验证失败次数过多,临时封禁10分钟");
// 邮件报警
String emailAddr = (String) plugin.getDb().getField(name, "email");
if (emailAddr != null && !emailAddr.isEmpty() && plugin.getEmail().isConfigured()) {
String ip = plugin.getPlayerIP(p);
plugin.getEmail().sendVerifyCode(emailAddr, name,
"[Sdf1_login] 风控报警: 玩家 " + name + " IP(" + ip + ")验证失败3次,已临时封禁10分钟");
}
// 踢出
p.kickPlayer("§c§l[风控系统] §f验证失败,临时封禁10分钟,请稍后再试");
}
/**
* 检查风控是否过期
*/
private boolean isExpired(String name) {
Long trigger = riskTriggerTime.get(name);
if (trigger == null) return true;
return System.currentTimeMillis() - trigger > RISK_TIMEOUT;
}
/**
* 风控过期处理
*/
private void expireRisk(String name) {
riskActive.remove(name);
passwordAttempts.remove(name);
emailAttempts.remove(name);
riskTriggerTime.remove(name);
}
/**
* 玩家是否处于风控状态
*/
public boolean isRiskActive(String name) {
return riskActive.getOrDefault(name, false);
}
/**
* 定时清理过期数据
*/
public void cleanup() {
long now = System.currentTimeMillis();
riskTriggerTime.entrySet().removeIf(e -> {
if (now - e.getValue() > RISK_TIMEOUT) {
riskActive.remove(e.getKey());
passwordAttempts.remove(e.getKey());
emailAttempts.remove(e.getKey());
return true;
}
return false;
});
tempBanExpiry.entrySet().removeIf(e -> now >= e.getValue());
}
}
File diff suppressed because it is too large Load Diff
-137
View File
@@ -1,137 +0,0 @@
package Sdf1_login;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
public class VerificationManager {
private final JavaPlugin plugin;
private final Logger logger;
public VerificationManager(JavaPlugin plugin) {
this.plugin = plugin;
this.logger = plugin.getLogger();
}
public boolean isBedrockPlayer(Player player) {
UUID uuid = player.getUniqueId();
if (uuid.toString().startsWith("00000000-0000-0000-0009-")) {
logger.info("[Sdf1_login] Geyser: UUID前缀匹配 → 基岩版");
return true;
}
if (player.hasMetadata("isgeyserplayer")) {
logger.info("[Sdf1_login] Geyser: metadata匹配 → 基岩版");
return true;
}
if (player.hasPermission("geyser.player")) {
logger.info("[Sdf1_login] Geyser: permission匹配 → 基岩版");
return true;
}
try {
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
if (floodgate == null) floodgate = Bukkit.getPluginManager().getPlugin("Floodgate");
if (floodgate != null && floodgate.isEnabled()) {
Class<?> apiClass = Class.forName("org.geysermc.floodgate.api.FloodgateApi");
Object api = apiClass.getMethod("getInstance").invoke(null);
boolean isBedrock = (boolean) apiClass.getMethod("isFloodgatePlayer", UUID.class).invoke(api, uuid);
if (isBedrock) {
logger.info("[Sdf1_login] Geyser: Floodgate API确认 → 基岩版");
return true;
}
}
} catch (Exception ignored) {}
logger.info("[Sdf1_login] Geyser: 非基岩版");
return false;
}
public boolean isOnlineMode() {
return Bukkit.getOnlineMode();
}
public void verifyPremiumAsync(Player player, Consumer<Boolean> callback) {
String name = player.getName();
UUID playerUUID = player.getUniqueId();
logger.info("[Sdf1_login] Mojang验证: 查询 " + name + " ...");
logger.info("[Sdf1_login] Mojang验证: 玩家UUID = " + playerUUID);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
UUID premiumUUID = queryMojangUUID(name);
if (premiumUUID == null) {
logger.info("[Sdf1_login] Mojang验证: " + name + " 无记录 → 非正版");
Bukkit.getScheduler().runTask(plugin, () -> callback.accept(false));
return;
}
logger.info("[Sdf1_login] Mojang验证: 正版UUID = " + premiumUUID);
boolean match = playerUUID.equals(premiumUUID);
if (match) {
logger.info("[Sdf1_login] Mojang验证: ✓ UUID匹配 → 正版");
} else {
logger.info("[Sdf1_login] Mojang验证: ✗ UUID不匹配 → 非正版");
logger.info("[Sdf1_login] Mojang验证: 玩家: " + playerUUID);
logger.info("[Sdf1_login] Mojang验证: 正版: " + premiumUUID);
}
final boolean finalResult = match;
Bukkit.getScheduler().runTask(plugin, () -> callback.accept(finalResult));
});
}
private UUID queryMojangUUID(String name) {
try {
URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "Sdf1_login");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
int code = conn.getResponseCode();
if (code != 200) {
conn.disconnect();
return null;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) sb.append(line);
reader.close();
conn.disconnect();
String json = sb.toString();
logger.info("[Sdf1_login] Mojang响应: " + json);
int idIndex = json.indexOf("\"id\"");
if (idIndex < 0) return null;
int colonIndex = json.indexOf(":", idIndex);
int quoteStart = json.indexOf("\"", colonIndex + 1);
int quoteEnd = json.indexOf("\"", quoteStart + 1);
String rawUUID = json.substring(quoteStart + 1, quoteEnd);
String formatted = rawUUID.replaceFirst(
"(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
"$1-$2-$3-$4-$5");
return UUID.fromString(formatted);
} catch (Exception e) {
logger.log(Level.WARNING, "[Sdf1_login] Mojang查询异常: " + name, e);
return null;
}
}
}
BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
playSpecific无结果,位置:Main.java