Files
Sdf1_login/backup/20260605_185500/Main.java.backup
T

5568 lines
209 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package Sdf1_login;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.bukkit.event.inventory.*;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.NamespacedKey;
import org.bukkit.persistence.PersistentDataType;
import java.util.Date;
import org.bukkit.command.CommandSender;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionEffect;
import org.bukkit.scheduler.BukkitTask;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerPickupItemEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.player.PlayerFishEvent;
import org.bukkit.scheduler.BukkitTask;
import java.util.HashMap;
public class Main extends JavaPlugin
implements CommandExecutor, Listener,
TabCompleter {
private DatabaseManager db;
private MenuIconManager menuIconMgr;
private ConfigManager config;
private EmailManager email;
private LoginManager loginMgr;
private AFKManager afk;
private GiftManager gift;
private CheckInManager checkIn;
private InviteManager invite;
private PointsManager points;
public GUIManager gui;
private ChatInputManager chatInput;
private Economy economy;
private VerificationManager verification;
private IPGroupManager ipGroup;
private AccountRequestManager accountRequest;
private TicketManager ticket;
private ChatFilterManager chatFilter;
private GarbageManager garbage;
public RadioManager radio;
// 在 lastActivity 字段后面添加:
// [ADDED] 任务追踪系统
private QuestTracker questTracker;
// [FIX] 垃圾箱防抖锁(记录上次操作时间戳)
private final Map<UUID, Long> garbageBusy =
new ConcurrentHashMap<>();
// [ADDED] 群系检测节流
private final Map<UUID, Long> lastBiomeCheck =
new ConcurrentHashMap<>();
// 在 private WelcomeManager welcome; 字段(若不存在)添加:
private WelcomeManager welcome;
private final Set<String> pendingAdminAuth =
new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
/*
资源包下载部分控制,不经过RadioDownloadListener的熔断控制
*/
private RadioDownloadListener radioDL;
private final Set<String> chatInputActive =
ConcurrentHashMap.newKeySet();
// 补签确认:玩家名 → "日期:总积分"
public final Map<String, String>
pendingBackCheck =
new HashMap<>();
public MenuManager menu;
private final Set<String> loggedIn =
new TreeSet<>(
String.CASE_INSENSITIVE_ORDER);
private final Set<String> needsPasswordChange =
new TreeSet<>(
String.CASE_INSENSITIVE_ORDER);
private final Map<UUID, Long> joinTime =
new ConcurrentHashMap<>();
private final Map<UUID, Location> joinLoc =
new HashMap<>();
private final Map<String, Long> ipAutoLogin =
new HashMap<>();
private static final long IP_SKIP_MS = 300000L;
private SalesStatsManager salesStats;
private final Map<UUID, Long> lastActivity =
new ConcurrentHashMap<>();
private CommissionManager commission;
// 余额操作待处理
private String balanceTarget = null;
private boolean balanceGiveMode = false;
private BondManager bondManager;
private CDKManager cdkManager;
private UserGroupManager userGroupManager;
private CypayCommand cypayCommand;
public BondManager getBonds() {
return bondManager;
}
public CDKManager getCDK() {
return cdkManager;
}
public UserGroupManager getUserGroup() {
return userGroupManager;
}
public CypayCommand getCypay() {
return cypayCommand;
}
// 在 Main.java 字段声明区域(bondManager 附近)添加:
// 在 bondManager 字段附近添加:
private BondPrinter bondPrinter;
private OrderManager orderManager;
public OrderManager getOrderManager() {
return orderManager;
}
public AreaProtection areaProtection;
// 钱包流水查看目标(玩家名 → 查看目标)
private final Map<String, String> walletViewTarget =
new ConcurrentHashMap<>();
// 流水格式化
private static final java.text.SimpleDateFormat
TX_SDF = new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
private ShopManager shopManager; //商店
public CommissionManager getCommission() {
return commission;
}
//pvp
private PVPManager pvpManager;
private static class PwdRollback {
final String hash;
final String salt;
final long time;
PwdRollback(String h, String s, long t) {
hash = h;
salt = s;
time = t;
}
}
// 待删除的玩家名(仅保留最后一个)
private String pendingDeleteName = null;
private BukkitRunnable pendingDeleteTask = null;
private final Map<String, PwdRollback>
pwdRollback =
new ConcurrentHashMap<>();
private final Map<String, String> menuChatField =
new java.util.concurrent.ConcurrentHashMap<>();
private final Map<String, Integer> menuChatIdx =
new java.util.concurrent.ConcurrentHashMap<>();
public void recordPasswordChange(String name,
String oldHash, String oldSalt) {
pwdRollback.put(name,
new PwdRollback(oldHash, oldSalt,
System.currentTimeMillis()));
}
// ===== Getters =====
public DatabaseManager getDb() {
return db;
}
public ConfigManager getConfig2() {
return config;
}
public EmailManager getEmail() {
return email;
}
public Economy getEconomy() {
return economy;
}
public LoginManager getLoginMgr() {
return loginMgr;
}
public AFKManager getAfk() {
return afk;
}
public GiftManager getGift() {
return gift;
}
public CheckInManager getCheckIn() {
return checkIn;
}
public InviteManager getInvite() {
return invite;
}
public PointsManager getPoints() {
return points;
}
public GUIManager getGui() {
return gui;
}
public ChatInputManager getChatInput() {
return chatInput;
}
public VerificationManager getVerification() {
return verification;
}
public IPGroupManager getIPGroup() {
return ipGroup;
}
public AccountRequestManager getAccountRequest() {
return accountRequest;
}
public TicketManager getTicket() {
return ticket;
}
public QuestTracker getQuestTracker() {
return questTracker;
}
public ChatFilterManager getChatFilter() {
return chatFilter;
}
public GarbageManager getGarbage() {
return garbage;
}
public Set<String> getLoggedIn() {
return loggedIn;
}
public Set<String> getNeedsPasswordChange() {
return needsPasswordChange;
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent e) {
if (areaProtection != null) {
Player p = e.getPlayer();
areaProtection.onPlayerOffline(
p.getUniqueId(), p.getName(), "");
if (areaProtection != null) {
areaProtection.onPlayerQuit(p.getName());
}
}
}
// public RadioManager radio;
@Override
public void onEnable() {
getDataFolder().mkdirs();
salesStats = new SalesStatsManager(this);
// ===== 1. 基础配置 =====
config = new ConfigManager(getDataFolder());
config.loadMessages();
config.loadSmtp();
config.loadSettings();
configMgr = new ConfigManager(getDataFolder());
configMgr.loadSettings();
configMgr.loadMessages();
orderManager = new OrderManager(this);
// ===== 2. 数据库 =====
db = new DatabaseManager(getDataFolder());
db.init();
menuIconMgr = new MenuIconManager(this);
// ===== 3. 菜单 =====
menu = new MenuManager(this);
menu.loadMenu();
// ===== 4. GUI(依赖 menu) =====
gui = new GUIManager(this);
shopManager = new ShopManager(this);
Bukkit.getPluginManager().registerEvents(shopManager, this);
// shopManager.loadCategories(); // 构造器内已调用,此处重复
getServer().getPluginManager()
.registerEvents(gui, this);
// ===== 5. 其他管理器 =====
email = new EmailManager(config);
loginMgr = new LoginManager(this);
afk = new AFKManager(config, this);
gift = new GiftManager(this);
gift.loadStages();
checkIn = new CheckInManager(this);
invite = new InviteManager(this);
points = new PointsManager(this);
chatInput = new ChatInputManager();
verification = new VerificationManager(this);
ipGroup = new IPGroupManager(this,
config.maxAccountsPerIP);
accountRequest =
new AccountRequestManager(this);
accountRequest.setMode(config.approvalMode);
accountRequest.setAutoDelayMinutes(
config.autoApproveDelayMinutes);
ticket = new TicketManager(this);
commission = new CommissionManager(this);
questTracker = new QuestTracker(this);
chatFilter = new ChatFilterManager(this);
chatFilter.loadConfig();
welcome = new WelcomeManager(this);
// ===== 6. 垃圾箱 =====
garbage = new GarbageManager(this);
garbage.init();
garbage.loadConfig();
garbage.startAutoCleanup();
// ===== 7. Radio =====
radio = new RadioManager(this);
radio.init();
radioDL = new RadioDownloadListener(this);
getServer().getPluginManager()
.registerEvents(radioDL, this);
// ===== 铁块电梯 =====
getServer().getPluginManager()
.registerEvents(new IronBlockElevator(), this);
// ===== 8. 经济 =====
setupEconomy();
// ===== 9. 注册命令 =====
if (getCommand("sdf1_login") != null) {
getCommand("sdf1_login")
.setExecutor(this);
getCommand("sdf1_login")
.setTabCompleter(this);
}
if (getCommand("reg") != null)
getCommand("reg").setExecutor(this);
if (getCommand("login") != null)
getCommand("login").setExecutor(this);
if (getCommand("l") != null)
getCommand("l").setExecutor(this);
if (getCommand("签到") != null)
getCommand("签到")
.setExecutor(this);
if (getCommand("recycle") != null)
getCommand("recycle")
.setExecutor(this);
if (getCommand("垃圾清理") != null)
getCommand("垃圾清理")
.setExecutor(this);
/* if (getCommand("oa") != null)
getCommand("oa").setExecutor(this);*/
if (getCommand("menu") != null)
getCommand("menu").setExecutor(this);
if (getCommand("printer") != null) {
getCommand("printer").setExecutor(this);
getCommand("shop").setExecutor(this);
getCommand("商店").setExecutor(this);
// getCommand("testorder").setExecutor(this);
if (getCommand("printer") != null) {
getCommand("printer").setExecutor(this);
getCommand("shop").setExecutor(this);
getCommand("商店").setExecutor(this);
}
// protect 命令独立注册
if (getCommand("protect") != null) {
getCommand("protect").setExecutor(this);
getCommand("protect").setTabCompleter(this);
}
}
// ===== 10. 注册事件 =====
getServer().getPluginManager()
.registerEvents(this, this);
// ===== 11. 定时任务 =====
afk.startCheck();
startLoginReminder();
startTimeoutCheck();
startPasswordReminder();
startTicketAutoProcess();
radio.startMainRadio();
new BukkitRunnable() {
public void run() {
if (questTracker == null) return;
for (Player p :
Bukkit.getOnlinePlayers()) {
if (!isFrozen(p))
questTracker.onPlayTime(
p.getName(), 30000L);
}
}
}.runTaskTimer(this, 600L, 600L);
// [新增] Printer 文件清理(每小时检测一次)
new BukkitRunnable() {
public void run() {
if (bondPrinter != null)
bondPrinter.cleanupOldFiles();
}
}.runTaskTimer(this, 1200L, 72000L);
// 12 ===PVP===
pvpManager = new PVPManager(this);
getServer().getPluginManager()
.registerEvents(pvpManager, this);
// PVP区域检测定时器
getServer().getScheduler()
.runTaskTimer(this,
() -> pvpManager.tickRegions(),
20L, 20L);
setupPVPTab();
// 13 ====cypay债券====
// 债券系统(独立DB)
bondManager = new BondManager(this);
cdkManager = new CDKManager(this);
userGroupManager = new UserGroupManager(this);
cypayCommand = new CypayCommand(this);
/*加载CDK文件
cdkManager.loadCDKsFromDir();*/
// cypay 命令
if (getCommand("cypay") != null) {
getCommand("cypay").setExecutor(cypayCommand);
getCommand("cypay").setTabCompleter(cypayCommand);
}
bondPrinter = new BondPrinter(this);
// cdkManager.loadCDKsFromDir();
//14 ====商店====
// ★ 构造器内已加载完毕,此处重复
// if (shopManager != null) {
// shopManager.loadCategories();
// }
configMgr = new ConfigManager(getDataFolder());
configMgr.loadSettings();
orderManager = new OrderManager(this);
// 15 ======区域保护=========
areaProtection = new AreaProtection(this);
getServer().getPluginManager()
.registerEvents(areaProtection, this);
// 效果清除定时器(只留一个)
getServer().getScheduler().runTaskTimer(this, () -> {
if (areaProtection != null) {
}
}, 20L, 20L);
// 和平模式独立检测
getServer().getScheduler().runTaskTimer(this, () -> {
if (areaProtection == null) return;
for (Player p : Bukkit.getOnlinePlayers()) {
AreaProtection.AreaConfig ac = areaProtection.getArea(
p.getWorld().getName(),
p.getLocation().getBlockX(),
p.getLocation().getBlockY(),
p.getLocation().getBlockZ());
if (ac != null) {
// 无条件打印,不管 peaceMode 是啥
/* getLogger().info("[和平定时器] " + p.getName()
+ " 在[" + ac.name
+ "] peaceMode=" + ac.peaceMode
+ " 白名单=" + ac.peaceWhitelist);*/
if (ac.peaceMode) {
areaProtection.banHostilesWithWhitelist(p, ac);
}
}
}
}, 40L, 40L);
areaProtection.startEnforceTask();
if (areaProtection != null) {
areaProtection.reload();
areaProtection.startEnforceTask();
}
getLogger().info("§b[Sdf1_login]启动完毕\n§lS欢迎使用sdf1系列插件,如有问题,您可在\nGitHub和Gitee提交反馈");
getLogger().info("sdf1系列插件包含:\nsdf1自助兑奖插件\nCY_beibao云背包\nsdf1_login登陆插件\nSdf1_game娱乐游戏插件");
// 大字画
getLogger().info("\n" +
" ____ _ __ _ _ _ \n" +
" / ___| __| |/ _/ | | | ___ __ _(_)_ __ \n" +
" \\___ \\ / _` | |_| | | |/ _ \\ / _` | | '_ \\ \n" +
" ___) | (_| | _| | | | (_) | (_| | | | | | \n" +
" |____/ \\__,_|_| |_|___|_|\\___/ \\__, |_|_| |_| \n" +
" ____ _ __ |_____| _ |___/ _ \n" +
" / ___| __| |/ _/ |_ __ | |_ _ __ _(_)_ __ \n" +
" \\___ \\ / _` | |_| | '_ \\| | | | |/ _` | | '_ \\ \n" +
" ___) | (_| | _| | |_) | | |_| | (_| | | | | | \n" +
" |____/ \\__,_|_| |_| .__/|_|\\__,_|\\__, |_|_| |_|___ _ __ _ \n" +
" _ __ _____ |_|___ _ __ | |___/_ _ / ___| __| |/ _/ |\n" +
" | '_ \\ / _ \\ \\ /\\ / / _ \\ '__| | '_ \\| | | | \\___ \\ / _` | |_| |\n" +
" | |_) | (_) \\ V V / __/ | | |_) | |_| | ___) | (_| | _| |\n" +
" | .__/ \\___/ \\_/\\_/ \\___|_| |_.__/ \\__, | |____/ \\__,_|_| |_|\n" +
" |_| |___/ "
);
}
public BondPrinter getBondPrinter() {
return bondPrinter;
}
public ShopManager getShopManager() {
return shopManager;
}
private ConfigManager configMgr;
public ConfigManager getConfigMgr() {
return configMgr;
}
private void setupPVPTab() {
if (getCommand("pvp") != null) {
getCommand("pvp")
.setTabCompleter(
(sender, cmd,
alias, args) -> {
List<String> list =
new ArrayList<>();
if (args.length == 1) {
String[] sub = {
"create", "stats",
"list", "delete",
"tool", "on", "off",
"tempban"};
for (String s : sub) {
if (s.startsWith(
args[0]
.toLowerCase())) {
list.add(s);
}
}
} else if (args.length == 2) {
String sub =
args[0].toLowerCase();
if (sub.equals("stats")) {
for (Player pp : Bukkit
.getOnlinePlayers()) {
if (pp.getName()
.toLowerCase()
.startsWith(args[1]
.toLowerCase())) {
list.add(pp.getName());
}
}
} else if (sub.equals("delete")
|| sub.equals("on")
|| sub.equals("off")
|| sub.equals("tempban")) {
for (String rn :
pvpManager
.getRegionNames()) {
if (rn.toLowerCase()
.startsWith(args[1]
.toLowerCase())) {
list.add(rn);
}
}
}
}
return list;
});
}
}
@Override
public void onDisable() {
if (shopManager != null) shopManager.saveAll();
// 取消未执行的删除任务
if (pendingDeleteTask != null) {
pendingDeleteTask.cancel();
pendingDeleteTask = null;
getLogger().info(
"[Sdf1_login] 服务器关闭,"
+ "取消待删除: "
+ pendingDeleteName);
pendingDeleteName = null;
}
if (db != null) db.close();
if (garbage != null) garbage.close();
if (questTracker != null)
questTracker.shutdown();
if (areaProtection != null) {
areaProtection.stopEnforceTask();
}
for (BukkitTask task : areaProtection.getPendingTasks()) {
task.cancel();
}
areaProtection.saveWhitelists();
areaProtection.stopEnforceTask();
// 取消所有延时清理任务,立即清理
if (areaProtection != null) {
for (Map.Entry<UUID, BukkitTask> entry
: areaProtection.getPendingClearTasks().entrySet()) {
entry.getValue().cancel();
// 立即清理效果
Player p = Bukkit.getPlayer(entry.getKey());
if (p != null && p.isOnline()) {
List<PotionEffectType> marked =
areaProtection.getPlayerMarkedEffects()
.remove(entry.getKey());
if (marked != null) {
for (PotionEffectType type : marked) {
p.removePotionEffect(type);
}
}
}
}
areaProtection.stopEnforceTask();
areaProtection.saveWhitelists();
areaProtection.getAlreadyForced().clear();
}
}
private void setupEconomy() {
if (getServer().getPluginManager()
.getPlugin("Vault") == null) return;
RegisteredServiceProvider<Economy> rsp =
getServer().getServicesManager()
.getRegistration(
Economy.class);
if (rsp != null)
economy = rsp.getProvider();
}
// ===== Timers =====
private void startTimeoutCheck() {
new BukkitRunnable() {
public void run() {
long now = System.currentTimeMillis();
Iterator<Map.Entry<UUID, Long>> it =
joinTime.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<UUID, Long> entry =
it.next();
if (now - entry.getValue()
> config.loginTimeout
* 1000L) {
Player p = Bukkit.getPlayer(
entry.getKey());
if (p != null && p.isOnline()
&& !loggedIn.contains(
p.getName()))
p.kickPlayer(config.msg(
"login_timeout"));
it.remove();
}
}
}
}.runTaskTimer(this, 80L, 80L);
}
private void startLoginReminder() {
new BukkitRunnable() {
public void run() {
Iterator<UUID> it =
joinTime.keySet().iterator();
while (it.hasNext()) {
UUID uuid = it.next();
Player p = Bukkit.getPlayer(uuid);
if (p == null || !p.isOnline()) {
it.remove();
continue;
}
if (loggedIn.contains(
p.getName())) {
it.remove();
continue;
}
// 已注册→提示登录,未注册→提示注册
if (db.userExists(p.getName()))
p.sendMessage(config.msg(
"not_logged_in"));
else
p.sendMessage(config.msg(
"not_registered"));
}
}
}.runTaskTimer(this, 200L, 200L);
}
private void startPasswordReminder() {
new BukkitRunnable() {
public void run() {
for (Player p :
Bukkit.getOnlinePlayers()) {
if (needsPasswordChange.contains(
p.getName())) {
p.sendTitle("§c§l请修改密码",
"§f使用 /sdf1_login pw 修改密码",
10, 40, 10);
p.playSound(p.getLocation(),
Sound.BLOCK_NOTE_BLOCK_PLING,
1.0f, 1.0f);
}
}
}
}.runTaskTimer(this, 200L, 200L);
}
private void startTicketAutoProcess() {
new BukkitRunnable() {
public void run() {
if (ticket != null)
ticket.autoProcessCompleted();
}
}.runTaskTimer(this, 6000L, 6000L);
}
// ===== Utils =====
public String getPlayerIP(Player p) {
InetSocketAddress addr = p.getAddress();
if (addr == null) return null;
return addr.getAddress().getHostAddress();
}
public void resetActivity(Player p) {
lastActivity.put(p.getUniqueId(),
System.currentTimeMillis());
}
public boolean canAutoLogin(Player p) {
String ip = getPlayerIP(p);
if (ip == null) return false;
Long last = ipAutoLogin.get(ip);
return last != null
&& System.currentTimeMillis() - last
< IP_SKIP_MS;
}
public void recordIPLogin(Player p) {
String ip = getPlayerIP(p);
if (ip != null)
ipAutoLogin.put(ip,
System.currentTimeMillis());
}
public boolean isFrozen(Player p) {
return !loggedIn.contains(p.getName());
}
private String debugCodePoints(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (i > 0) sb.append(" ");
sb.append(String.format("U+%04X",
(int) s.charAt(i)));
}
return sb.toString();
}
private boolean isAdmin(CommandSender sender) {
if (sender instanceof Player)
return ((Player) sender)
.getScoreboardTags()
.contains(config.adminTag);
return true;
}
private void clearPlayer(Player p) {
p.getInventory().clear();
p.getInventory().setArmorContents(
new ItemStack[4]);
p.getInventory().setExtraContents(
new ItemStack[1]);
p.setLevel(0);
p.setExp(0f);
}
public void restoreInventory(Player p) {
String name = p.getName();
try {
String[] backup =
db.loadInventoryBackup(name);
if (backup == null
|| !isValidBackup(backup)) {
return;
}
debugPrintBase64("恢复前", name,
backup[0]);
ItemStack[] contents =
InventorySerializer
.fromBase64(backup[0]);
if (contents != null)
p.getInventory()
.setContents(contents);
if (backup[1] != null
&& !backup[1].isEmpty()) {
ItemStack[] armor =
InventorySerializer
.fromBase64(
backup[1]);
if (armor != null)
p.getInventory()
.setArmorContents(
armor);
}
if (backup[2] != null
&& !backup[2].isEmpty()) {
ItemStack[] extra =
InventorySerializer
.fromBase64(
backup[2]);
if (extra != null)
p.getInventory()
.setExtraContents(
extra);
}
int lv = Integer.parseInt(
backup[3]);
if (lv > 0) p.setLevel(lv);
double xp = Double.parseDouble(
backup[4]);
if (xp > 0)
p.setExp((float) xp);
debugPrintBase64("恢复后", name,
InventorySerializer.toBase64(
p.getInventory()
.getContents()));
getLogger().info(
"[Sdf1_login] " + name
+ " 背包已恢复");
} catch (Exception e) {
getLogger().severe(
"[Sdf1_login] 还原背包失败: "
+ name);
e.printStackTrace();
}
}
public void backupInventory(Player p) {
String name = p.getName();
int cnt = countItems(p)
+ countArmor(p)
+ countExtra(p);
if (cnt == 0) {
getLogger().warning(
"[Sdf1_login] " + name
+ " 背包为空,跳过备份");
return;
}
try {
String invB64 =
InventorySerializer.toBase64(
p.getInventory()
.getContents());
String armorB64 =
InventorySerializer.toBase64(
p.getInventory()
.getArmorContents());
String extraB64 =
InventorySerializer.toBase64(
p.getInventory()
.getExtraContents());
db.saveInventoryBackup(name, invB64,
armorB64, extraB64,
p.getLevel(), p.getExp());
getLogger().info(
"[Sdf1_login] " + name
+ " 背包已备份 (物品:"
+ cnt + ")");
} catch (Exception e) {
getLogger().severe(
"[Sdf1_login] 背包备份失败: "
+ name);
e.printStackTrace();
}
}
private void hideInventory(Player p) {
String name = p.getName();
try {
debugPrintBase64("清空前", name,
InventorySerializer.toBase64(
p.getInventory()
.getContents()));
// 清空背包(备份已在上次退出时保存)
p.getInventory().clear();
p.getInventory()
.setArmorContents(null);
p.getInventory()
.setExtraContents(null);
p.setLevel(0);
p.setExp(0);
debugPrintBase64("清空后", name,
InventorySerializer.toBase64(
p.getInventory()
.getContents()));
} catch (Exception e) {
getLogger().severe(
"[Sdf1_login] hideInventory失败: "
+ name);
e.printStackTrace();
}
}
private void hideInventoryDelayed(Player p,
int attempt) {
String name = p.getName();
if (!p.isOnline()) return;
if (!isFrozen(p)) return;
// 从数据库加载已有备份
String[] existing =
db.loadInventoryBackup(name);
// 调试打印
debugPrintBase64("加入-加载DB", name,
existing != null
? existing[0] : null);
// 无有效备份 → 跳过清空背包
if (existing == null
|| !isValidBackup(existing)) {
getLogger().info(
"[Sdf1_login] " + name
+ " 无有效备份,"
+ "跳过清空背包");
return;
}
// 有有效备份 → 清空当前背包
hideInventory(p);
}
public void autoLogin(Player p,
String registerType) {
String name = p.getName();
String ip = getPlayerIP(p);
String uuid =
p.getUniqueId().toString();
String defaultPwd = "123456aA";
if (!db.userExists(name)) {
if (config.maxAccountsPerIP > 0
&& ip != null
&& !ipGroup.canRegister(ip)) {
p.kickPlayer(
buildIPKickMessage(p, ip));
return;
}
if (!"manual".equals(
config.approvalMode)) {
AccountRequestManager.Request req =
accountRequest.createRequest(
name, name, ip);
if (req != null) {
p.kickPlayer(
config.msg("need_approval")
.replace("{id}",
String.valueOf(
req.id)));
return;
}
}
String salt =
PasswordUtils.generateSalt();
String hash = PasswordUtils.hash(
defaultPwd, salt);
db.createUser(name, hash, salt);
db.setField(name,
"premium_uuid", uuid);
db.setField(name,
"register_ip", ip);
db.setField(name,
"register_type", registerType);
db.recordIP(name, ip);
}
loggedIn.add(name);
p.setAllowFlight(false);
p.setFlying(false);
db.setLoggedIn(name, true);
db.setField(name,
"last_login_time",
System.currentTimeMillis());
db.setField(name,
"last_online_check",
System.currentTimeMillis());
db.recordIP(name, ip);
joinTime.remove(p.getUniqueId());
recordIPLogin(p);
// 只在有有效备份时恢复
restoreInventory(p);
giveMenuSnowball(p);
p.sendMessage("§a[Sdf1_login] §f您已自动登录!");
if (welcome != null) welcome.onLogin(p);
activateBeibao(p);
pushPendingAlerts(p);
}
private void activateBeibao(Player 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, p.getName(),
0, 0);
} catch (Exception ignored) {
}
}
public void triggerSecurityAlert(Player player,
String suspiciousIP) {
String name = player.getName();
db.recordSecurityAlert(name, suspiciousIP);
Bukkit.broadcastMessage(
"§c§l[安全警告] §e" + name
+ " §f账号疑似遭到 §c"
+ suspiciousIP
+ " §f的盗号尝试");
String emailAddr =
(String) db.getField(name, "email");
if (emailAddr != null
&& !emailAddr.isEmpty()) {
String body = "玩家 " + name
+ ":\n您的账号在 "
+ new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss")
.format(new java.util.Date())
+ " 疑似遭到 IP: "
+ suspiciousIP
+ " 的盗号尝试。\n"
+ "建议立即修改密码。";
final String to = emailAddr;
final String finalBody = body;
Bukkit.getScheduler()
.runTaskAsynchronously(this,
() -> email
.sendTempPassword(
to, name,
finalBody));
}
}
public void pushPendingAlerts(Player p) {
String name = p.getName();
List<Map<String, Object>> alerts =
db.getUnnotifiedAlerts(name);
if (alerts.isEmpty()) return;
p.sendMessage("§c§l========== 安全报警 ==========");
for (Map<String, Object> alert : alerts) {
p.sendMessage("§e时间: §f"
+ alert.get("alert_date"));
p.sendMessage("§e可疑IP: §c"
+ alert.get("alert_ip"));
db.markAlertNotified(
((Number) alert.get("id"))
.intValue());
}
p.sendMessage(
"§c建议使用 §e/sdf1_login pw §c修改密码");
p.sendMessage(
"§c§l==================================");
}
public void handleReset(Player p) {
loginMgr.handleReset(p);
}
public boolean dailySign(Player p) {
if (checkIn.isCheckedInToday(p.getName()))
return false;
p.sendMessage(checkIn.checkIn(p));
invite.onInviteeCheckIn(p.getName());
return true;
}
public void openTaskPanel(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, "§6§l任务面板");
ItemStack gl = new ItemStack(
Material.GRAY_STAINED_GLASS_PANE);
ItemMeta glm = gl.getItemMeta();
if (glm != null) {
glm.setDisplayName(" ");
gl.setItemMeta(glm);
}
for (int i = 0; i < 27; i++)
g.setItem(i, gl);
// 打卡按钮(独立,slot 11)
g.setItem(11, mkItem(Material.COMPASS,
"§b§l群系打卡",
"§7在不同群系打卡完成任务",
"§7打卡范围: 32格去重"));
// 新人任务(slot 15)
g.setItem(15, mkItem(Material.BOOK,
"§e新人任务",
"§7完成新手阶段获取奖励"));
// 返回
g.setItem(22, mkItem(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
public void openAccountRequestPanel(Player p) {
Inventory g = Bukkit.createInventory(
null, 27, "§e§l账号请求");
ItemStack gl = new ItemStack(
Material.GRAY_STAINED_GLASS_PANE);
ItemMeta glm = gl.getItemMeta();
if (glm != null) {
glm.setDisplayName(" ");
gl.setItemMeta(glm);
}
for (int i = 0; i < 27; i++)
g.setItem(i, gl);
g.setItem(10, mkItem(Material.BARRIER,
"§c申请删除账号",
"§7申请删除自己名下的账号"));
g.setItem(13, mkItem(Material.PAPER,
"§6提交工单",
"§7提交bug反馈、求助、举报等"));
g.setItem(16, mkItem(Material.REDSTONE,
"§e找回密码",
"§7通过邮箱找回密码"));
g.setItem(22, mkItem(Material.ARROW,
"§7返回"));
p.openInventory(g);
}
private void playSuccessSound(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);
}
private int countItems(Player p) {
int c = 0;
for (ItemStack it : p.getInventory()
.getContents())
if (it != null && it.getType()
!= Material.AIR) c++;
return c;
}
private int countArmor(Player p) {
int c = 0;
for (ItemStack it : p.getInventory()
.getArmorContents())
if (it != null && it.getType()
!= Material.AIR) c++;
return c;
}
private int countExtra(Player p) {
int c = 0;
for (ItemStack it : p.getInventory()
.getExtraContents())
if (it != null && it.getType()
!= Material.AIR) c++;
return c;
}
/**
* 检查备份是否有效(包含非雪球的真实物品)
*/
public boolean isValidBackup(String[] backup) {
if (backup == null
|| backup[0] == null
|| backup[0].isEmpty()) {
return false;
}
try {
ItemStack[] items =
InventorySerializer
.fromBase64(backup[0]);
if (items == null) return false;
for (ItemStack item : items) {
if (item == null
|| item.getType()
== Material.AIR)
continue;
if (isMenuSnowball(item))
continue;
return true;
}
} catch (Exception e) {
return false;
}
return false;
}
/**
* 控制台打印 base64 前10位
*/
private void debugPrintBase64(String label,
String name, String base64) {
if (base64 == null || base64.isEmpty()) {
getLogger().info(
"[BACKUP_DEBUG] " + label
+ " " + name
+ ": <empty>");
return;
}
String preview =
base64.length() > 10
? base64.substring(0, 10)
: base64;
getLogger().info(
"[BACKUP_DEBUG] " + label + " "
+ name + ": " + preview);
}
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) {
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;
}
private String buildIPKickMessage(Player p,
String ip) {
List<Map<String, Object>> accounts =
db.getAccountDetailsByIP(ip);
StringBuilder sb = new StringBuilder();
sb.append("§c§l======== IP注册已满 ========\n");
sb.append("§e您的IP §f").append(ip)
.append(" §e下已注册 §f")
.append(accounts.size())
.append(" §e个账号:\n");
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy-MM-dd HH:mm");
int idx = 1;
for (Map<String, Object> acc : accounts) {
long regTime = ((Number) acc.get(
"register_time")).longValue();
String dateStr = regTime > 0
? sdf.format(new java.util.Date(
regTime))
: "未知";
sb.append("§7").append(idx)
.append(". §f")
.append(acc.get("player_name"))
.append(" §7注册于: §e")
.append(dateStr).append("\n");
idx++;
}
sb.append("§7-----------------------------\n");
sb.append("§e请使用已有账号登录\n");
sb.append("§e登录后输入 §f/oa §e提交工单\n");
sb.append("§c§l============================");
return sb.toString();
}
/**
* 读取备份,返回物品名预览
*/
private String getBackupItemPreview(
int backupId) {
try {
String[] backup =
db.getInventoryBackupById(
backupId);
if (backup == null
|| backup[0] == null
|| backup[0].isEmpty())
return "§7空";
ItemStack[] items =
InventorySerializer
.fromBase64(
backup[0]);
if (items == null)
return "§7空";
Map<String, Integer> counts =
new LinkedHashMap<>();
for (ItemStack it : items) {
if (it == null
|| it.getType()
== Material.AIR)
continue;
String name = translateMaterial(
it.getType().name());
counts.merge(name,
it.getAmount(),
Integer::sum);
}
if (counts.isEmpty())
return "§7空";
StringBuilder sb =
new StringBuilder();
int shown = 0;
for (Map.Entry<String, Integer> en
: counts.entrySet()) {
if (shown >= 5) {
sb.append(" §7...");
break;
}
if (shown > 0)
sb.append("§7, ");
sb.append("§f").append(en.getKey())
.append("§7x")
.append(en.getValue());
shown++;
}
return sb.toString();
} catch (Exception e) {
return "§7解析失败";
}
}
private String translateMaterial(
String name) {
switch (name) {
case "GRASS_BLOCK":
return "草方块";
case "OAK_LOG":
return "橡木原木";
case "OAK_PLANKS":
return "橡木木板";
case "STONE":
return "石头";
case "COBBLESTONE":
return "圆石";
case "DIRT":
return "泥土";
case "SAND":
return "沙子";
case "GRAVEL":
return "砂砾";
case "DIAMOND":
return "钻石";
case "DIAMOND_ORE":
return "钻石矿石";
case "IRON_INGOT":
return "铁锭";
case "GOLD_INGOT":
return "金锭";
case "COAL":
return "煤炭";
case "COAL_ORE":
return "煤矿石";
case "RAW_IRON":
return "粗铁";
case "RAW_GOLD":
return "粗金";
case "OAK_LEAVES":
return "橡树树叶";
case "OAK_SAPLING":
return "橡树树苗";
case "STICK":
return "木棍";
case "CRAFTING_TABLE":
return "工作台";
case "FURNACE":
return "熔炉";
case "CHEST":
return "箱子";
case "TORCH":
return "火把";
case "BEDROCK":
return "基岩";
case "TNT":
return "TNT";
case "REDSTONE":
return "红石";
case "REDSTONE_ORE":
return "红石矿石";
case "LAPIS_LAZULI":
return "青金石";
case "EMERALD":
return "绿宝石";
case "EMERALD_ORE":
return "绿宝石矿石";
case "OBSIDIAN":
return "黑曜石";
case "GLOWSTONE":
return "萤石";
case "ARROW":
return "箭";
case "BOW":
return "弓";
case "IRON_SWORD":
return "铁剑";
case "DIAMOND_SWORD":
return "钻石剑";
case "IRON_PICKAXE":
return "铁镐";
case "DIAMOND_PICKAXE":
return "钻石镐";
case "IRON_AXE":
return "铁斧";
case "DIAMOND_AXE":
return "钻石斧";
case "IRON_HELMET":
return "铁头盔";
case "IRON_CHESTPLATE":
return "铁胸甲";
case "IRON_LEGGINGS":
return "铁护腿";
case "IRON_BOOTS":
return "铁靴子";
case "DIAMOND_HELMET":
return "钻石头盔";
case "DIAMOND_CHESTPLATE":
return "钻石胸甲";
case "DIAMOND_LEGGINGS":
return "钻石护腿";
case "DIAMOND_BOOTS":
return "钻石靴子";
case "BREAD":
return "面包";
case "COOKED_BEEF":
return "牛排";
case "COOKED_PORKCHOP":
return "熟猪排";
case "COOKED_CHICKEN":
return "熟鸡肉";
case "GOLDEN_APPLE":
return "金苹果";
case "ENDER_PEARL":
return "末影珍珠";
case "BLAZE_ROD":
return "烈焰棒";
case "GHAST_TEAR":
return "恶魂之泪";
case "NETHER_STAR":
return "下界之星";
case "SHULKER_SHELL":
return "潜影壳";
case "ELYTRA":
return "鞘翅";
case "TOTEM_OF_UNDYING":
return "不死图腾";
case "TRIDENT":
return "三叉戟";
case "ENCHANTED_GOLDEN_APPLE":
return "附魔金苹果";
default:
// 转中文名格式:SANDSTONE -> 沙石
return name.toLowerCase()
.replace("_", " ");
}
}
// ===== Events =====
@EventHandler
public void onJoin(PlayerJoinEvent e) {
Player p = e.getPlayer();
String name = p.getName();
String ip = getPlayerIP(p);
// 强制重发资源包(清掉客户端拒绝记录)
Bukkit.getScheduler()
.runTaskLater(this, () -> {
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
if (radio != null) {
final Player jp = p;
Bukkit.getScheduler().runTaskLater(
this, () -> {
if (jp.isOnline()) {
radio.sendResourcePack(jp);
}
}, 60L);
}
}, 20L);
// 重置聊天输入状态(防止上次退出时残留)
chatInput.reset(p);
if (needsPasswordChange.contains(name))
needsPasswordChange.remove(name);
// 改完了直接打包,要打sdf1一起
lastActivity.put(p.getUniqueId(),
System.currentTimeMillis());
if (needsPasswordChange.contains(name))
needsPasswordChange.remove(name);
boolean isBedrock =
verification.isBedrockPlayer(p);
boolean isOnlineMode =
verification.isOnlineMode();
if (!db.userExists(name)) {
if (config.maxAccountsPerIP > 0
&& ip != null
&& !ipGroup.canRegister(ip)) {
p.kickPlayer(
buildIPKickMessage(p, ip));
return;
}
}
if (isFrozen(p)) {
p.setAllowFlight(true);
}
if (!"manual".equals(config.approvalMode)
&& !db.userExists(name)) {
AccountRequestManager.Request req =
accountRequest.createRequest(
name, name, ip);
if (req != null) {
p.kickPlayer(
config.msg("need_approval")
.replace("{id}",
String.valueOf(
req.id)));
return;
}
}
// [ADDED] 资源包(延迟发送,不强制)
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
if (radio != null) {
final Player jp = p;
Bukkit.getScheduler().runTaskLater(
this, () -> {
if (jp.isOnline()) {
radio.sendResourcePack(jp);
}
}, 60L);
}
if (isOnlineMode) {
autoLogin(p, "premium");
return;
}
if (isBedrock) {
autoLogin(p, "bedrock");
return;
}
if (canAutoLogin(p)) {
autoLogin(p, "ip_reconnect");
return;
}
afk.remove(p.getUniqueId());
afk.recordAction(p.getUniqueId());
hideInventoryDelayed(p, 0);
// [ADDED] 发送资源包
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
if (radio != null) {
final Player jp = p;
Bukkit.getScheduler().runTaskLater(
this, () -> {
if (jp.isOnline()) {
radio.sendResourcePack(jp);
}
}, 60L);
}
// [ADDED] 发送资源包(延迟5秒确保客户端就绪)
// ★ 资源包:只发送一次,延迟3秒确保客户端就绪
if (radio != null) {
final Player jp = p;
Bukkit.getScheduler().runTaskLater(
this, () -> {
if (jp.isOnline()) {
radio.sendResourcePack(jp);
}
}, 60L);
}
verification.verifyPremiumAsync(p,
isPremium -> {
if (isPremium) {
Bukkit.getScheduler()
.runTask(this,
() -> autoLogin(p,
"premium"));
} else {
Bukkit.getScheduler()
.runTask(this, () -> {
joinTime.put(
p.getUniqueId(),
System.currentTimeMillis());
if (!db.userExists(name))
p.sendMessage(config.msg(
"not_registered"));
else
p.sendMessage(config.msg(
"not_logged_in"));
});
}
});
}
@EventHandler
public void onQuit(PlayerQuitEvent e) {
Player p = e.getPlayer();
String name = p.getName();
chatInput.reset(p);
boolean wasLoggedIn =
loggedIn.contains(name);
// 判断是否需要保存:
// 1. 已登录 → 保存当前背包
// 2. 未登录但有真实物品 → 保存
// (插件安装前的老玩家)
// 3. 未登录且背包被插件清空 → 不保存
// (避免覆盖有效备份)
boolean hasRealItems = false;
for (ItemStack it : p.getInventory()
.getContents()) {
if (it != null
&& it.getType()
!= Material.AIR
&& !isMenuSnowball(it)) {
hasRealItems = true;
break;
}
}
if (wasLoggedIn || hasRealItems) {
try {
String contents =
InventorySerializer
.toBase64(
p.getInventory()
.getContents());
String armor =
InventorySerializer
.toBase64(
p.getInventory()
.getArmorContents());
String extra =
InventorySerializer
.toBase64(
p.getInventory()
.getExtraContents());
int level = p.getLevel();
double exp = p.getExp();
debugPrintBase64(
"退出保存", name,
contents);
db.saveInventoryBackup(name,
contents, armor, extra,
level, exp);
} catch (Exception ex) {
getLogger().severe(
"[Sdf1_login] "
+ "退出保存背包失败: "
+ name);
}
} else {
getLogger().info(
"[BACKUP_DEBUG] 退出跳过 "
+ name
+ ": 背包为空"
+ "(插件已清空)");
}
if (wasLoggedIn) {
loggedIn.remove(name);
db.setLoggedIn(name, false);
recordIPLogin(p);
}
lastActivity.remove(p.getUniqueId());
joinTime.remove(p.getUniqueId());
joinLoc.remove(p.getUniqueId());
needsPasswordChange.remove(name);
// [ADDED] 清除 Radio 熔断状态
// [ADDED] 清除 Radio 熔断状态
if (radioDL != null)
radioDL.onPlayerQuit(
p.getUniqueId());
pendingAdminAuth.remove(name);
}
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;
if (isCustomMenuTrigger(it)) return;
}
ItemStack customIcon =
menuIconMgr.getIcon(p);
if (customIcon != null) {
tagAsMenuTrigger(customIcon);
// 加外观标识
ItemMeta cm = customIcon.getItemMeta();
if (cm != null) {
String originalName =
cm.getDisplayName();
if (originalName == null
|| originalName.isEmpty()
|| originalName.equals(
customIcon.getType()
.name())) {
originalName = "自定义物品";
}
cm.setDisplayName(
"\u00a7e\u00a7l[菜单] \u00a7f"
+ originalName);
List<String> cmLore =
cm.hasLore()
? new ArrayList<>(cm.getLore())
: new ArrayList<>();
// 避免重复添加
if (cmLore.isEmpty()
|| !cmLore.get(0).contains(
"\u00a77右键打开主菜单")) {
cmLore.add(0,
"\u00a77右键打开主菜单");
}
cm.setLore(cmLore);
customIcon.setItemMeta(cm);
}
int slot = p.getInventory()
.firstEmpty();
if (slot >= 0) {
p.getInventory().setItem(
slot, customIcon);
} else {
p.getWorld().dropItemNaturally(
p.getLocation(), customIcon);
}
return;
}
ItemStack snow = new ItemStack(
Material.SNOWBALL);
ItemMeta im = snow.getItemMeta();
if (im != null) {
im.setDisplayName(
"\u00a7e\u00a7l[菜单] \u00a7f右键打开主菜单");
List<String> lore = new ArrayList<>();
lore.add("\u00a77右键点击打开功能菜单");
lore.add("\u00a78" + MENU_SNOWBALL_TAG);
im.setLore(lore);
snow.setItemMeta(im);
}
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(
slot, snow);
} else {
p.getWorld().dropItemNaturally(
p.getLocation(), snow);
}
}
private boolean isMenuSnowball(ItemStack item) {
if (item == null
|| item.getType()
!= Material.SNOWBALL)
return false;
if (!item.hasItemMeta())
return false;
ItemMeta im = item.getItemMeta();
if (im == null) return false;
if (!im.hasLore()) return false;
List<String> lore = im.getLore();
if (lore == null) return false;
for (String line : lore) {
if (line.contains(MENU_SNOWBALL_TAG))
return true;
}
return false;
}
// 运行时初始化版本(避免静态加载问题)
private NamespacedKey getTriggerKey() {
return new NamespacedKey(this, "menu_trigger");
}
/**
* 给物品打上菜单触发标记
*/
private void tagAsMenuTrigger(ItemStack item) {
if (item == null) return;
ItemMeta meta = item.getItemMeta();
if (meta == null) return;
meta.getPersistentDataContainer().set(
getTriggerKey(),
PersistentDataType.STRING,
"true");
item.setItemMeta(meta);
}
/**
* 检查物品是否带菜单触发标记
*/
private boolean isCustomMenuTrigger(
ItemStack item) {
if (item == null) return false;
ItemMeta meta = item.getItemMeta();
if (meta == null) return false;
return meta.getPersistentDataContainer()
.has(getTriggerKey(),
PersistentDataType.STRING);
}
@EventHandler
public void onMove(PlayerMoveEvent e) {
Player p = e.getPlayer();
Location from = e.getFrom();
Location to = e.getTo();
if (to == null) return;
// 冻结处理(未登录玩家不能移动)
if (isFrozen(p)) {
if (from.getBlockX() == to.getBlockX()
&& from.getBlockY() == to.getBlockY()
&& from.getBlockZ() == to.getBlockZ()) {
return;
}
e.setCancelled(true);
p.setAllowFlight(true);
}
}
private List<String> filterTab(List<String> opts, String prefix) {
List<String> r = new ArrayList<>();
for (String o : opts) {
if (o.toLowerCase().startsWith(prefix.toLowerCase())) {
r.add(o);
}
}
return r;
}
/*
雪球菜单:
同时检测左右手
*/
@EventHandler
public void onInteract(PlayerInteractEvent e) {
Player p = e.getPlayer();
if (isFrozen(p)) {
e.setCancelled(true);
return;
}
ItemStack hand = p.getInventory()
.getItemInMainHand();
ItemStack offhand = p.getInventory()
.getItemInOffHand();
if (isMenuSnowball(hand)
|| isMenuSnowball(offhand)) {
e.setCancelled(true);
gui.openMain(p);
return;
}
if (isCustomMenuTrigger(hand)
|| isCustomMenuTrigger(offhand)) {
if (db.hasMenuIcon(p.getName())) {
e.setCancelled(true);
gui.openMain(p);
}
}
}
@EventHandler
public void onBlockBreak(BlockBreakEvent e) {
Player p = e.getPlayer();
if (isFrozen(p)) {
e.setCancelled(true);
return;
}
afk.recordAction(p.getUniqueId());
// [ADDED] 任务计数
if (questTracker != null)
questTracker.onBlockMined(p.getName(),
e.getBlock().getType().name());
}
@EventHandler
public void onBlockPlace2(BlockPlaceEvent e) {
Player p = e.getPlayer();
if (isFrozen(p)) return;
afk.recordAction(p.getUniqueId());
if (questTracker != null)
questTracker.onBlockPlaced(
p.getName(),
e.getBlock().getType().name());
}
@EventHandler
public void onChatTrack(AsyncPlayerChatEvent e) {
if (isFrozen(e.getPlayer())) return;
if (questTracker != null)
questTracker.onPlayerChat(
e.getPlayer().getName());
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent e) {
if (isFrozen(e.getPlayer()))
e.setCancelled(true);
}
@EventHandler
public void onDrop(PlayerDropItemEvent e) {
if (isFrozen(e.getPlayer())) {
e.setCancelled(true);
return;
}
ItemStack drop =
e.getItemDrop().getItemStack();
if (isMenuSnowball(drop)) {
e.setCancelled(true);
}
}
@EventHandler
public void onPickup(PlayerPickupItemEvent e) {
if (isFrozen(e.getPlayer()))
e.setCancelled(true);
}
@EventHandler
public void onDamage(EntityDamageEvent e) {
if (e.getEntity() instanceof Player) {
if (isFrozen((Player) e.getEntity()))
e.setCancelled(true);
}
}
@EventHandler
public void onEntityAttack(
EntityDamageByEntityEvent e) {
if (e.getDamager() instanceof Player
&& isFrozen(
(Player) e.getDamager()))
e.setCancelled(true);
if (e.getEntity() instanceof Player
&& isFrozen(
(Player) e.getEntity()))
e.setCancelled(true);
}
// [ADDED] 击杀追踪
@EventHandler
public void onEntityDeath(EntityDeathEvent e) {
if (questTracker == null) return;
Player killer = e.getEntity().getKiller();
if (killer == null || isFrozen(killer)) return;
if (e.getEntity() instanceof Player)
questTracker.onPlayerKill(killer.getName());
else
questTracker.onMobKill(killer.getName(),
e.getEntityType().name());
}
// [ADDED] 合成追踪
@EventHandler
public void onCraft(CraftItemEvent e) {
if (e.getWhoClicked() instanceof Player) {
Player p = (Player) e.getWhoClicked();
if (!isFrozen(p) && questTracker != null)
questTracker.onItemCrafted(p.getName());
}
}
// [ADDED] 钓鱼追踪
@EventHandler
public void onFish(PlayerFishEvent e) {
Player p = e.getPlayer();
if (p == null || isFrozen(p)) return;
if (questTracker != null
&& e.getState() == PlayerFishEvent.State.CAUGHT_FISH)
questTracker.onFishCaught(p.getName());
}
// [ADDED] 村民交易追踪
@EventHandler(priority = EventPriority.MONITOR,
ignoreCancelled = true)
public void onVillagerTrade(InventoryClickEvent e) {
if (!(e.getWhoClicked() instanceof Player))
return;
Player p = (Player) e.getWhoClicked();
if (isFrozen(p)) return;
if (questTracker == null) return;
Inventory topInv =
e.getView().getTopInventory();
if (topInv == null
|| topInv.getHolder() == null)
return;
if (!(topInv.getHolder()
instanceof org.bukkit.entity.Villager))
return;
// 结果槽 = 2
if (e.getRawSlot() != 2) return;
ItemStack result = topInv.getItem(2);
if (result == null
|| result.getType() == Material.AIR)
return;
final Player fp = p;
Bukkit.getScheduler().runTaskLater(this,
() -> {
if (fp.isOnline())
questTracker.onVillagerTrade(
fp.getName());
}, 2L);
}
@EventHandler
public void onChat(AsyncPlayerChatEvent e) {
Player p = e.getPlayer();
String msg = e.getMessage();
// ★ CDK/经济/债券输入拦截 — 必须在所有聊天处理之前
if (this.getCDK() != null && this.getCDK().isListening(p.getName())) {
e.setCancelled(true);
this.getCDK().onChat(p, msg);
return;
}
// ===== 菜单聊天输入 =====
if (getMenu().isEditing(p.getName())) {
e.setCancelled(true);
getMenu().onChat(p, msg);
return;
}
// ===== 冻结检查 =====
if (isFrozen(p)) {
e.setCancelled(true);
return;
}
// ===== 聊天输入流程(改密码/绑邮箱等) =====
if (chatInput.isInFlow(p)) {
e.setCancelled(true);
chatInput.handleInput(p, msg, this);
return;
}
// ===== 管理员密码验证 =====
if (pendingAdminAuth.contains(p.getName())) {
e.setCancelled(true);
pendingAdminAuth.remove(p.getName());
if (msg.equals(config.adminPassword)) {
p.sendMessage("§a验证通过");
p.sendMessage("§7输入的密码已验证");
} else {
p.sendMessage("§c密码错误");
}
return;
}
// ===== 聊天过滤 - 开关检查 =====
if (!chatFilter.isEnabled()) return;
// ===== 白名单玩家 =====
if (chatFilter.isPlayerWhitelisted(
p.getName())) return;
// ===== 禁言检查 =====
if (chatFilter.isMuted(p.getName())) {
e.setCancelled(true);
p.sendMessage(chatFilter.msg(
"chat_muted"));
return;
}
// ===== URL检测 =====
List<String> urls =
chatFilter.extractUrls(msg);
if (urls.isEmpty()) return;
// 过滤白名单
List<String> blocked = new ArrayList<>();
for (String url : urls) {
if (!chatFilter.isWhitelisted(url)) {
blocked.add(url);
}
}
if (blocked.isEmpty()) return;
// 取消消息
e.setCancelled(true);
for (String url : blocked) {
p.sendMessage(chatFilter.msg(
"chat_url_blocked",
"url", url));
}
// 增加违规计数
chatFilter.incrementViolation(p.getName());
int count = chatFilter.getViolationCount(
p.getName());
p.sendMessage(chatFilter.msg(
"chat_url_violation",
"count", String.valueOf(count)));
// 通知管理员
if (chatFilter.isNotifyAdmin()) {
String notify = chatFilter.msg(
"chat_url_admin_notify",
"player", p.getName(),
"url", blocked.get(0),
"count", String.valueOf(count));
for (Player op :
Bukkit.getOnlinePlayers()) {
if (op.hasPermission(
"sdf1.admin")
|| op.isOp()) {
op.sendMessage(notify);
}
}
}
// 通报
if (chatFilter.isNotifyAll()) {
Bukkit.broadcastMessage(
chatFilter.msg(
"chat_url_broadcast",
"player",
p.getName()));
}
// 处罚
chatFilter.applyPunishment(p, count);
// ===== 补签确认 =====
if (pendingBackCheck.containsKey(
p.getName())) {
e.setCancelled(true);
String data = pendingBackCheck
.remove(p.getName());
String[] parts = data.split(":");
String dateStr = parts[0];
int totalPoints =
Integer.parseInt(parts[1]);
if (msg.trim().equals("1")
|| msg.trim()
.equalsIgnoreCase("yes")
|| msg.trim().contains("是")
|| msg.trim().contains("愿意")) {
int extra = totalPoints - 10;
if (extra > 0
&& !db.deductPoints(
p.getName(), extra)) {
db.addPoints(p.getName(), 10);
p.sendMessage("§c积分也不足,"
+ "10积分已退还");
return;
}
checkIn.doBackCheckIn(p, dateStr);
p.sendMessage("§a补签成功!日期: "
+ dateStr + " §7(扣除"
+ totalPoints
+ "积分,免经济)");
playSuccessSound(p);
} else {
db.addPoints(p.getName(), 10);
p.sendMessage("§c补签已取消,"
+ "10积分已退还");
}
return;
}
}
// 在 getter 区域添加
public MenuManager getMenu() {
return menu;
}
public PVPManager getPVPManager() {
return pvpManager;
}
// 在 Main.java 中(与其他 getter 放一起)
public AreaProtection getAreaProtection() {
return areaProtection;
}
// ==================== 我的钱包 ====================
public void openMyWallet(Player p) {
openWalletGUI(p, p.getName());
}
private void openWalletGUI(Player p, String target) {
Inventory g = Bukkit.createInventory(
null, 27, "§6§l我的钱包");
ItemStack glass = new ItemStack(
Material.GRAY_STAINED_GLASS_PANE);
ItemMeta gm = glass.getItemMeta();
if (gm != null) {
gm.setDisplayName(" ");
glass.setItemMeta(gm);
}
for (int i = 0; i < 27; i++)
g.setItem(i, glass);
// 债券余额 (slot 4)
int balance = bondManager.getBonds(target);
g.setItem(4, mkItem(Material.GOLD_INGOT,
"§6§l债券余额: §e" + balance + " §6枚",
"§7当前持有的债券数量"));
// 账户状态 (slot 10)
String status = bondManager
.getAccountStatus(target);
if ("frozen".equals(status)) {
g.setItem(10, mkItem(Material.REDSTONE,
"§c§l账户状态: 冻结中",
"§7无法进行任何资金交易"));
} else {
g.setItem(10, mkItem(Material.EMERALD,
"§a§l账户状态: 正常",
"§7账户运行正常"));
}
// 同IP玩家 (slot 11)
if (target.equals(p.getName())) {
List<String> sameIP = bondManager
.getSameIPPlayers(target);
List<String> lore = new ArrayList<>();
lore.add("§7同IP下注册的其他玩家:");
if (sameIP.isEmpty()) {
lore.add("§a 无");
} else {
for (String n : sameIP)
lore.add("§c - " + n);
}
lore.add("§7");
lore.add("§7转账限制:");
lore.add("§7 不能与同IP玩家转账");
g.setItem(11, mkItem(Material.PAPER,
"§e§l同IP玩家", lore.toArray(
new String[0])));
}
// 近7天流水 (slot 12)
g.setItem(12, mkItem(Material.BOOK,
"§e近7天流水",
"§7点击查看最近7天交易记录"));
// 近14天流水 (slot 13)
g.setItem(13, mkItem(Material.BOOK,
"§e近14天流水",
"§7点击查看最近14天交易记录",
"§8(系统最大支持14天)"));
// 转账记录 (slot 14)
g.setItem(14, mkItem(Material.PAPER,
"§e转账记录",
"§7点击查看所有转账明细"));
// 返回 (slot 22)
g.setItem(22, mkItem(Material.ARROW,
"§7← 返回"));
walletViewTarget.put(p.getName(), target);
p.openInventory(g);
}
// ==================== 流水记录GUI ====================
public void openWalletTransactions(
Player p, int days) {
String target = walletViewTarget.getOrDefault(
p.getName(), p.getName());
openWalletTxGUI(p, target, days, false);
}
public void openWalletTransfers(Player p) {
String target = walletViewTarget.getOrDefault(
p.getName(), p.getName());
openWalletTxGUI(p, target, 14, true);
}
private void openWalletTxGUI(Player p,
String target,
int days,
boolean transfersOnly) {
days = Math.min(days, 14); // 普通玩家最大14天
String title = transfersOnly
? "§e§l转账记录 - " + target
: "§e§l流水记录 - 近" + days
+ "天 - " + target;
Inventory g = Bukkit.createInventory(
null, 54, title);
List<Map<String, Object>> txs = transfersOnly
? bondManager.getTransferTransactions(
target, days)
: bondManager.getTransactions(
target, days);
int slot = 0;
for (Map<String, Object> tx : txs) {
if (slot >= 45) break;
String type = (String) tx.get("type");
int amount = ((Number) tx.get("amount"))
.intValue();
String tgt = (String) tx.get("target_player");
String reason = (String) tx.get("reason");
int bal = ((Number) tx.get("balance_after"))
.intValue();
long time = ((Number) tx.get("time"))
.longValue();
boolean isExpense = "shop_buy".equals(type)
|| "shop_packing".equals(type);
boolean pos = isExpense ? false : (amount > 0);
String sign = pos ? "+" : (isExpense ? "-" : "");
String color = pos ? "§a" : (isExpense ? "§c" : (amount < 0 ? "§c" : ""));
// 金额取绝对值显示
int displayAmount = Math.abs(amount);
// 用 if-else 替代 switch 避免编译问题
Material mat = Material.PAPER;
if (BondManager.TX_TRANSFER_OUT.equals(type)) {
mat = Material.RED_WOOL;
} else if (BondManager.TX_TRANSFER_IN
.equals(type)) {
mat = Material.LIME_WOOL;
} else if (BondManager.TX_ADMIN_GIVE
.equals(type)) {
mat = Material.GOLD_BLOCK;
} else if (BondManager.TX_ADMIN_DEDUCT
.equals(type)) {
mat = Material.REDSTONE_BLOCK;
} else if (BondManager.TX_REDEEM
.equals(type)) {
mat = Material.NAME_TAG;
} else if (BondManager.TX_SHOP_BUY
.equals(type)) {
mat = Material.CHEST;
} else if (BondManager.TX_FREEZE
.equals(type)) {
mat = Material.BARRIER;
} else if (BondManager.TX_UNFREEZE
.equals(type)) {
mat = Material.EMERALD_BLOCK;
} else if (pos) {
mat = Material.LIME_WOOL;
} else {
mat = Material.RED_WOOL;
}
String typeName = walletFormatType(type);
List<String> lore = new ArrayList<>();
lore.add("§7类型: " + typeName);
lore.add("§7金额: " + color + sign + displayAmount
+ " §6枚");
if (tgt != null && !tgt.isEmpty())
lore.add("§7对象: §e" + tgt);
if (reason != null && !reason.isEmpty())
lore.add("§7理由: §7" + reason);
lore.add("§7余额: §f" + bal + " §6枚");
lore.add("§8" + TX_SDF.format(
new Date(time)));
ItemStack item = new ItemStack(mat);
ItemMeta im = item.getItemMeta();
if (im != null) {
im.setDisplayName(color + sign + displayAmount
+ " §6枚 - " + typeName);
im.setLore(lore);
item.setItemMeta(im);
}
g.setItem(slot, item);
slot++;
}
if (txs.isEmpty()) {
ItemStack empty = new ItemStack(
Material.BARRIER);
ItemMeta em = empty.getItemMeta();
if (em != null) {
em.setDisplayName("§7暂无记录");
empty.setItemMeta(em);
}
g.setItem(22, empty);
} else if (txs.size() > 45) {
g.setItem(49, mkItem(Material.PAPER,
"§7还有 " + (txs.size() - 45)
+ " 条未显示"));
}
g.setItem(53, mkItem(Material.ARROW,
"§7← 返回钱包"));
p.openInventory(g);
}
private String walletFormatType(String type) {
if (type == null) return "未知";
switch (type) {
case BondManager.TX_ADMIN_GIVE:
return "管理员发放";
case BondManager.TX_ADMIN_DEDUCT:
return "管理员扣除";
case BondManager.TX_TRANSFER_OUT:
return "转出";
case BondManager.TX_TRANSFER_IN:
return "转入";
case BondManager.TX_REDEEM:
return "CDK兑换";
case BondManager.TX_SHOP_BUY:
return "商城购买";
case BondManager.TX_DAILY_SIGN:
return "签到奖励";
case BondManager.TX_FREEZE:
return "冻结";
case BondManager.TX_UNFREEZE:
return "解冻";
default:
return type;
}
}
// ===== Inventory Click(旧逻辑 + 新主菜单布局 + 新功能) =====
@EventHandler
public void onInvClick(InventoryClickEvent e) {
if (e.isCancelled()) return;
if (!(e.getWhoClicked() instanceof Player))
return;
Player p = (Player) e.getWhoClicked();
if (isFrozen(p)) {
e.setCancelled(true);
return;
}
String title = e.getView().getTitle();
int slot = e.getRawSlot();
// ===== 工单 =====
if (title.equals(TicketManager.T_TICKET_MAIN)
|| title.equals(
TicketManager.T_TICKET_CREATE)
|| title.equals(
TicketManager.T_TICKET_MY)
|| title.equals(
TicketManager.T_TICKET_ADMIN)
|| title.equals(
TicketManager.T_TICKET_DETAIL)
|| title.equals("§d§l服务商面板")
|| title.equals("§6§l抢单大厅")
|| title.equals(
"§b§l我处理中的工单")) {
if (e.getClickedInventory() == null)
return;
if (e.getClickedInventory()
!= e.getView().getTopInventory())
return;
e.setCancelled(true);
ticket.handleClick(p, title, slot);
return;
}
// ===== 垃圾箱 =====
// ===== 垃圾回收站 =====
if (title.equals("§6§l垃圾回收站")) {
if (e.getClickedInventory() == null)
return;
// 玩家背包普通点击 → 完全不管
if (e.getClickedInventory()
== p.getInventory()
&& !e.isShiftClick()) {
return;
}
// Shift+Click 从玩家背包存入
if (e.isShiftClick()
&& e.getClickedInventory()
== p.getInventory()
&& e.getCurrentItem() != null
&& e.getCurrentItem().getType()
!= Material.AIR
&& !isMenuSnowball(
e.getCurrentItem())) {
e.setCancelled(true);
long now = System.currentTimeMillis();
Long last = garbageBusy.get(
p.getUniqueId());
if (last != null && now - last < 200L)
return;
garbageBusy.put(p.getUniqueId(), now);
ItemStack toSave =
e.getCurrentItem().clone();
p.getInventory()
.setItem(e.getSlot(), null);
garbage.saveItem(toSave);
Bukkit.getScheduler()
.runTaskLater(this, () -> {
garbage.openRecyclePage(p, 0);
}, 1L);
return;
}
// GUI 内点击 → 全部取消,由 garbage.handleClick 处理
e.setCancelled(true);
// 功能格 49-53
if (slot >= 49 && slot <= 53) {
garbage.handleClick(p, title, slot);
return;
}
// 光标有物品 → 存入
if (slot < 54
&& e.getCursor() != null
&& e.getCursor().getType()
!= Material.AIR
&& !isMenuSnowball(
e.getCursor())) {
ItemStack toSave =
e.getCursor().clone();
p.getOpenInventory().setCursor(null);
garbage.saveItem(toSave);
Bukkit.getScheduler()
.runTaskLater(this, () -> {
garbage.openRecyclePage(p, 0);
}, 2L);
return;
}
// 点击有 ID 的物品 → 取出
if (slot < 54) {
ItemStack inSlot =
e.getInventory().getItem(slot);
if (inSlot != null
&& inSlot.hasItemMeta()
&& inSlot.getItemMeta()
.hasLore()) {
List<String> lore =
inSlot.getItemMeta().getLore();
if (lore != null && !lore.isEmpty()
&& lore.get(0).startsWith(
"§7ID: #")) {
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();
if (gm == null)
gm = Bukkit
.getItemFactory()
.getItemMeta(
give
.getType());
gm.setLore(null);
give.setItemMeta(gm);
p.getInventory()
.addItem(give);
e.getInventory()
.setItem(
slot, null);
}
} catch (Exception ex) {
p.sendMessage(
"§c取出失败");
}
}
}
}
return;
}
// ===== 二级菜单(.txt)—— 移到前面 =====
if (title.startsWith("§6§l")
&& !title.equals(GUIManager.T_MAIN)
&& !title.equals(GUIManager.T_ADMIN)
&& !title.equals(GUIManager.T_MY_INFO)
&& !title.equals(GUIManager.T_INVITE)
&& !title.equals(
GUIManager.T_TASK_CENTER)
&& !title.equals(
GUIManager.T_GIFT_STAGES)
&& !title.equals("§6§l任务面板")
&& !title.equals(
"§6§l垃圾回收站")) {
e.setCancelled(true);
if (slot == 49) {
gui.openMain(p);
return;
}
// ★ bounds检查 ★
if (slot < 45
&& slot < e.getInventory()
.getSize()
&& e.getCurrentItem() != null) {
ItemMeta subLm =
e.getCurrentItem().getItemMeta();
if (subLm != null
&& subLm.getLore() != null) {
for (String subLn
: subLm.getLore()) {
if (subLn.contains(
"§7指令: §f")) {
String cmd =
subLn.replace(
"§7指令: §f",
"").trim();
if (cmd.isEmpty()
|| cmd.equals("/")
|| cmd.equals(
"null")) {
p.sendMessage(
"§c指令为空");
return;
}
p.closeInventory();
if (cmd.startsWith("/"))
cmd = cmd.substring(1);
Bukkit.dispatchCommand(
p, cmd);
return;
}
}
}
}
return;
}
// ===== [DEBUG] 标题追踪 =====
{
String dt = title;
if (dt.contains("钱包")
|| dt.contains("余额")
|| dt.contains("流水")
|| dt.contains("转账")) {
getLogger().info(
"[DEBUG-CLICK] "
+ "player=" + p.getName()
+ " title=[" + dt + "]"
+ " slot=" + slot
+ " codePoints="
+ debugCodePoints(dt));
}
}
// ===== 二级菜单(.txt)—— 排除所有固定GUI =====
if (title.startsWith("§6§l")
&& !title.equals(GUIManager.T_MAIN)
&& !title.equals(GUIManager.T_ADMIN)
&& !title.equals(GUIManager.T_MY_INFO)
&& !title.equals(GUIManager.T_INVITE)
&& !title.equals(
GUIManager.T_TASK_CENTER)
&& !title.equals(
GUIManager.T_GIFT_STAGES)
&& !title.equals("§6§l任务面板")
&& !title.equals("§6§l垃圾回收站")
&& !title.equals("§6§l我的钱包")
&& !title.equals("§6§l余额操作")) {
e.setCancelled(true);
if (slot == 49) {
gui.openMain(p);
return;
}
if (slot < 45
&& slot < e.getInventory()
.getSize()
&& e.getCurrentItem() != null) {
ItemMeta subLm = e.getCurrentItem()
.getItemMeta();
if (subLm != null
&& subLm.getLore() != null) {
for (String subLn
: subLm.getLore()) {
if (subLn.contains(
"§7指令: §f")) {
String cmd = subLn
.replace("§7指令: §f", "")
.trim();
if (cmd.isEmpty()
|| cmd.equals("/")
|| cmd.equals("null")) {
p.sendMessage("§c指令为空");
return;
}
p.closeInventory();
if (cmd.startsWith("/")) {
cmd = cmd.substring(1);
}
Bukkit.dispatchCommand(p, cmd);
return;
}
}
}
}
return;
}
// ===== 我的钱包(唯一handler) =====
if (title.equals("§6§l我的钱包")) {
e.setCancelled(true);
if (slot == 22) {
gui.openMain(p);
} else if (slot == 12) {
openWalletTransactions(p, 7);
} else if (slot == 13) {
openWalletTransactions(p, 14);
} else if (slot == 14) {
openWalletTransfers(p);
}
return;
}
// ===== 流水/转账记录(唯一handler) =====
if (title.startsWith("§e§l流水记录")
|| title.startsWith("§e§l转账记录")) {
e.setCancelled(true);
if (slot == 53) {
openMyWallet(p);
}
return;
}
// ===== 余额操作(唯一handler) =====
if (title.equals("§d§l余额操作")) {
e.setCancelled(true);
if (slot == 49) {
gui.openMain(p);
return;
}
if (slot >= 0
&& slot < e.getInventory().getSize()) {
ItemStack item = e.getInventory()
.getItem(slot);
if (item == null
|| !item.hasItemMeta()
|| !item.getItemMeta()
.hasDisplayName()) {
return;
}
// 在 slot == 49 的 return 之后、读取 item 之前
// ItemStack item = e.getInventory().getItem(slot);
if (item == null) return;
Material mat = item.getType();
// 以下是常见的装饰性玻璃板材质,按需扩展
if (mat.name().contains("STAINED_GLASS")
|| mat.name().contains("GLASS_PANE")
|| mat == Material.LIGHT_BLUE_STAINED_GLASS_PANE
|| mat == Material.GRAY_STAINED_GLASS_PANE
// ... 其他装饰物
|| mat == Material.BLACK_STAINED_GLASS_PANE) {
e.setCancelled(true);
return; // 装饰物品,不处理
}
String targetName = item.getItemMeta()
.getDisplayName()
.replace("§e", "")
.replace("§a", "");
if (targetName.equals(p.getName())) {
p.sendMessage("§c不能操作自己");
return;
}
if (e.isLeftClick()) {
balanceTarget = targetName;
balanceGiveMode = true;
p.closeInventory();
p.sendMessage(
"§e请输入金额和理由");
p.sendMessage(
"§7例: +100 给款 / -100 扣款");
} else if (e.isRightClick()) {
if (this.getCDK() != null) {
this.getCDK().requestInput(
p, "bond",
targetName);
}
p.closeInventory();
p.sendMessage(
"§e【债券操作】对 §a"
+ targetName
+ " §e进行债券操作");
p.sendMessage(
"§e输入 +金额 给债券,"
+ " -金额 扣债券");
p.sendMessage(
"§7可附加理由: §a+100 "
+ "活动奖励 §7不填默认:"
+ "管理员手动调整");
}
}
return;
}
// ===== 菜单管理 =====
if (title.equals("§c§l菜单管理")) {
e.setCancelled(true);
if (slot == 26) {
gui.openMain(p);
} else if (slot == 22) {
gui.openEditor(p, -1);
} else if (slot >= 0 && slot < 9) {
List<MenuManager.MenuItem> items =
getMenu().getItems();
if (slot < items.size()) {
if (e.isLeftClick()) {
gui.openEditor(p, slot);
} else if (e.isRightClick()) {
MenuManager.MenuItem mi =
items.get(slot);
if ("OP".equals(mi.permType)) {
mi.permType =
"玩家";
} else {
mi.permType = "OP";
}
gui.openMenuManager(p);
p.sendMessage(
"§a" + mi.title
+ " §7权限切换为: §e"
+ mi.permType);
}
}
}
return;
}
// ===== 全局 slot 检查(放后面) =====
if (e.getClickedInventory() == null) return;
if (e.getClickedInventory()
!= e.getView().getTopInventory())
return;
if (slot < 0 || slot >= e.getInventory()
.getSize()) return;
if (e.getCurrentItem() == null) return;
// ==================== 群系传送(新) ====================
if (title.equals(GUIManager.T_BIOME)) {
e.setCancelled(true);
if (slot == 49) {
gui.openMain(p);
return;
}
if (slot < 12) {
String[] cats = {
"海洋", "平原", "森林", "沙漠",
"恶地", "雪原", "沼泽", "河流",
"地下", "蘑菇岛", "下界", "末地"
};
p.closeInventory();
gui.teleportToBiome(p, cats[slot]);
}
return;
}
// ===== 编辑器 =====
if (title.equals(GUIManager.T_EDITOR)) {
e.setCancelled(true);
if (slot >= 27) return;
List<MenuManager.MenuItem> el =
getMenu().getItems();
int ei = el.size() - 1;
if (ei < 0) return;
MenuManager.MenuItem em = el.get(ei);
switch (slot) {
case 10:
getMenu().editTitle(p, ei);
break;
case 11:
getMenu().editCommand(p, ei);
break;
case 20:
em.permType =
"玩家".equals(em.permType)
? "OP" : "玩家";
gui.openEditor(p, ei);
break;
// 编辑器 case 22(保存)
case 22:
if (getMenu() != null) {
MenuManager.MenuItem last =
getMenu().getItems().get(ei);
if (last.command == null
|| last.command.trim().isEmpty()
|| last.command.trim()
.equals("/")) {
p.sendMessage(
"§c指令不能为空,请先填写指令");
gui.openEditor(p, ei);
return;
}
getMenu().saveMenu();
p.closeInventory();
p.sendMessage("§a已保存!");
gui.openMain(p);
}
break;
case 24:
getMenu().loadMenu();
// ★ 如果最后一项是空指令的新项,删掉 ★
List<MenuManager.MenuItem> cancelItems =
getMenu().getItems();
if (!cancelItems.isEmpty()) {
MenuManager.MenuItem last =
cancelItems
.get(cancelItems.size() - 1);
if ((last.command == null
|| last.command.trim().isEmpty()
|| last.command.trim().equals("/"))
&& "新菜单项"
.equals(last.title)) {
cancelItems.remove(
cancelItems.size() - 1);
}
}
p.closeInventory();
p.sendMessage("§c已取消");
gui.openMain(p);
break;
}
}
// ==================== 主菜单(新布局) ====================
// ==================== 主菜单(新布局) ====================
if (title.equals(GUIManager.T_MAIN)) {
e.setCancelled(true);
// ★ 统一:所有自定义菜单项通过 lore 识别 ★
if (e.getCurrentItem() != null) {
ItemMeta meta = e.getCurrentItem()
.getItemMeta();
if (meta != null && meta.getLore() != null) {
for (String ln : meta.getLore()) {
if (ln.contains("§7指令: §f")) {
String cmd = ln.replace(
"§7指令: §f", "").trim();
if (cmd.isEmpty()
|| cmd.equals("/")
|| cmd.equals("null")) {
p.sendMessage("§c指令为空");
return;
}
p.closeInventory();
if (cmd.startsWith("/")) {
cmd = cmd.substring(1);
}
if (cmd.endsWith(".txt")) {
gui.openSubMenu(p,
cmd.substring(1));
} else {
Bukkit.dispatchCommand(p, cmd);
}
return;
}
}
}
}
// 固定按钮
if (slot == 10) {
gui.openMyInfo(p);
} else if (slot == 12) {
gui.openInvite(p);
} else if (slot == 14) {
p.openInventory(points.createShopGUI(p));
} else if (slot == 16) {
gui.openBiomeMenu(p);
} else if (slot == 19) {
ticket.openMain(p);
} else if (slot == 20) {
openMyWallet(p);
} else if (slot == 21) {
dailySignWithReward(p);
gui.openMain(p);
} else if (slot == 23) {
gui.openTaskCenter(p);
} else if (slot == 25) {
if (p.isOp() || isAdmin(p)) {
garbage.openRecycle(p);
}
} else if (slot == 29) {
if (isAdmin(p)) {
p.closeInventory();
chatInput.getState(p).type =
ChatInputManager.InputType.ADMIN_AUTH;
p.sendMessage("请输入管理密码:");
}
} else if (slot == 31) {
if (p.isOp()) {
gui.openMenuManager(p);
}
} else if (slot == 34) {
if (p.isOp() || isAdmin(p)) {
gui.openBalanceOps(p);
}
}
return;
}
// ==================== 新人礼包 ====================
if (title.equals(GUIManager.T_GIFT_STAGES)) {
e.setCancelled(true);
if (slot == 26) {
gui.openTaskCenter(p);
} else if (slot >= 10 && slot <= 18) {
QuestTracker qt = getQuestTracker();
if (qt != null) {
List<QuestTracker.QuestFile> quests =
qt.getQuests("新人任务");
int idx = slot - 10;
if (idx >= 0 && idx < quests.size()) {
QuestTracker.QuestFile qf =
quests.get(idx);
String pn = p.getName();
if (qt.hasClaimed(pn, qf)) {
p.sendMessage(
"§c该阶段奖励已领取");
} else if (!qt.isStageCompleted(
pn, qf)) {
p.sendMessage("§c条件未满足");
} else {
qt.claimRewards(p, qf);
qt.markClaimed(pn, qf);
gui.openGiftStages(p);
playSuccessSound(p);
}
}
} else {
int stage = slot - 9;
if (gift.canClaim(p, stage)) {
gift.claimReward(p, stage);
gui.openGiftStages(p);
} else {
p.sendMessage(config.msg(
"gift_not_ready"));
}
}
}
return;
}
// ==================== 主线/支线任务 ====================
if (title.equals("§d§l主线任务")
|| title.equals("§d§l支线任务")) {
e.setCancelled(true);
if (slot == e.getInventory().getSize() - 1) {
gui.openTaskCenter(p);
} else if (questTracker != null) {
String cat = title.contains("主线")
? "主线任务" : "支线任务";
List<QuestTracker.QuestFile> quests =
questTracker.getQuests(cat);
if (slot < quests.size()) {
QuestTracker.QuestFile qf =
quests.get(slot);
if (!questTracker.isStageCompleted(
p.getName(), qf)) {
p.sendMessage("§c条件未满足");
} else if (questTracker.hasClaimed(
p.getName(), qf)) {
p.sendMessage("§c已领取过");
} else {
questTracker.claimRewards(p, qf);
questTracker.markClaimed(
p.getName(), qf);
gui.openTaskList(p, cat);
playSuccessSound(p);
}
}
}
return;
}
// ==================== 我的信息 ====================
if (title.equals(GUIManager.T_MY_INFO)) {
e.setCancelled(true);
// ★ 只在图标槽(slot 4)调用,不干扰按钮
if (slot == 4
&& gui.handleMyInfoIconClick(p, e)) {
return;
}
if (slot == 26) gui.openMain(p);
else if (slot == 10) {
if (checkIn.isCheckedInToday(
p.getName()))
p.sendMessage(
config.msg("checkin_already"));
else {
p.sendMessage(checkIn.checkIn(p));
invite.onInviteeCheckIn(
p.getName());
playSuccessSound(p);
gui.openMyInfo(p);
}
} else if (slot == 11) {
chatInput.getState(p).type =
ChatInputManager.InputType
.CHANGE_PWD_STEP1;
p.closeInventory();
p.sendMessage("§e请输入当前密码:");
} else if (slot == 12) {
chatInput.getState(p).type =
ChatInputManager.InputType
.BACK_CHECKIN;
p.closeInventory();
p.sendMessage("§e请输入补签日期:");
} else if (slot == 13) {
String code = (String) db.getField(
p.getName(), "invite_code");
if (code == null || code.isEmpty())
code = invite.generateCode(p);
p.sendMessage("§e邀请码: " + code);
} else if (slot == 14) {
chatInput.getState(p).type =
ChatInputManager.InputType
.SET_EMAIL;
p.closeInventory();
p.sendMessage("§e请输入邮箱:");
} else if (slot == 15) {
p.openInventory(
points.createShopGUI(p));
}
return;
}
// ==================== 管理面板 ====================
if (title.equals(GUIManager.T_ADMIN)) {
e.setCancelled(true);
if (slot == 22) gui.openMain(p);
else if (slot == 10) {
chatInput.getState(p).type =
ChatInputManager.InputType
.SMTP_CONFIG;
p.closeInventory();
p.sendMessage("§e当前SMTP: "
+ config.getSmtp("smtp地址"));
p.sendMessage(
"§e输入新地址(0跳过):");
} else if (slot == 11)
gui.openUserManagement(p);
else if (slot == 12) gui.openMyInfo(p);
else if (slot == 14)
p.openInventory(
points.createShopGUI(p));
else if (slot == 16) {
if (e.getClick().isLeftClick()) {
config.afkEnabled =
!config.afkEnabled;
p.sendMessage(config.msg(
config.afkEnabled
? "afk_set_enabled"
: "afk_set_disabled"));
config.saveSettings();
gui.openAdmin(p);
} else if (e.getClick()
.isRightClick()) {
p.closeInventory();
chatInput.getState(p).type =
ChatInputManager.InputType
.ADMIN_SET_AFK_TIME;
p.sendMessage(
"§e输入挂机时长(分钟):");
}
}
return;
}
// ==================== 任务中心 ====================
if (title.equals(GUIManager.T_TASK_CENTER)) {
e.setCancelled(true);
if (slot == 22) gui.openMain(p);
else if (slot == 10)
gui.openGiftStages(p);
else if (slot == 12) {
if (questTracker != null) {
Location loc = p.getLocation();
String result =
questTracker
.checkInAtPosition(
p.getName(),
p.getWorld()
.getName(),
loc.getX(),
loc.getY(),
loc.getZ());
p.sendMessage(result);
playSuccessSound(p);
}
} else if (slot == 14)
gui.openTaskList(p, "主线任务");
else if (slot == 16)
gui.openTaskList(p, "支线任务");
else if (slot == 18)
ticket.openMain(p);
else if (slot == 20) {
dailySignWithReward(p);
gui.openMain(p);
}
return;
}
// ==================== 邀请 ====================
if (title.equals(GUIManager.T_INVITE)) {
e.setCancelled(true);
if (slot == 26) gui.openMain(p);
else if (slot == 10) {
String code =
invite.generateCode(p);
p.sendMessage("§e邀请码: " + code);
gui.openInvite(p);
} else if (slot == 16) {
chatInput.getState(p).type =
ChatInputManager.InputType
.INVITE_INPUT_CODE;
p.closeInventory();
p.sendMessage("§e请输入邀请码:");
}
return;
}
// ==================== 用户管理 ====================
if (title.equals(GUIManager.T_USER_MGMT)) {
e.setCancelled(true);
if (slot == 53) gui.openAdmin(p);
else if (slot >= 0 && slot < 45) {
ItemStack item =
e.getInventory().getItem(slot);
if (item != null && item.hasItemMeta()
&& item.getItemMeta()
.hasDisplayName())
gui.openUserDetail(p,
item.getItemMeta()
.getDisplayName()
.replace("§e", "")
.replace("§a", ""));
}
return;
}
// ==================== 用户详情 ====================
if (title.startsWith("§e§l管理: ")) {
e.setCancelled(true);
String tgt = title.substring(
"§e§l管理: ".length());
if (slot == 22) gui.openUserManagement(p);
else if (slot == 10) {
chatInput.getState(p).type =
ChatInputManager.InputType
.ADMIN_SET_POINTS;
chatInput.getState(p)
.targetPlayer = tgt;
p.closeInventory();
p.sendMessage(
"§e输入" + tgt + "的新积分:");
} else if (slot == 12) {
chatInput.getState(p).type =
ChatInputManager.InputType
.ADMIN_SET_PWD;
chatInput.getState(p)
.targetPlayer = tgt;
p.closeInventory();
p.sendMessage(
"§e输入" + tgt + "的临时密码:");
} 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;
chatInput.getState(p)
.targetPlayer = tgt;
p.closeInventory();
p.sendMessage(config.msg(
"admin_delete_confirm"));
}
return;
}
// ==================== 积分商城 ====================
if (title.startsWith("§d§l积分商城")) {
e.setCancelled(true);
if (slot == 49) gui.openMain(p);
else if (slot == 50 && isAdmin(p))
gui.openShopAdmin(p);
else points.handleClick(p, slot);
return;
}
// ==================== 商城管理 ====================
if (title.equals("§c§l商城管理")) {
e.setCancelled(true);
if (slot == 49)
p.openInventory(
points.createShopGUI(p));
return;
}
// ==================== CY背包商城 ====================
if (title.startsWith("§d§lCY背包商城")) {
e.setCancelled(true);
int size = e.getInventory().getSize();
if (slot == size - 1)
p.openInventory(
points.createShopGUI(p));
else points.handleCYClick(p, slot);
return;
}
// ==================== 任务面板 ====================
if (title.equals("§6§l任务面板")) {
e.setCancelled(true);
if (slot == 22) gui.openMain(p);
else if (slot == 11) {
Location loc = p.getLocation();
String result =
questTracker.checkInAtPosition(
p.getName(),
p.getWorld().getName(),
loc.getX(), loc.getY(),
loc.getZ());
p.sendMessage(result);
playSuccessSound(p);
openTaskPanel(p);
} else if (slot == 15) {
gui.openGiftStages(p);
}
return;
}
// ==================== 账号请求面板 ====================
if (title.equals("§e§l账号请求")) {
e.setCancelled(true);
if (slot == 22) gui.openMain(p);
else if (slot == 10) {
p.closeInventory();
p.sendMessage("§c§l[确认] §f请输入:");
p.sendMessage("§e/sdf1_login del "
+ p.getName());
} else if (slot == 13) {
ticket.openCreate(p);
} else if (slot == 16) {
p.closeInventory();
loginMgr.handleReset(p);
}
}
}
/*
* 签到 + 盲盒经济奖励
*/
public void dailySignWithReward(Player p) {
String name = p.getName();
if (checkIn.isCheckedInToday(name)) {
p.sendMessage(
config.msg("checkin_already"));
return;
}
// ===== 1. 执行签到(内部已加积分) =====
p.sendMessage(checkIn.checkIn(p));
invite.onInviteeCheckIn(name);
// ===== 2. 获取连续天数 =====
int streak = checkIn.getStreak(name);
double multiplier = 1.0;
if (streak >= 21) {
multiplier = 1.3;
} else if (streak >= 14) {
multiplier = 1.2;
} else if (streak >= 7) {
multiplier = 1.1;
}
// ===== 3. 经济奖励(不再重复加积分) =====
if (economy == null) {
playSuccessSound(p);
return;
}
// 固定金额模式
if ("fixed".equals(
config.checkinRewardType)
&& config.checkinRewardFixed > 0) {
final double reward =
config.checkinRewardFixed;
final double m = multiplier;
p.sendMessage("§e§l[签到] §f"
+ "正在生成今日奖励...");
Bukkit.getScheduler()
.runTaskLater(this, () -> {
double rewardFinal =
Math.round(
reward * m
* 100.0)
/ 100.0;
double before =
economy
.getBalance(p);
economy.depositPlayer(
p, rewardFinal);
double after =
economy
.getBalance(p);
p.sendMessage(
"§6§l[签到] §f"
+ "获得奖励: §a§l$"
+ String.format(
"%.2f",
rewardFinal));
p.sendMessage("§7余额: §e"
+ String.format(
"%.2f", before)
+ " §7→ §a"
+ String.format(
"%.2f", after));
playSuccessSound(p);
}, 30L);
return;
}
// 盲盒范围模式
if ("range".equals(
config.checkinRewardType)
&& config.checkinRewardMin > 0
&& config.checkinRewardMax
> config.checkinRewardMin) {
final double min =
config.checkinRewardMin;
final double max =
config.checkinRewardMax;
final double m = multiplier;
getLogger().info(
"[签到] 盲盒: min="
+ min + " max=" + max
+ " multi=" + m);
p.sendMessage("§e§l[签到盲盒] "
+ "§f正在生成今日惊喜...");
Bukkit.getScheduler()
.runTaskLater(this, () -> {
double rand =
Math.random();
double reward = min
+ rand
* (max - min);
reward = Math.round(
reward * m * 100.0)
/ 100.0;
getLogger().info(
"[签到] 盲盒结果: rand="
+ String.format(
"%.4f", rand)
+ " reward="
+ reward);
double before =
economy
.getBalance(p);
economy.depositPlayer(
p, reward);
double after =
economy
.getBalance(p);
p.sendMessage(
"§6§l====== 签到盲盒 ======");
p.sendMessage(
"§7开出: §a§l$"
+ String.format(
"%.2f",
reward));
p.sendMessage("§7余额: §e"
+ String.format(
"%.2f", before)
+ " §7→ §a"
+ String.format(
"%.2f", after));
p.sendMessage(
"§6§l========================");
playSuccessSound(p);
}, 40L);
return;
}
// 无经济奖励
playSuccessSound(p);
}
public BondManager getBondManager() {
return bondManager;
}
@EventHandler
public void onVillagerTrade(
org.bukkit.event.inventory.TradeSelectEvent e) {
// TradeSelectEvent 在玩家点击交易时触发
if (!(e.getWhoClicked() instanceof Player)) return;
Player p = (Player) e.getWhoClicked();
if (isFrozen(p) || questTracker == null) return;
questTracker.onVillagerTrade(p.getName());
}
@EventHandler
public void onInvDrag(InventoryDragEvent e) {
if (!(e.getWhoClicked() instanceof Player))
return;
Player p = (Player) e.getWhoClicked();
if (isFrozen(p)) {
e.setCancelled(true);
return;
}
String t = e.getView().getTitle();
// 禁止T_MY_INFO拖拽,只允许点击操作
// 我的信息:允许拖入图标槽(31)
if (GUIManager.T_MY_INFO.equals(
e.getView().getTitle())) {
boolean toIconSlot = false;
for (int raw : e.getRawSlots()) {
if (raw == 31) {
toIconSlot = true;
break;
}
}
if (toIconSlot) {
ItemStack dragged =
e.getOldCursor().clone();
String b64 = gui.serializeItem(dragged);
if (b64 != null) {
db.saveMenuIcon(p.getName(), b64,
dragged.getType().name());
}
p.sendMessage("§a已保存菜单图标: "
+ dragged.getType().name());
// 刷新GUI显示
Bukkit.getScheduler()
.runTaskLater(this, () ->
gui.openMyInfo(p), 2L);
}
e.setCancelled(true);
return;
}
// 回收站拖拽
if (t.equals("§6§l垃圾回收站")) {
boolean toGUI = false;
for (int rawSlot : e.getRawSlots()) {
if (rawSlot < 54) {
toGUI = true;
break;
}
}
if (toGUI) {
ItemStack cur = e.getCursor();
if (cur != null
&& cur.getType() != Material.AIR
&& !isMenuSnowball(cur)) {
e.setCancelled(true);
p.getOpenInventory().setCursor(null);
garbage.saveItem(cur.clone());
Bukkit.getScheduler()
.runTaskLater(this, () -> {
garbage.openRecyclePage(
p,
garbage
.getRecyclePage(
p
.getName()));
}, 2L);
return;
}
}
return;
}
//服务商工单系统
if (t.equals(TicketManager.T_TICKET_MAIN)
|| t.equals(
TicketManager.T_TICKET_CREATE)
|| t.equals(
TicketManager.T_TICKET_MY)
|| t.equals(
TicketManager.T_TICKET_ADMIN)
|| t.equals(
TicketManager.T_TICKET_DETAIL)
|| t.equals("§d§l服务商面板")
|| t.equals("§6§l抢单大厅")
|| t.equals(
"§b§l我处理中的工单")
|| t.equals("§6§l我的历史工单")
|| t.equals("§c§l商城管理")) {
e.setCancelled(true);
}
}
/** 验证玩家是否在主库中真实存在 */
public boolean isRealPlayer(String name) {
if (name == null || name.isEmpty()) return false;
try {
Map<String, Object> user =
getDb().getUser(name);
return user != null && !user.isEmpty()
&& user.containsKey("player_name");
} catch (Exception e) {
return false;
}
}
// ===== Commands =====
@Override
public boolean onCommand(CommandSender sender,
Command cmd, String label,
String[] args) {
String cmdName = cmd.getName().toLowerCase();
// 区域防护(必须在最前面)
if (cmd.getName().equalsIgnoreCase("protect")
|| cmd.getName().equals("区域保护")) {
if (areaProtection == null) {
sender.sendMessage("§c区域防护未初始化");
return true;
}
if (args.length == 0) {
sender.sendMessage("§a§l===== 区域防护 =====");
sender.sendMessage("§e/protect 工具 §7获取选区工具");
sender.sendMessage("§e/protect 创建 <区域名> §7创建区域");
sender.sendMessage("§e/protect 列表 §7查看所有区域");
sender.sendMessage("§e/protect 重载 §7重载配置");
sender.sendMessage("§e/protect add <玩家> §7全局加白");
sender.sendMessage("§e/protect add <区域> <玩家> §7区域加白");
sender.sendMessage("§e/protect remove <玩家> §7全局删白");
sender.sendMessage("§e/protect remove <区域> <玩家> §7区域删白");
sender.sendMessage("§e/protect additem <物品ID> §7全局加黑");
sender.sendMessage("§e/protect additem <区域> <物品ID> §7区域加黑");
sender.sendMessage("§e/protect removeitem <物品ID> §7全局删黑");
sender.sendMessage("§e/protect removeitem <区域> <物品ID> §7区域删黑");
sender.sendMessage("§e/protect list §7全局白名单");
sender.sendMessage("§e/protect list <区域> §7区域白名单");
sender.sendMessage("§e/protect listitem §7全局物品黑名单");
sender.sendMessage("§e/protect listitem <区域> §7区域物品黑名单");
sender.sendMessage("§e/protect expand [格数] §7扩建选区");
sender.sendMessage("§e/protect contraction [格数] §7收缩选区");
sender.sendMessage("§e/protect addname <区域> <名字> §7添加和平白名单生物");
sender.sendMessage("§e/protect removename <区域> <名字> §7移除和平白名单生物");
sender.sendMessage("§e/protect addwhite <玩家> §7添加模式排除玩家");
sender.sendMessage("§e/protect removewhite <玩家> §7移除模式排除玩家");
sender.sendMessage("§e/protect listname <区域> §7查看和平白名单");
sender.sendMessage("§e/protect listwhite §7查看模式排除名单");
return true;
}
// 其他子命令交给 areaProtection
try {
return areaProtection.handleCommand(sender, args);
} catch (Exception e) {
sender.sendMessage("§c执行出错: " + e.getMessage());
e.printStackTrace();
return true;
}
}
if (cmd.getName().equalsIgnoreCase("shop")
|| cmd.getName().equals("商店")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可使用");
return true;
}
if (shopManager != null) {
shopManager.openShopMain((Player) sender);
} else {
sender.sendMessage("§c商店系统未加载");
}
return true;
}
String sub = (args.length > 0) ? args[0].toLowerCase() : "";
// ===== 在 onCommand 方法体内,最开头加: =====
if (cmd.getName().equalsIgnoreCase("sdf1_login")
&& args.length > 0
&& args[0].equalsIgnoreCase("shop")) {
if (shopManager == null) {
sender.sendMessage("§c商店未加载");
return true;
}
return shopManager.handleCommand(sender, args);
}
// set
if (sub.equals("set")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 3) {
sender.sendMessage("§e用法: /sdf1_login set <玩家> <临时密码>");
return true;
}
String tgt = args[1];
String tempPwd = args[2];
if (!db.userExists(tgt)) {
sender.sendMessage("§c玩家不存在");
return true;
}
String salt = (String) db.getField(tgt, "password_salt");
String hash = PasswordUtils.hash(tempPwd, salt);
db.setField(tgt, "temp_password", hash);
db.setField(tgt, "temp_pw_expire",
System.currentTimeMillis() + 300000L);
db.setField(tgt, "temp_pw_used", 0);
// ★ 立即发邮件(踢人之前)
String emailAddr = (String) db.getField(tgt, "email");
if (emailAddr != null && !emailAddr.isEmpty()) {
// 获取最近登录信息
Object lastLoginObj = db.getField(tgt, "last_login_time");
long lastLogin = lastLoginObj != null
? ((Number) lastLoginObj).longValue() : 0;
String lastIP = (String) db.getField(tgt, "last_ip");
if (lastIP == null || lastIP.isEmpty())
lastIP = "未知";
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String loginTimeStr = lastLogin > 0
? sdf.format(new java.util.Date(lastLogin))
: "未知时间";
final String to = emailAddr;
final String fName = tgt;
final String pwd = tempPwd;
final String ipInfo = lastIP;
final String timeInfo = loginTimeStr;
final Main self = this;
Bukkit.getScheduler().runTaskAsynchronously(this,
() -> {
// ★ 发送临时密码邮件(带登录信息)
boolean sent = email.sendTempPassword(
to, fName, pwd);
// ★ 额外发送安全通知邮件
if (sent) {
String body = "玩家 " + fName
+ ":\n\n"
+ "管理员已为您的账号设置了临时密码。\n\n"
+ "临时密码: " + pwd + "\n"
+ "过期时间: 5分钟内\n\n"
+ "最近登录记录:\n"
+ " 时间: " + timeInfo + "\n"
+ " IP地址: " + ipInfo + "\n\n"
+ "如果您本人未操作,请忽略此邮件。\n"
+ "如果您怀疑账号被盗,请立即修改密码。";
email.sendBody(to,
"【安全通知】账号临时密码已设置", body);
}
Bukkit.getScheduler().runTask(self, () -> {
if (sent) {
sender.sendMessage("§a临时密码已发送至 "
+ tgt + " 的邮箱");
}
});
});
} else {
sender.sendMessage("§7该玩家未绑定邮箱,跳过邮件通知");
}
// 踢人
Player tp = Bukkit.getPlayer(tgt);
if (tp != null && tp.isOnline())
tp.kickPlayer("§c管理员已设置了临时密码,请用新密码登录");
sender.sendMessage("§a已为 " + tgt + " 设置临时密码");
return true;
}
if (label.equalsIgnoreCase("pvp")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p = (Player) sender;
return pvpManager.onCommand(p, args);
}
if (cmdName.equals("cypay")) {
if (args.length > 0) {
String target = args[0];
if (!getDb().userExists(target)) {
sender.sendMessage("§c玩家 " + target
+ " 不存在于服务器");
return true;
}
}
return cypayCommand.onCommand(
sender, cmd, label, args);
}
// ===== /printer [玩家名] =====
if (label.equalsIgnoreCase("printer")) {
if (bondPrinter == null) {
sender.sendMessage("§cPrinter未初始化");
return true;
}
if (!sender.isOp()
&& !isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length == 0) {
// 打印全服
sender.sendMessage(
"§e正在导出全服流水...");
bondPrinter.printAll(sender);
return true;
}
if (args.length == 1) {
// 打印指定玩家
String target = args[0];
sender.sendMessage("§e正在导出 "
+ target + " 的流水...");
bondPrinter.printPlayer(
target, sender);
return true;
}
sender.sendMessage(
"§7用法: /printer [玩家名]");
return true;
}
// ===== /chat 独立命令 =====
if (cmdName.equals("chat")
|| cmdName.equals("聊天")) {
if (sender instanceof Player) {
Player chatP = (Player) sender;
if (!chatP.getScoreboardTags()
.contains(config.adminTag)) {
sender.sendMessage("§c权限不足");
return true;
}
}
if (args.length < 1) {
sender.sendMessage(
"§e/chat <reload|开|关|add|remove|addplayer|takeplayer|unmute|reset>");
return true;
}
String cs = args[0].toLowerCase();
if (cs.equals("reload")) {
chatFilter.loadConfig();
sender.sendMessage("§a聊天配置已重载");
return true;
}
if (cs.equals("开") || cs.equals("on")) {
chatFilter.setEnabled(true);
chatFilter.saveConfig();
sender.sendMessage("§aURL过滤已开启");
return true;
}
if (cs.equals("关") || cs.equals("off")) {
chatFilter.setEnabled(false);
chatFilter.saveConfig();
sender.sendMessage("§cURL过滤已关闭");
return true;
}
if (cs.equals("add")) {
if (args.length < 2) {
sender.sendMessage("§e/chat add <URL>");
return true;
}
chatFilter.addUrl(args[1]);
sender.sendMessage("§a已添加白名单: " + args[1]);
return true;
}
if (cs.equals("remove")) {
if (args.length < 2) {
sender.sendMessage("§e/chat remove <URL>");
return true;
}
if (chatFilter.removeUrl(args[1]))
sender.sendMessage("§c已移除: " + args[1]);
else
sender.sendMessage("§c未找到: " + args[1]);
return true;
}
if (cs.equals("addplayer")) {
if (args.length < 2) {
sender.sendMessage("§e/chat addplayer <玩家>");
return true;
}
chatFilter.addWhitelistPlayer(args[1]);
sender.sendMessage("§a白名单玩家: " + args[1]);
return true;
}
if (cs.equals("takeplayer")) {
if (args.length < 2) {
sender.sendMessage("§e/chat takeplayer <玩家>");
return true;
}
if (chatFilter.removeWhitelistPlayer(args[1]))
sender.sendMessage("§c已移除白名单: " + args[1]);
else
sender.sendMessage("§c未找到: " + args[1]);
return true;
}
if (cs.equals("unmute")) {
if (args.length < 2) {
sender.sendMessage("§e/chat unmute <玩家>");
return true;
}
chatFilter.unmutePlayer(args[1]);
sender.sendMessage("§a已解禁: " + args[1]);
return true;
}
if (cs.equals("reset")) {
if (args.length < 2) {
sender.sendMessage("§e/chat reset <玩家>");
return true;
}
chatFilter.resetPlayer(args[1]);
sender.sendMessage("§a已重置: " + args[1]);
return true;
}
sender.sendMessage("§c未知子命令");
return true;
}
if (label.equalsIgnoreCase("sdf1_login")
&& args.length >= 2
&& args[0].equalsIgnoreCase("give")) {
if (!sender.isOp()) {
sender.sendMessage("§c仅OP可用");
return true;
}
Player target =
Bukkit.getPlayer(args[1]);
if (target == null || !target.isOnline()) {
sender.sendMessage(
"§c玩家不在线: " + args[1]);
return true;
}
giveMenuSnowball(target);
sender.sendMessage("§a已发放菜单雪球给 "
+ target.getName());
target.sendMessage(
"§a管理员已发放菜单雪球给你");
return true;
}
if (cmdName.equals("recycle")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player pr = (Player) sender;
if (!pr.isOp()
&& !pr.getScoreboardTags()
.contains(config.adminTag)) {
pr.sendMessage("§c权限不足");
return true;
}
garbage.openRecycle(pr);
return true;
}
// /垃圾清理
if (cmdName.equals("垃圾清理")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
int count = garbage.collectItems();
sender.sendMessage("§a已清理 "
+ 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("注册")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p = (Player) sender;
chatInput.reset(p);
if (db.userExists(p.getName())) {
p.sendMessage(
"§c您已注册过,请使用 /login");
return true;
}
try {
loginMgr.handleRegister(p, args);
// [ADDED] 欢迎仪式
if (welcome != null
&& loggedIn.contains(p.getName())) {
welcome.onRegister(p);
}
} catch (Exception e) {
getLogger().severe(
"[Sdf1_login] reg命令异常: "
+ e.getMessage());
e.printStackTrace();
p.sendMessage("§c注册出错,请联系管理员");
}
return true;
}
if (cmdName.equals("login")
|| cmdName.equals("l")
|| cmdName.equals("登录")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p = (Player) sender;
chatInput.reset(p);
if (loggedIn.contains(p.getName())) {
p.sendMessage("§c您已登录");
return true;
}
try {
loginMgr.handleLogin(p, args);
// [FIX] 手动登录后关闭飞行(仅一次)
if (loggedIn.contains(p.getName())
&& p.getAllowFlight()) {
p.setAllowFlight(false);
p.setFlying(false);
}
// [ADDED] 欢迎仪式
if (welcome != null
&& loggedIn.contains(p.getName())) {
welcome.onLogin(p);
}
} catch (Exception e) {
getLogger().severe(
"[Sdf1_login] login命令异常: "
+ e.getMessage());
e.printStackTrace();
p.sendMessage("§c登录出错,请联系管理员");
}
return true;
}
// /找回密码
if (cmdName.equals("找回密码")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
handleReset((Player) sender);
return true;
}
// /签到
if (cmdName.equals("签到")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
if (dailySign((Player) sender))
playSuccessSound((Player) sender);
else
((Player) sender).sendMessage(
config.msg("checkin_already"));
return true;
}
// 绑定邮箱
if (cmdName.equals("绑定邮箱")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
if (args.length >= 1) {
String emailAddr = args[0];
if (!emailAddr.contains("@")
|| !emailAddr.contains(".")) {
p2.sendMessage("§c邮箱格式不正确");
return true;
}
String oldEmail = (String) db.getField(
p2.getName(), "email");
if (oldEmail != null
&& !oldEmail.isEmpty()) {
chatInput.getState(p2).type =
ChatInputManager.InputType
.SET_EMAIL_VERIFY;
chatInput.getState(p2)
.targetPlayer = emailAddr;
chatInput.getState(p2)
.ticketTitle = "need_pwd";
p2.sendMessage("§e您已绑定邮箱: "
+ oldEmail);
p2.sendMessage(
"§e请输入登录密码以确认修改:");
} else {
String code = String.valueOf(
(int) (Math.random()
* 900000 + 100000));
chatInput.getState(p2).type =
ChatInputManager.InputType
.SET_EMAIL_VERIFY;
chatInput.getState(p2)
.targetPlayer = emailAddr;
chatInput.getState(p2)
.ticketTitle = code;
final String to = emailAddr;
final String c = code;
final Player fp = p2;
Bukkit.getScheduler()
.runTaskAsynchronously(this,
() -> {
boolean sent = email
.sendVerifyCode(to, c);
Bukkit.getScheduler().runTask(
this, () -> {
if (sent) {
fp.sendMessage(
"§a验证码已发送到 "
+ to);
fp.sendMessage(
"§e输入6位验证码(0取消):");
} else {
fp.sendMessage(
"§c发送失败");
}
});
});
}
} else {
chatInput.getState(p2).type =
ChatInputManager.InputType
.SET_EMAIL;
p2.sendMessage("§e请输入邮箱:");
}
return true;
}
// /oa
if (cmdName.equals("oa")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
openAccountRequestPanel(
(Player) sender);
return true;
}
// /玩家信息
if (cmdName.equals("玩家信息")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
gui.openMain((Player) sender);
return true;
}
// ===== /sdf1_login =====
if (!cmdName.equals("sdf1_login"))
return false;
if (args.length == 0) {
if (!(sender instanceof Player)) {
sender.sendMessage(
"§e用法: /sdf1_login <子命令>");
return true;
}
gui.openMain((Player) sender);
return true;
}
// ===== del / confirm / cancel(最先处理) =====
if (sub.equals("del")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 2) {
sender.sendMessage(
"§e用法: /sdf1_login del <玩家>");
return true;
}
String target = args[1];
if (!db.userExists(target)) {
sender.sendMessage(
"§c玩家 " + target + " 不存在");
return true;
}
if (pendingDeleteTask != null) {
pendingDeleteTask.cancel();
String old = pendingDeleteName;
pendingDeleteTask = null;
pendingDeleteName = null;
getLogger().info(
"[Sdf1_login] 取消旧待删除: " + old);
}
pendingDeleteName = target;
pendingDeleteTask = new BukkitRunnable() {
@Override
public void run() {
String name = pendingDeleteName;
pendingDeleteName = null;
pendingDeleteTask = null;
if (name == null) return;
db.deleteUser(name);
getLogger().info(
"[Sdf1_login] 已删除: " + name);
Player tp = Bukkit.getPlayer(name);
if (tp != null && tp.isOnline())
tp.kickPlayer(
"§c账号已被管理员删除");
}
};
pendingDeleteTask.runTaskLater(this, 1200L);
sender.sendMessage("§e§l[删除确认] 目标: §c"
+ target);
sender.sendMessage("§760秒后执行,"
+ "§a/sdf1_login confirm §7确认,"
+ "§c/sdf1_login cancel §7取消");
getLogger().warning("[Sdf1_login] 待删除: "
+ target + " by " + sender.getName());
return true;
}
if (sub.equals("confirm")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (pendingDeleteName == null
|| pendingDeleteTask == null) {
sender.sendMessage(
"§c当前没有待删除的账号");
return true;
}
pendingDeleteTask.cancel();
String name = pendingDeleteName;
pendingDeleteName = null;
pendingDeleteTask = null;
db.deleteUser(name);
sender.sendMessage("§a已立即删除: " + name);
Player tp = Bukkit.getPlayer(name);
if (tp != null && tp.isOnline())
tp.kickPlayer("§c账号已被管理员删除");
return true;
}
if (sub.equals("cancel")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (pendingDeleteName == null
|| pendingDeleteTask == null) {
sender.sendMessage(
"§c当前没有待删除的账号");
return true;
}
pendingDeleteTask.cancel();
String name = pendingDeleteName;
pendingDeleteName = null;
pendingDeleteTask = null;
sender.sendMessage("§a已取消删除: " + name);
return true;
}
// ===== stop =====
if (sub.equals("stop")) {
if (!(sender instanceof Player)) {
if (args.length >= 2) {
db.clearTempPassword(args[1]);
sender.sendMessage("§a已报废 "
+ args[1] + " 的临时密码");
} else {
db.clearAllTempPasswords();
sender.sendMessage("§a已报废所有临时密码");
}
return true;
}
Player p2 = (Player) sender;
if (args.length >= 2) {
String tgt = args[1];
String myIP = getPlayerIP(p2);
String targetIP =
db.getPlayerIPByName(tgt);
if (targetIP == null) {
Player tp = Bukkit.getPlayer(tgt);
if (tp != null && tp.isOnline())
targetIP = getPlayerIP(tp);
}
if (targetIP == null
|| !targetIP.equals(myIP)) {
p2.sendMessage("§c只能操作同IP下的玩家");
return true;
}
db.clearTempPassword(tgt);
p2.sendMessage("§a已报废 " + tgt + " 的临时密码");
} else {
db.clearTempPassword(p2.getName());
p2.sendMessage("§a已报废您的临时密码");
}
return true;
}
// ===== reload =====
if (sub.equals("reload")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
config.loadMessages();
config.loadSmtp();
config.loadSettings();
chatFilter.loadConfig();
if (radio != null) radio.reload();
if (getMenu() != null) {
getMenu().loadMenu();
}
if (shopManager != null) {
shopManager.loadCategories();
}
if (shopManager != null) shopManager.loadCategories();
if (areaProtection != null) {
areaProtection.clearAllPlayerAreas();
}
if (questTracker != null) {
questTracker.reload();
}
sender.sendMessage("§a§l配置已重载!");
return true;
}
// ===== back =====
if (sub.equals("back")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
// ... 保持原有back代码不变 ...
if (args.length < 2) {
sender.sendMessage("§e用法: /sdf1_login back <玩家> [#编号]");
return true;
}
// 原有back逻辑
String backTarget = args[1];
if (args.length >= 3
&& args[2].startsWith("#")) {
int backupId;
try {
backupId = Integer.parseInt(
args[2].replace("#", ""));
} catch (NumberFormatException ex) {
sender.sendMessage("§c无效编号");
return true;
}
String[] backup =
db.getInventoryBackupById(backupId);
if (backup == null) {
sender.sendMessage("§c备份 #" + backupId + " 不存在");
return true;
}
Player tp = Bukkit.getPlayer(backTarget);
if (tp != null && tp.isOnline()) {
try {
tp.getInventory().setContents(
InventorySerializer.fromBase64(backup[0]));
if (backup[1] != null && !backup[1].isEmpty())
tp.getInventory().setArmorContents(
InventorySerializer.fromBase64(backup[1]));
if (backup[2] != null && !backup[2].isEmpty())
tp.getInventory().setExtraContents(
InventorySerializer.fromBase64(backup[2]));
int lv = Integer.parseInt(backup[3]);
if (lv > 0) tp.setLevel(lv);
tp.sendMessage("§a管理员已恢复您的背包");
sender.sendMessage("§a已恢复 " + backTarget + " (备份#" + backupId + ")");
} catch (Exception e) {
sender.sendMessage("§c恢复失败: " + e.getMessage());
}
} else {
db.saveInventoryBackup(backTarget,
backup[0], backup[1], backup[2],
Integer.parseInt(backup[3]),
Double.parseDouble(backup[4]));
sender.sendMessage("§a" + backTarget + " 离线,备份已写入");
}
} else {
List<Map<String, Object>> backups =
db.getInventoryBackups(backTarget, 5);
if (backups.isEmpty()) {
sender.sendMessage("§c" + backTarget + " 没有背包备份");
return true;
}
sender.sendMessage("§e§l=== " + backTarget + " 背包备份 ===");
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat("MM-dd HH:mm:ss");
for (Map<String, Object> b : backups) {
int bid = ((Number) b.get("id")).intValue();
Object stObj = b.get("save_time");
long st = stObj != null
? ((Number) stObj).longValue() : 0;
String time = st > 0
? sdf.format(new java.util.Date(st))
: "未知时间";
String itemNames = getBackupItemPreview(bid);
sender.sendMessage("§7#" + bid + " §f" + time + " §7: " + itemNames);
}
sender.sendMessage("§7使用 §e/sdf1_login back " + backTarget + " #编号 §7恢复");
}
return true;
}
// ===== pw / password =====
if (sub.equals("pw") || sub.equals("password")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
if (args.length >= 3) {
String oldPwd = args[1];
String newPwd = args[2];
if (!PasswordUtils.validate(newPwd)) {
p2.sendMessage(config.msg("password_format_error"));
return true;
}
String salt = (String) db.getField(
p2.getName(), "password_salt");
String hash = PasswordUtils.hash(oldPwd, salt);
boolean mainOk = db.checkPassword(
p2.getName(), hash);
boolean tempOk = db.checkPasswordOrTemp(
p2.getName(), hash);
if (!mainOk && !tempOk) {
p2.sendMessage(config.msg("password_wrong"));
return true;
}
String oldHash = (String) db.getField(
p2.getName(), "password_hash");
String newSalt = PasswordUtils.generateSalt();
String newHash = PasswordUtils.hash(newPwd, newSalt);
if (newHash.equals(oldHash)) {
p2.sendMessage(config.msg("password_same"));
return true;
}
recordPasswordChange(p2.getName(), oldHash, salt);
db.setField(p2.getName(), "password_hash", newHash);
db.setField(p2.getName(), "password_salt", newSalt);
if (!mainOk && tempOk)
db.clearTempPassword(p2.getName());
// pw 命令改密码成功后
needsPasswordChange.remove(p2.getName());
// 清除临时密码
if (!mainOk && tempOk)
db.clearTempPassword(p2.getName());
return true;
}
if (args.length == 2) {
String oldPwd = args[1];
String salt = (String) db.getField(
p2.getName(), "password_salt");
String hash = PasswordUtils.hash(oldPwd, salt);
boolean mainOk = db.checkPassword(
p2.getName(), hash);
boolean tempOk = db.checkPasswordOrTemp(
p2.getName(), hash);
if (!mainOk && !tempOk) {
p2.sendMessage(config.msg("password_wrong"));
return true;
}
chatInput.getState(p2).type =
ChatInputManager.InputType.CHANGE_PWD_STEP2;
chatInput.getState(p2).ticketTitle =
mainOk ? "main" : "temp";
p2.sendMessage("§e请输入新密码:");
return true;
}
chatInput.getState(p2).type =
ChatInputManager.InputType.CHANGE_PWD_STEP1;
p2.sendMessage("§e请输入当前密码:");
return true;
}
// ===== email =====
if (sub.equals("email")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
if (args.length >= 2) {
String emailAddr = args[1];
if (!emailAddr.contains("@")
|| !emailAddr.contains(".")) {
p2.sendMessage("§c邮箱格式不正确");
return true;
}
String oldEmail = (String) db.getField(
p2.getName(), "email");
if (oldEmail != null && !oldEmail.isEmpty()) {
chatInput.getState(p2).type =
ChatInputManager.InputType.SET_EMAIL_VERIFY;
chatInput.getState(p2).targetPlayer = emailAddr;
chatInput.getState(p2).ticketTitle = "need_pwd";
p2.sendMessage("§e您已绑定邮箱: " + oldEmail);
p2.sendMessage("§e请输入登录密码以确认修改:");
} else {
String code = String.valueOf(
(int) (Math.random() * 900000 + 100000));
chatInput.getState(p2).type =
ChatInputManager.InputType.SET_EMAIL_VERIFY;
chatInput.getState(p2).targetPlayer = emailAddr;
chatInput.getState(p2).ticketTitle = code;
final String to = emailAddr;
final String c = code;
final Player fp = p2;
Bukkit.getScheduler()
.runTaskAsynchronously(this,
() -> {
boolean sent =
email.sendVerifyCode(to, c);
Bukkit.getScheduler()
.runTask(this, () -> {
if (sent) {
fp.sendMessage("§a验证码已发送到 " + to);
fp.sendMessage("§e输入6位验证码(0取消):");
} else {
fp.sendMessage("§c发送失败");
}
});
});
}
} else {
chatInput.getState(p2).type =
ChatInputManager.InputType.SET_EMAIL;
p2.sendMessage("§e请输入邮箱:");
}
return true;
}
// ===== sign =====
if (sub.equals("sign")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
dailySignWithReward((Player) sender);
return true;
}
// ===== reset =====
if (sub.equals("reset")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
loginMgr.handleReset((Player) sender);
return true;
}
// ===== undo(密码撤销) =====
if (sub.equals("undo")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
PwdRollback rb = pwdRollback.get(p2.getName());
if (rb == null) {
p2.sendMessage("§c无可撤销记录");
return true;
}
if (System.currentTimeMillis() - rb.time > 600000L) {
pwdRollback.remove(p2.getName());
p2.sendMessage("§c撤销已过期");
return true;
}
db.setField(p2.getName(), "password_hash", rb.hash);
db.setField(p2.getName(), "password_salt", rb.salt);
pwdRollback.remove(p2.getName());
p2.sendMessage("§a密码已撤销!");
return true;
}
// radio子命令(控制台+玩家)
// sdf1_login radio → 停止全服广播
// sdf1_login radio reload → 重载
// sdf1_login radio <歌名> → 全服播放
// sdf1_login radio <歌名> <玩家名> → 指定玩家播放
// sdf1_login radio <歌名> login → 推送给登录中玩家
if (sub.equals("radio")) {
if (args.length < 2) {
// 无参数 = 停止
radio.stopMainRadio();
sender.sendMessage("§c全服广播已停止");
return true;
}
String arg1 = args[1];
// reload
if (arg1.equalsIgnoreCase("reload")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
radio.reload();
sender.sendMessage("§aRadio已重载");
return true;
}
// radio <歌名> → 全服播放
// radio <歌名> <玩家名> → 指定玩家
// radio <歌名> login → 登录阶段播放
String songName = arg1;
if (args.length >= 3) {
String target = args[2];
if (target.equalsIgnoreCase("login")) {
if (radio.hasSong(songName)) {
radio.playToLoginPlayers(songName);
sender.sendMessage(
"§a已推送 §e" + songName
+ " §a给登录中玩家");
} else {
sender.sendMessage(
"§c未找到歌曲: " + songName);
return true;
}
} else {
Player targetP =
Bukkit.getPlayer(target);
if (targetP != null
&& targetP.isOnline()) {
if (radio.hasSong(songName)) {
radio.playToPlayer(
targetP, songName);
sender.sendMessage(
"§a已向 §e" + target
+ " §a播放 §e"
+ songName);
targetP.sendMessage(
"§6§l[广播] §f正在播放: §e"
+ songName);
} else {
sender.sendMessage(
"§c未找到歌曲: "
+ songName);
}
} else {
sender.sendMessage(
"§c玩家不在线: "
+ target);
}
return true;
}
} else {
if (radio.hasSong(songName)) {
radio.playToAll(songName);
sender.sendMessage(
"§a全服播放: §e" + songName);
} else {
sender.sendMessage(
"§c未找到歌曲: " + songName);
}
return true;
}
// pw / password
if (sub.equals("pw")
|| sub.equals("password")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
if (args.length >= 3) {
// /sdf1_login pw 旧密码 新密码
String oldPwd = args[1];
String newPwd = args[2];
if (!PasswordUtils.validate(
newPwd)) {
p2.sendMessage(config.msg(
"password_format_error"));
return true;
}
String salt = (String) db.getField(
p2.getName(),
"password_salt");
String hash =
PasswordUtils.hash(
oldPwd, salt);
boolean mainOk =
db.checkPassword(
p2.getName(), hash);
boolean tempOk =
db.checkPasswordOrTemp(
p2.getName(), hash);
if (!mainOk && !tempOk) {
p2.sendMessage(config.msg(
"password_wrong"));
return true;
}
String oldHash =
(String) db.getField(
p2.getName(),
"password_hash");
String newSalt =
PasswordUtils.generateSalt();
String newHash =
PasswordUtils.hash(
newPwd, newSalt);
if (newHash.equals(oldHash)) {
p2.sendMessage(config.msg(
"password_same"));
return true;
}
recordPasswordChange(
p2.getName(),
oldHash, salt);
db.setField(p2.getName(),
"password_hash", newHash);
db.setField(p2.getName(),
"password_salt", newSalt);
if (!mainOk && tempOk) {
db.clearTempPassword(
p2.getName());
}
needsPasswordChange.remove(
p2.getName());
p2.sendMessage(config.msg(
"password_changed"));
return true;
}
if (args.length == 2) {
// /sdf1_login pw 旧密码
// 验证旧密码,然后聊天输入新密码
String oldPwd = args[1];
String salt = (String) db.getField(
p2.getName(),
"password_salt");
String hash =
PasswordUtils.hash(
oldPwd, salt);
boolean mainOk =
db.checkPassword(
p2.getName(), hash);
boolean tempOk =
db.checkPasswordOrTemp(
p2.getName(), hash);
if (!mainOk && !tempOk) {
p2.sendMessage(config.msg(
"password_wrong"));
return true;
}
chatInput.getState(p2).type =
ChatInputManager.InputType
.CHANGE_PWD_STEP2;
chatInput.getState(p2)
.ticketTitle =
mainOk ? "main" : "temp";
p2.sendMessage(
"§e请输入新密码:");
return true;
}
// /sdf1_login pw (无参数)
chatInput.getState(p2).type =
ChatInputManager.InputType
.CHANGE_PWD_STEP1;
p2.sendMessage(
"§e请输入当前密码:");
return true;
}
// email
if (sub.equals("email")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
if (args.length >= 2) {
String emailAddr = args[1];
if (!emailAddr.contains("@")
|| !emailAddr.contains(".")) {
p2.sendMessage("§c邮箱格式不正确");
return true;
}
String oldEmail = (String) db.getField(
p2.getName(), "email");
if (oldEmail != null
&& !oldEmail.isEmpty()) {
// 已绑定:先验证密码
chatInput.getState(p2).type =
ChatInputManager.InputType
.SET_EMAIL_VERIFY;
chatInput.getState(p2)
.targetPlayer = emailAddr;
chatInput.getState(p2)
.ticketTitle = "need_pwd";
p2.sendMessage("§e您已绑定邮箱: "
+ oldEmail);
p2.sendMessage(
"§e请输入登录密码以确认修改:");
} else {
// 未绑定:直接发验证码
String code = String.valueOf(
(int) (Math.random()
* 900000 + 100000));
chatInput.getState(p2).type =
ChatInputManager.InputType
.SET_EMAIL_VERIFY;
chatInput.getState(p2)
.targetPlayer = emailAddr;
chatInput.getState(p2)
.ticketTitle = code;
final String to = emailAddr;
final String c = code;
final Player fp = p2;
Bukkit.getScheduler()
.runTaskAsynchronously(this,
() -> {
boolean sent = email
.sendVerifyCode(to, c);
Bukkit.getScheduler().runTask(
this, () -> {
if (sent) {
fp.sendMessage(
"§a验证码已发送到 "
+ to);
fp.sendMessage(
"§e输入6位验证码(0取消):");
} else {
fp.sendMessage(
"§c发送失败");
}
});
});
}
} else {
chatInput.getState(p2).type =
ChatInputManager.InputType
.SET_EMAIL;
p2.sendMessage("§e请输入邮箱:");
}
return true;
}
// sign
if (sub.equals("sign")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
if (dailySign((Player) sender))
playSuccessSound((Player) sender);
else
((Player) sender).sendMessage(
config.msg("checkin_already"));
return true;
}
// reset
if (sub.equals("reset")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
loginMgr.handleReset(
(Player) sender);
return true;
}
// undo
if (sub.equals("undo")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
Player p2 = (Player) sender;
PwdRollback rb =
pwdRollback.get(p2.getName());
if (rb == null) {
p2.sendMessage("§c无可撤销记录");
return true;
}
if (System.currentTimeMillis() - rb.time
> 600000L) {
pwdRollback.remove(p2.getName());
p2.sendMessage("§c撤销已过期");
return true;
}
db.setField(p2.getName(),
"password_hash", rb.hash);
db.setField(p2.getName(),
"password_salt", rb.salt);
pwdRollback.remove(p2.getName());
p2.sendMessage("§a密码已撤销!");
return true;
}
// take
if (sub.equals("take")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 3) {
sender.sendMessage(
"§e用法: /sdf1_login take <玩家> <积分>");
return true;
}
int amount;
try {
amount = Integer.parseInt(args[2]);
} catch (NumberFormatException ex) {
sender.sendMessage("§c无效数字");
return true;
}
if (!db.deductPoints(args[1], amount)) {
sender.sendMessage("§c积分不足");
return true;
}
sender.sendMessage("§a已扣除 " + args[1]
+ " " + amount + " 积分");
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")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 4) {
sender.sendMessage(
"§e用法: /sdf1_login shopadd <名称> <价格> <命令>");
return true;
}
String shopName = args[1];
int price;
try {
price = Integer.parseInt(args[2]);
} catch (NumberFormatException ex) {
sender.sendMessage("§c无效数字");
return true;
}
String shopCmd = args[3];
points.addItem(shopName, price, shopCmd);
sender.sendMessage("§a已添加商品: "
+ shopName + " (" + price
+ "积分)");
return true;
}
// shopdel
if (sub.equals("shopdel")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 2) {
sender.sendMessage(
"§e用法: /sdf1_login shopdel <序号>");
return true;
}
try {
int idx = Integer.parseInt(args[1]);
points.removeItem(idx);
sender.sendMessage("§a已删除商品 #" + idx);
} catch (NumberFormatException ex) {
sender.sendMessage("§c无效数字");
}
return true;
}
// get
if (sub.equals("get")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 2) {
sender.sendMessage(
"§e用法: /sdf1_login get <玩家>");
return true;
}
Map<String, Object> user =
db.getUser(args[1]);
if (user.isEmpty()) {
sender.sendMessage("§c玩家不存在");
return true;
}
sender.sendMessage(
"§e=== " + args[1] + " ===");
sender.sendMessage("§7积分: §e"
+ user.getOrDefault("points", 0));
sender.sendMessage("§7邮箱: §e"
+ user.getOrDefault("email", "无"));
sender.sendMessage("§7签到天数: §e"
+ user.getOrDefault(
"total_checkin_days", 0));
sender.sendMessage("§7邀请码: §e"
+ user.getOrDefault(
"invite_code", "无"));
return true;
}
// kick (挂机)
if (sub.equals("kick")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 2) {
sender.sendMessage(
"§e用法: /sdf1_login kick <开关> [分钟]");
return true;
}
String mode = args[1].toLowerCase();
if (mode.equals("on")
|| mode.equals("开")
|| mode.equals("启用")) {
config.afkEnabled = true;
if (args.length >= 3) {
try {
config.afkTimeout =
Integer.parseInt(
args[2]) * 60;
} catch (NumberFormatException ignored) {
}
}
config.saveSettings();
int minutes =
config.afkTimeout / 60;
sender.sendMessage(
"§a挂机踢出已开启,超时: "
+ minutes + " 分钟");
} else if (mode.equals("off")
|| mode.equals("关")
|| mode.equals("停用")) {
config.afkEnabled = false;
config.saveSettings();
sender.sendMessage(
"§c挂机踢出已关闭");
} else {
sender.sendMessage(
"§c无效参数 on/off");
}
return true;
}
// add
if (sub.equals("add")) {
if (!isAdmin(sender)) {
sender.sendMessage("§c权限不足");
return true;
}
if (args.length < 3) {
sender.sendMessage(
"§e用法: /sdf1_login add <玩家> <积分>");
return true;
}
int amount;
try {
amount = Integer.parseInt(args[2]);
} catch (NumberFormatException ex) {
sender.sendMessage("§c无效数字");
return true;
}
db.addPoints(args[1], amount);
sender.sendMessage("§a已为 " + args[1]
+ " 增加 " + amount + " 积分");
return true;
}
// ticket
if (sub.equals("ticket")
|| sub.equals("工单")) {
if (!(sender instanceof Player)) {
sender.sendMessage("§c仅玩家可用");
return true;
}
ticket.openMain((Player) sender);
return true;
}
sender.sendMessage("§c未知参数");
return true;
}
return true;
}
/**
* 供SDF1反射调用的CDK兑换
* 返回: "success:金额:余额前:余额后"
* 或 "fail:原因"
*/
public String redeemBondForExternal(
String playerName, String code) {
if (cdkManager == null)
return "fail:CDK未初始化";
return cdkManager.redeem(code, playerName);
}
// 旧的(可能缺失或拼写错误)
public SalesStatsManager getSalesStats() {
return salesStats;
}
/**
* 是否是垃圾站展示物品(带ID lore)
*/
private boolean isTrashDisplayItem(
ItemStack item) {
if (item == null
|| item.getType()
== Material.AIR)
return false;
if (!item.hasItemMeta())
return false;
ItemMeta im = item.getItemMeta();
if (im == null || !im.hasLore())
return false;
List<String> lore = im.getLore();
if (lore == null || lore.isEmpty())
return false;
return lore.get(0)
.startsWith("§7ID: #");
}
private void sendChatHelp(Player p) {
p.sendMessage(
"§e§l======== 聊天过滤帮助 ==========");
p.sendMessage("§e/chat reload §7- 重载配置");
p.sendMessage("§e/chat 开/关 §7- 开启/关闭聊天过滤");
p.sendMessage("§e/chat add <URL> §7- 添加白名单URL");
p.sendMessage("§e/chat remove <URL> §7- 移除白名单URL");
p.sendMessage("§e/chat addplayer <玩家> §7- 添加白名单玩家");
p.sendMessage("§e/chat takeplayer <玩家> §7- 移除白名单玩家");
p.sendMessage("§e/chat unmute <玩家> §7- 解禁玩家");
p.sendMessage("§e/chat reset <玩家> §7- 重置违规记录");
p.sendMessage(
"§e§l==================================");
}
private int findEmptySlot(Inventory inv) {
for (int i = 0; i < 49; i++) {
ItemStack item = inv.getItem(i);
if (item == null
|| item.getType()
== Material.AIR)
return i;
}
return -1;
}
// ===== TabComplete =====
@Override
public List<String> onTabComplete(
CommandSender sender, Command cmd,
String label, String[] args) {
List<String> list = new ArrayList<>();
if (cmd.getName().equalsIgnoreCase(
"sdf1_login")
&& args.length == 1) {
if (isAdmin(sender)) {
list.addAll(Arrays.asList(
"reload", "pw", "email",
"sign", "reset", "undo",
"get", "del", "set",
"take", "add", "kick",
"ticket", "oa", "stop",
"shopadd", "shopdel", "back", "radio", "back", "shop"));
} else {
list.addAll(Arrays.asList(
"pw", "email", "sign",
"reset", "undo",
"ticket", "oa", "stop"));
}
}
if (cmd.getName().equalsIgnoreCase("protect")
|| cmd.getName().equals("区域保护")) {
String sub = args.length > 0 ? args[0].toLowerCase() : "";
// ===== 第一层:子命令 =====
if (args.length == 1) {
List<String> opts = Arrays.asList(
"工具", "创建", "列表", "重载", "删除",
"add", "remove",
"additem", "removeitem",
"on", "off", "tempon",
"expand", "contraction",
"list", "listitem",
"addname", "removename", "listname",
"addwhite", "removewhite", "listwhite");
return filterTab(opts, args[0]);
}
// ===== 第二层 =====
if (args.length == 2) {
// add/remove: 区域名 + global + 在线玩家
if (sub.equals("add") || sub.equals("remove")) {
List<String> all = new ArrayList<>();
all.add("global");
all.addAll(areaProtection.getAreaNames());
for (Player pl : Bukkit.getOnlinePlayers())
all.add(pl.getName());
return filterTab(all, args[1]);
}
// additem/removeitem: 区域名 + global
if (sub.equals("additem") || sub.equals("removeitem")) {
List<String> all = new ArrayList<>();
all.add("global");
all.addAll(areaProtection.getAreaNames());
return filterTab(all, args[1]);
}
// list/listitem: 区域名
if (sub.equals("list") || sub.equals("listitem")) {
return filterTab(
new ArrayList<>(areaProtection.getAreaNames()),
args[1]);
}
// expand/contraction: 数字
if (sub.equals("expand") || sub.equals("contraction")) {
return Arrays.asList("1", "3", "5", "10", "20", "50");
}
// 创建/删除: 区域名
if (sub.equals("创建") || sub.equals("删除")) {
return filterTab(
new ArrayList<>(areaProtection.getAreaNames()),
args[1]);
}
// addname/removename/listname: 区域名
if (sub.equals("addname") || sub.equals("removename")
|| sub.equals("listname")) {
return filterTab(
new ArrayList<>(areaProtection.getAreaNames()),
args[1]);
}
// addwhite/removewhite/listwhite: 区域名
if (sub.equals("addwhite") || sub.equals("removewhite")
|| sub.equals("listwhite")) {
return filterTab(
new ArrayList<>(areaProtection.getAreaNames()),
args[1]);
}
}
// ===== 第三层 =====
if (args.length == 3) {
// add/remove: 第三层是玩家名
if (sub.equals("add") || sub.equals("remove")) {
List<String> players = new ArrayList<>();
for (Player pl : Bukkit.getOnlinePlayers())
players.add(pl.getName());
return filterTab(players, args[2]);
}
// additem/removeitem: 第三层是物品ID
if (sub.equals("additem") || sub.equals("removeitem")) {
List<String> items = Arrays.asList(
"TNT", "BROWN_MUSHROOM", "RED_MUSHROOM",
"MUSHROOM_STEW", "POISONOUS_POTATO",
"SPIDER_EYE", "POTION", "SPLASH_POTION",
"OMINOUS_BOTTLE", "ENDER_PEARL",
"BOW", "CROSSBOW", "ARROW",
"FLINT_AND_STEEL");
return filterTab(items, args[2]);
}
// addname/removename: 第三层是生物名字(自由输入)
if (sub.equals("addname") || sub.equals("removename")) {
return null;
}
// addwhite/removewhite: 第三层是玩家名
if (sub.equals("addwhite") || sub.equals("removewhite")) {
List<String> players = new ArrayList<>();
for (Player pl : Bukkit.getOnlinePlayers())
players.add(pl.getName());
return filterTab(players, args[2]);
}
}
return null;
}
if (cmd.getName().equalsIgnoreCase("chat")
&& args.length == 1
&& isAdmin(sender)) {
list.addAll(Arrays.asList(
"reload", "add", "remove",
"addplayer", "takeplayer",
"unmute", "reset"));
}
if (cmd.getName().equalsIgnoreCase("cypay")) {
return cypayCommand.onTabComplete(sender, cmd, label, args);
}
return list;
}
}