Compare commits

...
2 Commits
Author SHA1 Message Date
ypsdf1 b79754039f chore: 移除非项目文件,添加gitignore规则
- 从仓库追踪中移除txt/、工作簿1.xlsx、unnamed.patch等本地开发文件
- 更新.gitignore排除这些文件类型
2026-05-14 21:08:55 +08:00
ypsdf1 63d0a99cff Remove: 删除误传的radiopack.zip 2026-05-14 21:03:41 +08:00
39 changed files with 2000 additions and 19188 deletions
+7 -1
View File
@@ -36,4 +36,10 @@ build/
.vscode/
### Mac OS ###
.DS_Store
.DS_Store
# 开发辅助文件(本地保留,不推送到仓库)
txt/
*.xlsx
*.patch
新建*.txt
-1
View File
@@ -62,5 +62,4 @@
</plugin>
</plugins>
</build>
</project>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -25,7 +25,6 @@ public class ConfigManager {
public long smtpVerifyExpire = 0;
public long smtpLastSendTime = 0;
public String smtpConfiguringPlayer = "";
public String updatePlatform = "gh";
public ConfigManager(File dataFolder) {
+3 -14
View File
@@ -213,18 +213,6 @@ public class DatabaseManager {
+ ")");
safeAdd(st, "menu_snowball",
"INTEGER DEFAULT 1");
// ===== 独立任务进度表 =====
st.executeUpdate(
"CREATE TABLE IF NOT EXISTS "
+ "quest_progress ("
+ "player_name TEXT NOT NULL, "
+ "condition_key TEXT NOT NULL, "
+ "progress_value INTEGER DEFAULT 0, "
+ "completed INTEGER DEFAULT 0, "
+ "PRIMARY KEY (player_name, "
+ "condition_key)"
+ ")");
st.close();
logger.info("[Sdf1_login] 数据库初始化完成");
} catch (Exception e) {
@@ -873,8 +861,9 @@ public class DatabaseManager {
return list;
}
/*
获取指定ID的备份数据
/**
* 获取指定ID的备份数据
*/
public String[] getInventoryBackupById(
int backupId) {
+60 -319
View File
@@ -5,6 +5,7 @@ import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.*;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.File;
import java.util.*;
@@ -41,7 +42,7 @@ public class GUIManager {
g.setItem(14, mkItem(Material.EMERALD,
"§d§l积分商城"));
g.setItem(16, mkItem(Material.CHEST,
"§e§l任务中心"));
"§e§l新人礼包"));
g.setItem(18, mkItem(Material.BOOK,
"§6工单系统",
"§7提交bug、求助、举报"));
@@ -370,7 +371,6 @@ public class GUIManager {
"§7双击管理"));
slot++;
}
g.setItem(53, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
@@ -379,279 +379,90 @@ public class GUIManager {
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("主线任务");
"§e§l新人任务",
"§7完成新手阶段获取奖励"));
g.setItem(12, mkItem(Material.COMPASS,
"§b§l主线任务 §7["
+ qt.getCompletedStageCount(
player, mainQuests)
+ "/" + mainQuests.size()
+ "]",
"§b§l主线任务",
"§7查看主线任务列表"));
// 支线任务
List<QuestTracker.QuestFile> sideQuests =
qt.getQuests("支线任务");
g.setItem(14, mkItem(Material.PAPER,
"§a§l支线任务 §7["
+ qt.getCompletedStageCount(
player, sideQuests)
+ "/" + sideQuests.size()
+ "]",
"§a§l支线任务",
"§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返回"));
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
public void openGiftStages(Player p) {
Inventory g = Bukkit.createInventory(
null, 54, T_GIFT_STAGES);
null, 27, 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已领取奖励");
}
int max = plugin.getGift().getMaxStage();
for (int i = 0; i < max && i < 9; i++) {
int stage = i + 1;
boolean claimed = plugin.getDb()
.getField(p.getName(),
"gift_claimed") != null
&& ((String) plugin.getDb()
.getField(p.getName(),
"gift_claimed"))
.contains("[" + stage + "]");
boolean can = plugin.getGift()
.canClaim(p, stage);
Material mat = claimed ? Material.LIME_WOOL
: (can ? Material.YELLOW_WOOL
: Material.RED_WOOL);
String status = claimed ? "§a已领取"
: (can ? "§e可领取" : "§c未达标");
g.setItem(10 + i, mkItem(mat,
"§e" + qf.displayName,
lore.toArray(new String[0])));
"§e第" + stage + "阶段",
"§7" + status));
}
// 返回按钮
g.setItem(53, mkItem(
Material.ARROW, "§7返回"));
g.setItem(26, 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,
public void openTaskList(Player p, String folderName) {
File folder = new File(
plugin.getDataFolder(), folderName);
List<Map<String, Object>> tasks =
plugin.getGift()
.loadTasksFromFolder(folder);
int size = Math.max(54,
((tasks.size() / 9) + 1) * 9);
if (size > 54) size = 54;
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;
for (int i = 0; i < tasks.size()
&& i < 45; i++) {
Map<String, Object> task = tasks.get(i);
String name = (String) task.get("name");
List<String> conds =
(List<String>) task.get("conditions");
List<String> rewards =
(List<String>) task.get("rewards");
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.add("§7───── 条件 ─────");
if (conds != null)
for (String c : conds)
lore.add("§e" + c);
lore.add("");
lore.add("§7───── 奖励 ─────");
if (rewards != null)
for (String r2 : rewards)
lore.add("§a" + r2);
g.setItem(i, mkItem(Material.BOOK,
"§e" + name,
lore.toArray(new String[0])));
}
if (tasks.isEmpty()) {
g.setItem(22, mkItem(
Material.BARRIER,
g.setItem(22, mkItem(Material.BARRIER,
"§7暂无任务"));
}
g.setItem(size - 1, mkItem(
Material.ARROW, "§7返回"));
g.setItem(size - 1,
mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
@@ -683,81 +494,11 @@ public class GUIManager {
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 命令触发
+96 -89
View File
@@ -1,89 +1,96 @@
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 = plugin.getCommission()
.getCommissionRate(invitedBy);
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")
.replace("{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;
}
}
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")
.replace("{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;
}
}
+302 -302
View File
@@ -1,302 +1,302 @@
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.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该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密码长度不足6位");
return true;
}
if (!pwd.matches(".*[a-z].*")
|| !pwd.matches(".*[A-Z].*")
|| !pwd.matches(".*[0-9].*")) {
p.sendMessage("§c密码必须包含大写字母、"
+ "小写字母和数字");
return true;
}
String salt =
PasswordUtils.generateSalt();
String hash =
PasswordUtils.hash(pwd, salt);
plugin.getDb().createUser(
name, hash, salt);
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.getDb().recordIP(name, ip);
plugin.getDb().setField(name,
"register_time",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
p.sendMessage(plugin.getConfig2()
.msg("reg_success"));
playRegisterSound(p);
try {
org.bukkit.plugin.Plugin cy =
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;
}
private void playRegisterSound(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 boolean handleLogin(Player p,
String[] args) {
String name = p.getName();
try {
if (plugin.getLoggedIn().contains(name)) {
p.sendMessage(plugin.getConfig2()
.msg("already_logged_in"));
return true;
}
if (!plugin.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.getDb()
.getField(name, "password_salt");
if (salt == null) {
p.sendMessage(
"§c数据异常,请联系管理员");
plugin.getLogger().severe(
"[Sdf1_login] 玩家 " + name
+ " 盐值为null");
return true;
}
String hash =
PasswordUtils.hash(pwd, salt);
boolean matchMain =
plugin.getDb().checkPassword(
name, hash);
if (matchMain) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
// 只在有有效备份时恢复
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
p.sendMessage(plugin.getConfig2()
.msg("login_success"));
activateCY(p, name);
return true;
}
boolean matchTemp =
plugin.getDb()
.checkPasswordOrTemp(
name, hash);
if (matchTemp) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
plugin.getNeedsPasswordChange()
.add(name);
p.sendMessage(plugin.getConfig2()
.msg("login_success"));
p.sendMessage(
"§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
p.sendMessage(
"§7用法: /sdf1_login pw");
activateCY(p, name);
return true;
}
p.sendMessage(plugin.getConfig2()
.msg("login_failed"));
} catch (Exception e) {
plugin.getLogger().severe(
"[Sdf1_login] handleLogin异常: "
+ e.getMessage());
e.printStackTrace();
p.sendMessage(
"§c登录过程出错,请联系管理员");
}
return true;
}
public void handleReset(Player p) {
String name = p.getName();
String emailAddr = (String)
plugin.getDb().getField(
name, "email");
if (emailAddr == null
|| emailAddr.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()
+ 300000L);
plugin.getDb().setField(name,
"temp_pw_used", 0);
p.sendMessage("§e正在发送临时密码...");
final String to = emailAddr;
final String fName = name;
final String pwd = tempPwd;
final Main self = plugin;
Bukkit.getScheduler()
.runTaskAsynchronously(self,
() -> {
final boolean[] sent = {false};
Thread t = new Thread(() -> {
sent[0] = self.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().runTask(
self, () ->
p.sendMessage(
"§c邮件发送超时"));
return;
}
Bukkit.getScheduler().runTask(
self, () -> {
if (sent[0]) {
p.sendMessage(
"§a临时密码已发送到 "
+ to);
} else {
p.sendMessage(
"§c邮件发送失败");
}
});
});
}
private void activateCY(Player p,
String name) {
try {
org.bukkit.plugin.Plugin cy =
Bukkit.getPluginManager()
.getPlugin("CY_beibao");
if (cy != null && cy.isEnabled()) {
cy.getClass().getMethod(
"onSdf1Activation",
String.class,
int.class, int.class)
.invoke(cy, name, 0, 0);
}
} catch (Exception ignored) {
}
}
}
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.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该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密码长度不足6位");
return true;
}
if (!pwd.matches(".*[a-z].*")
|| !pwd.matches(".*[A-Z].*")
|| !pwd.matches(".*[0-9].*")) {
p.sendMessage("§c密码必须包含大写字母、"
+ "小写字母和数字");
return true;
}
String salt =
PasswordUtils.generateSalt();
String hash =
PasswordUtils.hash(pwd, salt);
plugin.getDb().createUser(
name, hash, salt);
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.getDb().recordIP(name, ip);
plugin.getDb().setField(name,
"register_time",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
p.sendMessage(plugin.getConfig2()
.msg("reg_success"));
playRegisterSound(p);
try {
org.bukkit.plugin.Plugin cy =
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;
}
private void playRegisterSound(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 boolean handleLogin(Player p,
String[] args) {
String name = p.getName();
try {
if (plugin.getLoggedIn().contains(name)) {
p.sendMessage(plugin.getConfig2()
.msg("already_logged_in"));
return true;
}
if (!plugin.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.getDb()
.getField(name, "password_salt");
if (salt == null) {
p.sendMessage(
"§c数据异常,请联系管理员");
plugin.getLogger().severe(
"[Sdf1_login] 玩家 " + name
+ " 盐值为null");
return true;
}
String hash =
PasswordUtils.hash(pwd, salt);
boolean matchMain =
plugin.getDb().checkPassword(
name, hash);
if (matchMain) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
// 只在有有效备份时恢复
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
p.sendMessage(plugin.getConfig2()
.msg("login_success"));
activateCY(p, name);
return true;
}
boolean matchTemp =
plugin.getDb()
.checkPasswordOrTemp(
name, hash);
if (matchTemp) {
plugin.getLoggedIn().add(name);
plugin.getDb().setLoggedIn(
name, true);
plugin.getDb().setField(name,
"last_login_time",
System.currentTimeMillis());
plugin.getDb().setField(name,
"last_online_check",
System.currentTimeMillis());
plugin.restoreInventory(p);
plugin.recordIPLogin(p);
plugin.giveMenuSnowball(p);
plugin.getNeedsPasswordChange()
.add(name);
p.sendMessage(plugin.getConfig2()
.msg("login_success"));
p.sendMessage(
"§c§l[警告] §f您使用的是临时密码,请尽快修改密码!");
p.sendMessage(
"§7用法: /sdf1_login pw");
activateCY(p, name);
return true;
}
p.sendMessage(plugin.getConfig2()
.msg("login_failed"));
} catch (Exception e) {
plugin.getLogger().severe(
"[Sdf1_login] handleLogin异常: "
+ e.getMessage());
e.printStackTrace();
p.sendMessage(
"§c登录过程出错,请联系管理员");
}
return true;
}
public void handleReset(Player p) {
String name = p.getName();
String emailAddr = (String)
plugin.getDb().getField(
name, "email");
if (emailAddr == null
|| emailAddr.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()
+ 300000L);
plugin.getDb().setField(name,
"temp_pw_used", 0);
p.sendMessage("§e正在发送临时密码...");
final String to = emailAddr;
final String fName = name;
final String pwd = tempPwd;
final Main self = plugin;
Bukkit.getScheduler()
.runTaskAsynchronously(self,
() -> {
final boolean[] sent = {false};
Thread t = new Thread(() -> {
sent[0] = self.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().runTask(
self, () ->
p.sendMessage(
"§c邮件发送超时"));
return;
}
Bukkit.getScheduler().runTask(
self, () -> {
if (sent[0]) {
p.sendMessage(
"§a临时密码已发送到 "
+ to);
} else {
p.sendMessage(
"§c邮件发送失败");
}
});
});
}
private void activateCY(Player p,
String name) {
try {
org.bukkit.plugin.Plugin cy =
Bukkit.getPluginManager()
.getPlugin("CY_beibao");
if (cy != null && cy.isEnabled()) {
cy.getClass().getMethod(
"onSdf1Activation",
String.class,
int.class, int.class)
.invoke(cy, name, 0, 0);
}
} catch (Exception ignored) {
}
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1641,4 +1641,4 @@ public class TicketManager {
}
return it;
}
}
}
+1 -2
View File
@@ -86,5 +86,4 @@ commands:
usage: /垃圾清理
menu:
description: 雪球菜单开关控制
usage: /menu <开/关>
commands:
usage: /menu <开/关>
-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;
}
}
}
-989
View File
@@ -1,989 +0,0 @@
Index: .gitattributes
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/.gitattributes b/.gitattributes
--- a/.gitattributes (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/.gitattributes (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -1,6 +1,5 @@
+*.md text=auto encoding=UTF-8
*.png binary
*.jpg binary
*.jpeg binary
-*.gif binary
-*.ico binary
-*.bmp binary
\ No newline at end of file
+*.gif binary
\ No newline at end of file
Index: src/main/java/Sdf1_login/ConfigManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/ConfigManager.java b/src/main/java/Sdf1_login/ConfigManager.java
--- a/src/main/java/Sdf1_login/ConfigManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/ConfigManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -312,6 +312,14 @@
requesterPointsReward = parseInt(
m.getOrDefault("报单人积分奖励", "10"),
10);
+ garbageEnabled = Boolean.parseBoolean(
+ m.getOrDefault("垃圾站_启用", "true"));
+ garbageInterval = parseInt(
+ m.getOrDefault("垃圾站_清理间隔秒",
+ "300"), 300);
+ garbageMaxRounds = parseInt(
+ m.getOrDefault("垃圾站_保留轮数",
+ "1"), 1);
}
@@ -331,12 +339,10 @@
L.add("挂机超时秒数=" + afkTimeout);
// 垃圾站设置
L.add("# ---- 垃圾站 ----");
- L.add("垃圾站_启用: "
- + garbageEnabled);
- L.add("垃圾站_清理间隔秒: "
- + garbageInterval);
- L.add("垃圾站_保留轮数: "
- + garbageMaxRounds);
+ L.add("垃圾站_启用=" + garbageEnabled);
+ L.add("垃圾站_清理间隔秒=" + garbageInterval);
+ L.add("垃圾站_保留轮数=" + garbageMaxRounds);
+
writeLines(file, L);
}
@@ -364,6 +370,11 @@
L.add("基础经济奖励=" + baseEconomyReward);
L.add("经济奖励每评分=" + providerEconomyPerScore);
L.add("报单人积分奖励=" + requesterPointsReward);
+ L.add("# ---- 垃圾站 ----");
+ L.add("垃圾站_启用=true");
+ L.add("垃圾站_清理间隔秒=300");
+ L.add("垃圾站_保留轮数=1");
+
writeLines(f, L);
}
Index: src/main/java/Sdf1_login/DatabaseManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/DatabaseManager.java b/src/main/java/Sdf1_login/DatabaseManager.java
--- a/src/main/java/Sdf1_login/DatabaseManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/DatabaseManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -201,7 +201,18 @@
+ "active INTEGER DEFAULT 1,"
+ "join_time INTEGER DEFAULT 0"
+ ")");
-
+ // 垃圾回收站物品表
+ st.execute("CREATE TABLE IF NOT EXISTS "
+ + "items ("
+ + "id INTEGER PRIMARY KEY "
+ + "AUTOINCREMENT,"
+ + "player_name TEXT NOT NULL,"
+ + "item_data TEXT DEFAULT '',"
+ + "amount INTEGER DEFAULT 1,"
+ + "create_time INTEGER DEFAULT 0"
+ + ")");
+ safeAdd(st, "menu_snowball",
+ "INTEGER DEFAULT 1");
st.close();
logger.info("[Sdf1_login] 数据库初始化完成");
} catch (Exception e) {
Index: src/main/java/Sdf1_login/GUIManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/GUIManager.java b/src/main/java/Sdf1_login/GUIManager.java
--- a/src/main/java/Sdf1_login/GUIManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/GUIManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -489,6 +489,12 @@
"§e设置密码"));
g.setItem(16, mkItem(Material.BARRIER,
"§c删除用户", "§7二次验证删除"));
+ // 雪球菜单检测(slot 14)
+ g.setItem(14, mkItem(
+ Material.SNOWBALL,
+ "§e§l雪球菜单",
+ "§7点击为该玩家发放菜单雪球"));
+
g.setItem(22, mkItem(Material.ARROW, "§7返回"));
p.openInventory(g);
}
Index: src/main/java/Sdf1_login/GarbageManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/GarbageManager.java b/src/main/java/Sdf1_login/GarbageManager.java
--- a/src/main/java/Sdf1_login/GarbageManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/GarbageManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -231,26 +231,26 @@
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 {
@@ -381,6 +381,38 @@
.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();
@@ -431,6 +463,10 @@
g.setItem(51, mk(Material.ARROW,
"§7已是最后一页"));
}
+ // 52格:返回按钮
+ g.setItem(52, mk(Material.ARROW,
+ "§7返回",
+ "§7返回主菜单"));
p.openInventory(g);
}
@@ -459,6 +495,12 @@
openRecyclePage(p, page + 1);
return true;
}
+ // 52:返回
+ if (slot == 52) {
+ plugin.getGui().openMain(p);
+ return true;
+ }
+
// 50/52/53:不处理
if (slot >= 49 && slot <= 53)
return true;
Index: src/main/java/Sdf1_login/GiftManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/GiftManager.java b/src/main/java/Sdf1_login/GiftManager.java
--- a/src/main/java/Sdf1_login/GiftManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/GiftManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -155,55 +155,18 @@
}
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();
+ String name = p.getName();
+ int curStage = ((Number) plugin.getDb()
+ .getField(name, "gift_stage"))
+ .intValue();
+ String claimed = (String) plugin.getDb()
+ .getField(name, "gift_claimed");
+ if (claimed == null) claimed = "";
+ return curStage >= stage
+ && !claimed.contains(
+ String.valueOf(stage));
+ }
- 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;
Index: src/main/java/Sdf1_login/Main.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java
--- a/src/main/java/Sdf1_login/Main.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/Main.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -196,6 +196,8 @@
getCommand("sdf1debug").setExecutor(this);
if (getCommand("oa") != null)
getCommand("oa").setExecutor(this);
+ if (getCommand("menu") != null)
+ getCommand("menu").setExecutor(this);
getServer().getPluginManager()
@@ -1099,6 +1101,9 @@
return;
}
}
+ if (isFrozen(p)) {
+ p.setAllowFlight(true);
+ }
if (!"manual".equals(config.approvalMode)
&& !db.userExists(name)) {
@@ -1238,36 +1243,82 @@
private static final String MENU_SNOWBALL_TAG =
"sdf1_menu";
+ // 雪球菜单
public void giveMenuSnowball(Player p) {
- // 检查背包里是否已有菜单雪球
+ // 检查开关
+ Object val = db.getField(
+ p.getName(), "menu_snowball");
+ if (val != null) {
+ int on = val instanceof Number
+ ? ((Number) val).intValue()
+ : 1;
+ if (on == 0) {
+ return; // 开关关了,不发放
+ }
+ }
+ // 检查是否已有
for (ItemStack it : p.getInventory()
.getContents()) {
if (isMenuSnowball(it)) {
- return; // 已有,不重复发
+ return;
}
}
- // 没有则发放
+ // 发放
ItemStack snow = new ItemStack(
Material.SNOWBALL);
ItemMeta im = snow.getItemMeta();
if (im != null) {
im.setDisplayName(
"§e§l[菜单] §f右键打开主菜单");
- List<String> lore = new ArrayList<>();
+ List<String> lore =
+ new ArrayList<>();
lore.add("§7右键点击打开功能菜单");
lore.add("§8" + MENU_SNOWBALL_TAG);
im.setLore(lore);
snow.setItemMeta(im);
}
- // 放到最后一个空格,避免覆盖
- int emptySlot =
- p.getInventory()
- .firstEmpty();
- if (emptySlot >= 0) {
+ int slot = p.getInventory()
+ .firstEmpty();
+ if (slot >= 0) {
+ p.getInventory().setItem(
+ slot, snow);
+ } else {
+ p.getWorld().dropItemNaturally(
+ p.getLocation(), snow);
+ }
+ }
+
+ /**
+ * 手动发放雪球菜单(绕过开关检查)
+ */
+ public void forceGiveMenuSnowball(
+ Player p) {
+ // 只检查是否已有
+ for (ItemStack it : p.getInventory()
+ .getContents()) {
+ if (isMenuSnowball(it)) {
+ return;
+ }
+ }
+ ItemStack snow = new ItemStack(
+ Material.SNOWBALL);
+ ItemMeta im = snow.getItemMeta();
+ if (im != null) {
+ im.setDisplayName(
+ "§e§l[菜单] §f右键打开主菜单");
+ List<String> lore =
+ new ArrayList<>();
+ lore.add("§7右键点击打开功能菜单");
+ lore.add("§8" + MENU_SNOWBALL_TAG);
+ im.setLore(lore);
+ snow.setItemMeta(im);
+ }
+ int slot = p.getInventory()
+ .firstEmpty();
+ if (slot >= 0) {
p.getInventory().setItem(
- emptySlot, snow);
+ slot, snow);
} else {
- // 背包满,丢在地上
p.getWorld().dropItemNaturally(
p.getLocation(), snow);
}
@@ -1296,21 +1347,24 @@
@EventHandler
public void onMove(PlayerMoveEvent e) {
Player p = e.getPlayer();
+ if (!isFrozen(p)) return;
+
Location from = e.getFrom();
Location to = e.getTo();
- if (isFrozen(p)) {
- if (to != null && (from.getX() != to.getX()
- || from.getY() != to.getY()
- || from.getZ() != to.getZ())) {
- e.setTo(e.getFrom());
- }
+ if (to == null) return;
+
+ // 允许转头(无位移)
+ if (from.getBlockX() == to.getBlockX()
+ && from.getBlockY() == to.getBlockY()
+ && from.getBlockZ() == to.getBlockZ()) {
return;
}
- if (to != null && (from.getX() != to.getX()
- || from.getY() != to.getY()
- || from.getZ() != to.getZ())) {
- afk.recordAction(p.getUniqueId());
- }
+
+ // 有位移 → 取消
+ e.setCancelled(true);
+
+ // 防止"服务器未启用飞行"踢出
+ p.setAllowFlight(true);
}
@EventHandler
@@ -1484,7 +1538,8 @@
- // ===== Inventory Click =====
+ // ===== Inventory Click =====
+ // ===== Inventory Click =====
@EventHandler
public void onInvClick(InventoryClickEvent e) {
if (!(e.getWhoClicked() instanceof Player))
@@ -1494,12 +1549,41 @@
e.setCancelled(true);
return;
}
- String title =
- e.getView().getTitle();
+ String title = e.getView().getTitle();
int slot = e.getRawSlot();
- if (needsPasswordChange.contains(p.getName())
- && !title.equals("§6§l任务面板")) {
+ // Shift+点击背包物品 → 存入垃圾箱
+ if (e.isShiftClick()
+ && e.getClickedInventory() != null
+ && e.getClickedInventory()
+ == p.getInventory()
+ && e.getCurrentItem() != null
+ && e.getCurrentItem().getType()
+ != Material.AIR
+ && !isMenuSnowball(
+ e.getCurrentItem())
+ && title.equals(
+ "§6§l垃圾回收站")) {
+ e.setCancelled(true);
+ ItemStack clicked =
+ e.getCurrentItem().clone();
+ garbage.saveItem(clicked);
+ e.getInventory().setItem(
+ e.getSlot(), null);
+ Bukkit.getScheduler()
+ .runTaskLater(this,
+ () -> {
+ garbage.openRecyclePage(p,
+ garbage.getRecyclePage(
+ p.getName()));
+ }, 1L);
+ return;
+ }
+
+ if (needsPasswordChange
+ .contains(p.getName())
+ && !title.equals(
+ "§6§l任务面板")) {
e.setCancelled(true);
p.sendMessage("§c请先修改密码!");
return;
@@ -1516,12 +1600,15 @@
TicketManager.T_TICKET_ADMIN)
|| title.equals(
TicketManager.T_TICKET_DETAIL)
- || title.equals("§d§l服务商面板")
- || title.equals("§6§l抢单大厅")
+ || title.equals(
+ "§d§l服务商面板")
+ || title.equals(
+ "§6§l抢单大厅")
|| title.equals(
"§b§l我处理中的工单")) {
e.setCancelled(true);
- ticket.handleClick(p, title, slot);
+ ticket.handleClick(
+ p, title, slot);
return;
}
@@ -1539,96 +1626,67 @@
ItemStack inSlot =
e.getInventory().getItem(slot);
- ItemStack onCursor = e.getCursor();
+ ItemStack cursor = e.getCursor();
- // 1. 格子里是垃圾站物品 → 取出
- if (isTrashDisplayItem(inSlot)) {
- e.setCancelled(true);
- try {
- List<String> lore =
- inSlot.getItemMeta()
- .getLore();
- String numStr =
- lore.get(0)
- .replace(
- "§7ID: #", "")
- .trim();
- int dbId = Integer
- .parseInt(numStr);
- // 先从DB删除
- if (garbage.removeItem(
- dbId)) {
- // 再给玩家(去掉ID lore)
- ItemStack give =
- inSlot.clone();
- ItemMeta gm =
- give.getItemMeta();
- if (gm != null) {
- List<String> newLore =
- new ArrayList<>();
- // 保留原始lore(跳过ID和数量行)
- if (lore.size() > 2) {
- for (int li = 2;
- li < lore
- .size();
- li++) {
- newLore.add(
- lore.get(
- li));
- }
- }
- gm.setLore(
- newLore.isEmpty()
- ? null
- : newLore);
+ // ===== 取出 =====
+ if (inSlot != null
+ && inSlot.hasItemMeta()
+ && inSlot.getItemMeta().hasLore()) {
+ List<String> lore =
+ inSlot.getItemMeta().getLore();
+ if (lore != null && !lore.isEmpty()
+ && lore.get(0)
+ .startsWith("§7ID: #")) {
+ e.setCancelled(true);
+ try {
+ String numStr = lore.get(0)
+ .replace("§7ID: #", "")
+ .trim();
+ int dbId =
+ Integer.parseInt(numStr);
+ if (garbage.removeItem(dbId)) {
+ ItemStack give =
+ inSlot.clone();
+ ItemMeta gm =
+ give.getItemMeta();
+ gm.setLore(null);
give.setItemMeta(gm);
- }
- p.getInventory()
- .addItem(give);
- // 清空格子
- e.getInventory()
- .setItem(slot,
- null);
- }
- } catch (Exception ex) {
- p.sendMessage("§c取出失败");
- }
- return;
+ p.getInventory()
+ .addItem(give);
+ e.getInventory()
+ .setItem(slot, null);
+ }
+ } catch (Exception ex) {
+ p.sendMessage("§c取出失败");
+ }
+ return;
+ }
}
- // 2. 格子空 + 手上有物品 → 放入
- if ((inSlot == null
- || inSlot.getType()
- == Material.AIR)
- && onCursor != null
- && onCursor.getType()
+ // ===== 光标存入 =====
+ if (cursor != null
+ && cursor.getType()
!= Material.AIR
- && !isMenuSnowball(
- onCursor)) {
- // 不取消!让物品自然进入格子
- Bukkit.getScheduler()
- .runTaskLater(this,
- () -> {
- ItemStack after =
- e.getInventory()
- .getItem(
- slot);
- if (after != null
- && after.getType()
- != Material.AIR) {
- garbage.saveItem(
- after
- .clone());
- e.getInventory()
- .setItem(
- slot,
- null);
- }
- }, 1L);
+ && !isMenuSnowball(cursor)) {
+ ItemStack toSave = cursor.clone();
+ getLogger().info(
+ "[垃圾站] 存入: "
+ + toSave.getType()
+ + " x" + toSave.getAmount());
+ garbage.saveItem(toSave);
+ e.setCancelled(true);
+ Bukkit.getScheduler().runTaskLater(
+ this, () -> {
+ p.getOpenInventory()
+ .setCursor(null);
+ garbage.openRecyclePage(p,
+ garbage.getRecyclePage(
+ p.getName()));
+ }, 1L);
return;
}
- // 3. 其他情况
+ // 默认取消
e.setCancelled(true);
return;
}
@@ -1806,6 +1864,7 @@
p.closeInventory();
p.sendMessage("§e请输入邀请码:");
}
+
return;
}
@@ -1828,6 +1887,7 @@
.replace("§e", "")
.replace("§a", ""));
}
+
return;
}
@@ -1856,7 +1916,18 @@
p.closeInventory();
p.sendMessage("§e输入" + tgt
+ "的临时密码:");
- } else if (slot == 16) {
+ } else if (slot == 14) {
+ Player tp = Bukkit.getPlayer(tgt);
+ if (tp != null && tp.isOnline()) {
+ forceGiveMenuSnowball(tp);
+ p.sendMessage("§a已发放雪球菜单给 "
+ + tgt);
+ } else {
+ p.sendMessage("§c玩家不在线");
+ }
+
+
+ } else if (slot == 16) {
chatInput.getState(p).type =
ChatInputManager.InputType
.ADMIN_DELETE_CONFIRM;
@@ -1866,8 +1937,11 @@
p.sendMessage(config.msg(
"admin_delete_confirm"));
}
+
return;
+
}
+
// 积分商城
if (title.startsWith("§d§l积分商城")) {
@@ -1958,43 +2032,45 @@
// 回收站拖拽
if (t.equals("§6§l垃圾回收站")) {
- for (int rawSlot : e.getRawSlots()) {
- if (rawSlot >= 49
- && rawSlot <= 53) {
+ for (int rawSlot :
+ e.getRawSlots()) {
+ if (rawSlot >= 49) {
e.setCancelled(true);
return;
}
}
- // 0-48:不取消,延迟存DB
+ // 0-48允许拖入,不取消
+ // 延迟2tick后扫描格子
+ final Player fp = p;
Bukkit.getScheduler()
- .runTaskLater(this, () -> {
- for (int rawSlot :
- e.getRawSlots()) {
- if (rawSlot < 0
- || rawSlot >= 49)
- continue;
- ItemStack item =
- e.getInventory()
- .getItem(
- rawSlot);
- if (item != null
- && item.getType()
- != Material.AIR
- && !isMenuSnowball(
- item)) {
- garbage.saveItem(
- item.clone());
- e.getInventory()
- .setItem(
- rawSlot,
- null);
- }
- }
- }, 1L);
+ .runTaskLater(this,
+ () -> {
+ Inventory top = fp
+ .getOpenInventory()
+ .getTopInventory();
+ for (int rawSlot :
+ e.getRawSlots()) {
+ if (rawSlot < 0
+ || rawSlot >= 49)
+ continue;
+ ItemStack item =
+ top.getItem(rawSlot);
+ if (item != null
+ && item.getType()
+ != Material.AIR
+ && !isMenuSnowball(
+ item)) {
+ garbage.saveItem(
+ item.clone());
+ top.setItem(
+ rawSlot, null);
+ }
+ }
+ }, 2L);
return;
}
-
+ //服务商工单系统
if (t.equals(TicketManager.T_TICKET_MAIN)
|| t.equals(
TicketManager.T_TICKET_CREATE)
@@ -2050,6 +2126,38 @@
+ count + " 个掉落物");
return true;
}
+ // /menu 开关雪球菜单
+ if (cmdName.equals("menu")) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(
+ "§c仅玩家可用");
+ return true;
+ }
+ Player p2 = (Player) sender;
+ if (args.length == 0) {
+ p2.sendMessage(
+ "§e用法: /menu <开/关>");
+ return true;
+ }
+ String arg = args[0];
+ if (arg.equals("开")) {
+ db.setField(p2.getName(),
+ "menu_snowball", 1);
+ p2.sendMessage(
+ "§a雪球菜单已开启,"
+ + "下次进入将发放");
+ } else if (arg.equals("关")) {
+ db.setField(p2.getName(),
+ "menu_snowball", 0);
+ p2.sendMessage(
+ "§c雪球菜单已关闭,"
+ + "下次进入不再发放");
+ } else {
+ p2.sendMessage(
+ "§e用法: /menu <开/关>");
+ }
+ return true;
+ }
if (cmdName.equals("reg")
|| cmdName.equals("注册")) {
@@ -2461,9 +2569,9 @@
sender.sendMessage(
"§e§l================================");
}
- }
+ }
- // pw / password
+ // pw / password
if (sub.equals("pw")
|| sub.equals("password")) {
if (!(sender instanceof Player)) {
@@ -2790,6 +2898,29 @@
}
return true;
}
+ // give - 手动发放雪球菜单
+ if (sub.equals("give")) {
+ if (args.length < 2) {
+ sender.sendMessage(
+ "§e用法: /sdf1_login give <玩家名>");
+ return true;
+ }
+ Player tp = Bukkit.getPlayer(
+ args[1]);
+ if (tp == null || !tp.isOnline()) {
+ sender.sendMessage(
+ "§c玩家不在线: "
+ + args[1]);
+ return true;
+ }
+ forceGiveMenuSnowball(tp);
+ tp.sendMessage(
+ "§a管理员已为您发放菜单雪球");
+ sender.sendMessage(
+ "§a已发放雪球菜单给 "
+ + args[1]);
+ return true;
+ }
// shopadd
if (sub.equals("shopadd")) {
@@ -3052,4 +3183,4 @@
return list;
}
-}
+}
\ No newline at end of file
Index: src/main/java/Sdf1_login/TicketManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/Sdf1_login/TicketManager.java b/src/main/java/Sdf1_login/TicketManager.java
--- a/src/main/java/Sdf1_login/TicketManager.java (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/java/Sdf1_login/TicketManager.java (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -1014,39 +1014,27 @@
openDetail(p, id);
return true;
}
- // 管理员:标记完结(自己在处理)
- if (adm(p) && "processing".equals(st)
+ // 管理员:标记完结(已回复状态)
+ if (adm(p) && "replied".equals(st)
&& asg.equals("admin")) {
- Map<String, Object> tk =
- plugin.getDb().getTicket(id);
- String tkType = sv(tk, "type");
- if (TYPE_DELETE.equals(tkType)) {
- approveDeleteTicket(p, id);
- return true;
- }
- plugin.getDb().completeTicket(id);
- p.sendMessage("§a工单 #" + id
- + " 已标记完结");
- openDetail(p, id);
- return true;
- }
- // 管理员:标记完结(已回复状态)
- if (adm(p) && "replied".equals(st)
- && asg.equals("admin")) {
- Map<String, Object> tk =
- plugin.getDb().getTicket(id);
- String tkType = sv(tk, "type");
- if (TYPE_DELETE.equals(tkType)) {
- approveDeleteTicket(p, id);
- return true;
- }
- plugin.getDb().completeTicket(id);
- p.sendMessage("§a工单 #" + id
- + " 已标记完结");
- openDetail(p, id);
- return true;
+
+ // 管理员:标记完结(已回复状态)
+ if (adm(p) && "replied".equals(st)
+ && asg.equals("admin")) {
+ Map<String, Object> tk =
+ plugin.getDb().getTicket(id);
+ String tkType = sv(tk, "type");
+ if (TYPE_DELETE.equals(tkType)) {
+ approveDeleteTicket(p, id);
+ return true;
+ }
+ plugin.getDb().completeTicket(id);
+ p.sendMessage("§a工单 #" + id
+ + " 已标记完结");
+ openDetail(p, id);
+ return true;
+ }
}
-
// 服务商:抢单
if (plugin.getDb()
.isServiceProvider(p.getName())
Index: src/main/resources/plugin.yml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
--- a/src/main/resources/plugin.yml (revision 145b392112786b20534254a7a38d716657c962ab)
+++ b/src/main/resources/plugin.yml (revision 87de2e12c81f5a5e04825113e7835aca09e0fd09)
@@ -84,3 +84,6 @@
垃圾清理:
description: 手动清理掉落物
usage: /垃圾清理
+ menu:
+ description: 雪球菜单开关控制
+ usage: /menu <开/关>
\ No newline at end of file
BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
playSpecific无结果,位置:Main.java