3712 lines
133 KiB
Plaintext
3712 lines
133 KiB
Plaintext
package Sdf1_login;
|
||
|
||
import java.net.InetSocketAddress;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.HashMap;
|
||
import org.bukkit.event.player.PlayerInteractEvent;
|
||
import java.util.Iterator;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Set;
|
||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||
import org.bukkit.event.player.PlayerMoveEvent;
|
||
import org.bukkit.Location;
|
||
import org.bukkit.entity.Entity;
|
||
import org.bukkit.entity.Villager;
|
||
import Sdf1_login.ChatInputManager.InputType;
|
||
|
||
|
||
|
||
|
||
import java.util.TreeSet;
|
||
import java.util.UUID;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.concurrent.ConcurrentHashMap;
|
||
import org.bukkit.event.entity.EntityDeathEvent;
|
||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||
import org.bukkit.event.inventory.CraftItemEvent;
|
||
import org.bukkit.event.player.PlayerFishEvent;
|
||
|
||
|
||
import net.milkbowl.vault.economy.Economy;
|
||
import org.bukkit.Bukkit;
|
||
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.inventory.InventoryClickEvent;
|
||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||
import org.bukkit.event.player.PlayerJoinEvent;
|
||
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;
|
||
|
||
public class Main extends JavaPlugin
|
||
implements CommandExecutor, Listener,
|
||
TabCompleter {
|
||
|
||
private DatabaseManager db;
|
||
private ConfigManager config;
|
||
private EmailManager email;
|
||
private LoginManager loginMgr;
|
||
private AFKManager afk;
|
||
private GiftManager gift;
|
||
private CheckInManager checkIn;
|
||
private InviteManager invite;
|
||
private PointsManager points;
|
||
private 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;
|
||
|
||
private final Set<String> loggedIn =
|
||
new TreeSet<>(
|
||
String.CASE_INSENSITIVE_ORDER);
|
||
private final Set<String> needsPasswordChange =
|
||
new TreeSet<>(
|
||
String.CASE_INSENSITIVE_ORDER);
|
||
private final Set<String> frozenFlightPlayers =
|
||
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 final Map<UUID, Long> lastActivity =
|
||
new ConcurrentHashMap<>();
|
||
// 登录后首次回收飞行(仅一次)
|
||
private final Set<String> removeFlightOnce =
|
||
new TreeSet<>(
|
||
String.CASE_INSENSITIVE_ORDER);
|
||
private RadioManager radio;
|
||
public RadioManager getRadio() {
|
||
return radio;
|
||
}
|
||
|
||
|
||
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 final Map<String, PwdRollback>
|
||
pwdRollback =
|
||
new 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 ChatFilterManager getChatFilter() { return chatFilter; }
|
||
public GarbageManager getGarbage() { return garbage; }
|
||
public Set<String> getLoggedIn() { return loggedIn; }
|
||
public Set<String> getNeedsPasswordChange() { return needsPasswordChange; }
|
||
private WelcomeManager welcome;
|
||
private UpdateChecker updateChecker;
|
||
private QuestTracker questTracker;
|
||
private CommissionManager commission;
|
||
public CommissionManager getCommission() {
|
||
return commission;
|
||
}
|
||
|
||
|
||
|
||
// ===== Enable/Disable =====
|
||
@Override
|
||
public void onEnable() {
|
||
radio = new RadioManager(this);
|
||
getDataFolder().mkdirs();
|
||
config = new ConfigManager(getDataFolder());
|
||
config.loadMessages();
|
||
config.loadSmtp();
|
||
config.loadSettings();
|
||
getCommand("oa").setExecutor(this);
|
||
|
||
db = new DatabaseManager(getDataFolder());
|
||
db.init();
|
||
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);
|
||
gui = new GUIManager(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);
|
||
garbage = new GarbageManager(this);
|
||
garbage.init();
|
||
garbage.loadConfig();
|
||
garbage.startAutoCleanup();
|
||
chatFilter = new ChatFilterManager(this);
|
||
chatFilter.loadConfig();
|
||
welcome = new WelcomeManager(this);
|
||
updateChecker = new UpdateChecker(this);
|
||
questTracker = new QuestTracker(this);
|
||
commission = new CommissionManager(this);
|
||
|
||
|
||
setupEconomy();
|
||
|
||
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("绑定邮箱") != null)
|
||
getCommand("绑定邮箱").setExecutor(this);
|
||
if (getCommand("找回密码") != null)
|
||
getCommand("找回密码").setExecutor(this);
|
||
if (getCommand("玩家信息") != null)
|
||
getCommand("玩家信息").setExecutor(this);
|
||
if (getCommand("sdf1debug") != null)
|
||
getCommand("sdf1debug").setExecutor(this);
|
||
if (getCommand("oa") != null)
|
||
getCommand("oa").setExecutor(this);
|
||
if (getCommand("menu") != null)
|
||
getCommand("menu").setExecutor(this);
|
||
if (getCommand("radio") != null)
|
||
getCommand("radio").setExecutor(this);
|
||
getServer().getPluginManager()
|
||
.registerEvents(this, this);
|
||
|
||
afk.startCheck();
|
||
startLoginReminder();
|
||
startTimeoutCheck();
|
||
startPasswordReminder();
|
||
startTicketAutoProcess();
|
||
startPlayTimeTracker();
|
||
// 启动后异步检查更新
|
||
updateChecker.checkOnEnable();
|
||
|
||
getLogger().info(
|
||
"Sdf1_login v1.0 | 就绪");
|
||
for (Player online
|
||
: Bukkit.getOnlinePlayers()) {
|
||
radio.sendResourcePack(online);
|
||
}
|
||
|
||
|
||
}
|
||
|
||
@Override
|
||
public void onDisable() {
|
||
if (questTracker != null) {
|
||
questTracker.shutdown();
|
||
}
|
||
if (radio != null) {
|
||
radio.stopAllLoginRadio();
|
||
radio.stopMainRadio();
|
||
}
|
||
|
||
if (questTracker != null) {
|
||
}
|
||
}
|
||
|
||
// 没替换,只是粗略看了下,有几个问题
|
||
private void setupEconomy() {
|
||
if (getServer().getPluginManager()
|
||
.getPlugin("Vault") == null) return;
|
||
RegisteredServiceProvider<Economy> rsp =
|
||
getServer().getServicesManager()
|
||
.getRegistration(
|
||
Economy.class);
|
||
if (rsp != null)
|
||
economy = rsp.getProvider();
|
||
}
|
||
public WelcomeManager getWelcome() {
|
||
return welcome;
|
||
}
|
||
public UpdateChecker getUpdateChecker() {
|
||
return updateChecker;
|
||
}
|
||
public QuestTracker getQuestTracker() {
|
||
return questTracker;
|
||
}
|
||
|
||
// ===== 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 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();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
@EventHandler
|
||
public void onBlockPlace_Quest(
|
||
BlockPlaceEvent e) {
|
||
if (questTracker == null) return;
|
||
Player p = e.getPlayer();
|
||
if (isFrozen(p)) return;
|
||
questTracker.onBlockPlaced(
|
||
p.getName(),
|
||
e.getBlock().getType().name());
|
||
}
|
||
|
||
@EventHandler
|
||
public void onMoveBiome(
|
||
PlayerMoveEvent e) {
|
||
if (questTracker == null) return;
|
||
Player p = e.getPlayer();
|
||
if (isFrozen(p)) return;
|
||
Location to = e.getTo();
|
||
if (to == null) return;
|
||
String fromKey = e.getFrom().getBlock()
|
||
.getBiome().getKey().getKey();
|
||
String toKey = to.getBlock()
|
||
.getBiome().getKey().getKey();
|
||
if (fromKey.equals(toKey)) return;
|
||
questTracker.onPlayerMove(
|
||
p.getName(), toKey);
|
||
}
|
||
|
||
|
||
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;
|
||
}
|
||
radio.stopLoginRadio(p);
|
||
|
||
}
|
||
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);
|
||
removeFlightOnce.add(name);
|
||
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);
|
||
|
||
// 标记注册登录条件完成
|
||
if (questTracker != null) {
|
||
questTracker.markConditionComplete(
|
||
name, "完成注册登录");
|
||
}
|
||
|
||
// 欢迎信息
|
||
this.getWelcome().onLogin(p);
|
||
|
||
// 只在有有效备份时恢复
|
||
restoreInventory(p);
|
||
|
||
giveMenuSnowball(p);
|
||
p.sendMessage("§a[Sdf1_login] §f您已自动登录!");
|
||
activateBeibao(p);
|
||
pushPendingAlerts(p);
|
||
}
|
||
|
||
// ===== 任务追踪事件 =====
|
||
|
||
@EventHandler
|
||
public void onBlockMine(BlockBreakEvent event) {
|
||
if (questTracker == null) return; // ← 新增
|
||
Player p = event.getPlayer();
|
||
if (isFrozen(p)) return;
|
||
questTracker.onBlockMined(
|
||
p.getName(),
|
||
event.getBlock().getType().name());
|
||
}
|
||
|
||
@EventHandler
|
||
public void onMobDeath(EntityDeathEvent e) {
|
||
if (questTracker == null) return; // ← 新增
|
||
if (e.getEntity().getKiller() == null)
|
||
return;
|
||
Player p = e.getEntity().getKiller();
|
||
questTracker.onMobKill(
|
||
p.getName(),
|
||
e.getEntity().getType().name());
|
||
}
|
||
|
||
@EventHandler
|
||
public void onPlayerDeath(
|
||
PlayerDeathEvent e) {
|
||
if (questTracker == null) return; // ← 新增
|
||
Player killer =
|
||
e.getEntity().getKiller();
|
||
if (killer == null) return;
|
||
questTracker.onPlayerKill(
|
||
killer.getName());
|
||
}
|
||
|
||
@EventHandler
|
||
public void onCraft(
|
||
CraftItemEvent e) {
|
||
if (questTracker == null) return; // ← 新增
|
||
if (!(e.getWhoClicked()
|
||
instanceof Player)) return;
|
||
Player p = (Player) e.getWhoClicked();
|
||
if (isFrozen(p)) return;
|
||
questTracker.onItemCrafted(
|
||
p.getName());
|
||
}
|
||
|
||
@EventHandler
|
||
public void onFish(
|
||
PlayerFishEvent e) {
|
||
if (questTracker == null) return; // ← 新增
|
||
if (e.getState()
|
||
!= PlayerFishEvent
|
||
.State.CAUGHT_FISH) return;
|
||
if (!(e.getCaught()
|
||
instanceof
|
||
org.bukkit.entity.Item)) return;
|
||
Player p = e.getPlayer();
|
||
questTracker.onFishCaught(
|
||
p.getName());
|
||
}
|
||
|
||
// 在线时长追踪(每分钟)
|
||
public void startPlayTimeTracker() {
|
||
Bukkit.getScheduler()
|
||
.runTaskTimer(this,
|
||
() -> {
|
||
if (questTracker == null) return;
|
||
for (Player p :
|
||
Bukkit.getOnlinePlayers()) {
|
||
if (isFrozen(p)) continue;
|
||
questTracker.onPlayTime(
|
||
p.getName(),
|
||
60000L);
|
||
}
|
||
}, 1200L, 1200L);
|
||
}
|
||
|
||
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 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("_", " ");
|
||
}
|
||
}
|
||
|
||
@EventHandler
|
||
public void onTeleportBiome(
|
||
PlayerTeleportEvent e) {
|
||
if (questTracker == null) return;
|
||
Player p = e.getPlayer();
|
||
if (isFrozen(p)) return;
|
||
Location to = e.getTo();
|
||
if (to == null) return;
|
||
String toKey = to.getBlock()
|
||
.getBiome().getKey().getKey();
|
||
questTracker.onPlayerMove(
|
||
p.getName(), toKey);
|
||
}
|
||
|
||
@EventHandler
|
||
public void onJoin(PlayerJoinEvent e) {
|
||
Player p = e.getPlayer();
|
||
String name = p.getName();
|
||
String ip = getPlayerIP(p);
|
||
// 重置聊天输入状态(防止上次退出时残留)
|
||
chatInput.reset(p);
|
||
if (needsPasswordChange.contains(name))
|
||
needsPasswordChange.remove(name);
|
||
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);
|
||
frozenFlightPlayers.add(p.getName());
|
||
}
|
||
if (questTracker != null) {
|
||
questTracker.debugLogin(p.getName());
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
|
||
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();
|
||
// 记录冻结状态的飞行
|
||
if (!db.userExists(p.getName())
|
||
|| !loggedIn.contains(p.getName())) {
|
||
p.setAllowFlight(true);
|
||
frozenFlightPlayers.add(p.getName());
|
||
}
|
||
|
||
String name = p.getName();
|
||
chatInput.reset(p);
|
||
|
||
boolean wasLoggedIn =
|
||
loggedIn.contains(name);
|
||
|
||
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);
|
||
}
|
||
|
||
frozenFlightPlayers.remove(name); // ← 新增
|
||
lastActivity.remove(p.getUniqueId());
|
||
joinTime.remove(p.getUniqueId());
|
||
joinLoc.remove(p.getUniqueId());
|
||
needsPasswordChange.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;
|
||
}
|
||
}
|
||
// 发放
|
||
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);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 手动发放雪球菜单(绕过开关检查)
|
||
*/
|
||
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;
|
||
}
|
||
|
||
@EventHandler
|
||
public void onMove(PlayerMoveEvent e) {
|
||
Player p = e.getPlayer();
|
||
|
||
// 已登录:一次性关闭飞行
|
||
if (!isFrozen(p)) {
|
||
if (frozenFlightPlayers.remove(
|
||
p.getName())) {
|
||
p.setAllowFlight(false);
|
||
p.setFlying(false);
|
||
}
|
||
return;
|
||
}
|
||
|
||
|
||
// 未登录:检查是否能移动
|
||
Location from = e.getFrom();
|
||
Location to = e.getTo();
|
||
if (to == null) return;
|
||
|
||
// 允许转头(无位移)
|
||
if (from.getBlockX() == to.getBlockX()
|
||
&& from.getBlockY() == to.getBlockY()
|
||
&& from.getBlockZ() == to.getBlockZ()) {
|
||
return;
|
||
}
|
||
|
||
// 有位移 → 取消移动
|
||
e.setCancelled(true);
|
||
|
||
// 确保允许飞行(防止被踢出)
|
||
if (!p.getAllowFlight()) {
|
||
p.setAllowFlight(true);
|
||
}
|
||
|
||
// 确保已记录(首次加入时未触发移动的情况)
|
||
if (!frozenFlightPlayers.contains(
|
||
p.getName())) {
|
||
frozenFlightPlayers.add(p.getName());
|
||
}
|
||
}
|
||
|
||
@EventHandler
|
||
public void onInteract(PlayerInteractEvent e) {
|
||
Player p = e.getPlayer();
|
||
ItemStack item = e.getItem();
|
||
if (item == null
|
||
|| item.getType()
|
||
!= Material.SNOWBALL)
|
||
return;
|
||
if (!isMenuSnowball(item)) return;
|
||
if (isFrozen(p)) return;
|
||
if (!loggedIn.contains(p.getName())) return;
|
||
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());
|
||
}
|
||
|
||
@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);
|
||
}
|
||
|
||
@EventHandler(priority = EventPriority.LOWEST)
|
||
public void onChat(AsyncPlayerChatEvent e) {
|
||
Player p = e.getPlayer();
|
||
String msg = e.getMessage();
|
||
|
||
if (chatInput.getState(p).type
|
||
!= ChatInputManager.InputType.NONE) {
|
||
e.setCancelled(true);
|
||
ChatInputManager.InputType inputType =
|
||
chatInput.getState(p).type;
|
||
if (ticket != null
|
||
&& ticket.handleChatInput(
|
||
p, msg, inputType)) {
|
||
return;
|
||
}
|
||
chatInput.handleInput(p, msg, this);
|
||
return;
|
||
}
|
||
|
||
if (isFrozen(p)) {
|
||
e.setCancelled(true);
|
||
return;
|
||
}
|
||
// 在 onChat 方法中,isFrozen 检查之后
|
||
// (在 chatFilter 检查之前)
|
||
if (!isFrozen(p)
|
||
&& questTracker != null) {
|
||
final String pn = p.getName();
|
||
Bukkit.getScheduler().runTask(this,
|
||
() -> questTracker
|
||
.onPlayerChat(pn));
|
||
}
|
||
|
||
if (chatFilter == null) return;
|
||
// 安全兜底:任何输入状态下跳过过滤
|
||
if (chatInput.getState(p).type
|
||
!= ChatInputManager.InputType.NONE) {
|
||
return;
|
||
}
|
||
|
||
if (chatFilter.isPlayerWhitelisted(
|
||
p.getName())) return;
|
||
// 联控:绑定邮箱/配置SMTP时跳过URL过滤
|
||
InputType inType =
|
||
chatInput.getState(p).type;
|
||
if (inType == InputType.SET_EMAIL
|
||
|| inType == InputType.SET_EMAIL_VERIFY
|
||
|| inType == InputType.SMTP_CONFIG
|
||
|| inType == InputType.SMTP_VERIFY) {
|
||
return;
|
||
}
|
||
|
||
if (chatFilter.isMuted(p.getName())) {
|
||
e.setCancelled(true);
|
||
p.sendMessage(
|
||
chatFilter.msg("chat_muted"));
|
||
return;
|
||
}
|
||
|
||
List<String> urls =
|
||
chatFilter.extractUrls(msg);
|
||
if (urls.isEmpty()) return;
|
||
|
||
for (String u : urls) {
|
||
if (chatFilter.isPlayerWhitelisted(u))
|
||
continue;
|
||
if (!chatFilter.isLikelyDomain(u))
|
||
continue;
|
||
|
||
e.setCancelled(true);
|
||
chatFilter.incrementViolation(
|
||
p.getName());
|
||
int count = chatFilter
|
||
.getViolationCount(p.getName());
|
||
|
||
p.sendMessage(chatFilter
|
||
.msg("chat_url_blocked")
|
||
.replace("{url}", u));
|
||
p.sendMessage(chatFilter
|
||
.msg("chat_url_violation")
|
||
.replace("{count}",
|
||
String.valueOf(count)));
|
||
|
||
chatFilter.applyPunishment(p, count);
|
||
|
||
if (chatFilter.isNotifyAdmin()) {
|
||
String tag = config.adminTag;
|
||
for (Player op :
|
||
Bukkit.getOnlinePlayers()) {
|
||
if (op.getScoreboardTags()
|
||
.contains(tag)
|
||
&& !op.equals(p)) {
|
||
op.sendMessage(chatFilter
|
||
.msg("chat_url_admin_notify")
|
||
.replace("{player}",
|
||
p.getName())
|
||
.replace("{url}", u)
|
||
.replace("{count}",
|
||
String.valueOf(
|
||
count)));
|
||
}
|
||
}
|
||
}
|
||
|
||
if (chatFilter.isNotifyAll()) {
|
||
Bukkit.broadcastMessage(chatFilter
|
||
.msg("chat_url_broadcast")
|
||
.replace("{player}",
|
||
p.getName()));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// ===== Inventory Click =====
|
||
// ===== Inventory Click =====
|
||
@EventHandler
|
||
public void onInvClick(InventoryClickEvent e) {
|
||
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();
|
||
|
||
// Shift+点击背包物品 → 存入垃圾箱
|
||
if (e.isShiftClick()
|
||
&& e.getClickedInventory() != null
|
||
&& e.getClickedInventory()
|
||
!= e.getView().getTopInventory()
|
||
&& e.getCurrentItem() != null
|
||
&& e.getCurrentItem().getType()
|
||
!= Material.AIR
|
||
&& !isMenuSnowball(
|
||
e.getCurrentItem())
|
||
&& title.equals(
|
||
"§6§l垃圾回收站")) {
|
||
e.setCancelled(true);
|
||
|
||
ItemStack clicked =
|
||
e.getCurrentItem().clone();
|
||
|
||
getLogger().info(
|
||
"[垃圾站] Shift存入: "
|
||
+ clicked.getType()
|
||
+ " x" + clicked.getAmount());
|
||
|
||
// 先存入数据库
|
||
garbage.saveItem(clicked);
|
||
|
||
// 关闭背包强制同步服务器与客户端状态
|
||
// (解决Paper的shift-click取消后仍移动物品的问题)
|
||
p.closeInventory();
|
||
|
||
// 1 tick后:移除物品 + 重新打开垃圾站
|
||
Bukkit.getScheduler()
|
||
.runTaskLater(this,
|
||
() -> {
|
||
// 此时物品状态已同步
|
||
// 搜索并移除对应物品
|
||
boolean found = false;
|
||
for (int i = 0;
|
||
i < p.getInventory()
|
||
.getSize();
|
||
i++) {
|
||
ItemStack is =
|
||
p.getInventory()
|
||
.getItem(i);
|
||
if (is != null
|
||
&& is.getType()
|
||
== clicked
|
||
.getType()
|
||
&& is.getAmount()
|
||
== clicked
|
||
.getAmount()) {
|
||
p.getInventory()
|
||
.setItem(
|
||
i,
|
||
null);
|
||
found = true;
|
||
getLogger()
|
||
.info(
|
||
"[垃圾站] "
|
||
+ "移除成功: slot="
|
||
+ i);
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
getLogger()
|
||
.info(
|
||
"[垃圾站] "
|
||
+ "物品已被移走");
|
||
}
|
||
|
||
p.updateInventory();
|
||
|
||
// 重新打开垃圾站
|
||
garbage
|
||
.openRecyclePage(
|
||
p,
|
||
garbage
|
||
.getRecyclePage(
|
||
p
|
||
.getName()));
|
||
}, 2L);
|
||
return;
|
||
}
|
||
|
||
|
||
if (needsPasswordChange
|
||
.contains(p.getName())
|
||
&& !title.equals(
|
||
"§6§l任务面板")) {
|
||
e.setCancelled(true);
|
||
p.sendMessage("§c请先修改密码!");
|
||
return;
|
||
}
|
||
// 任务中心
|
||
if (title.equals(
|
||
GUIManager.T_TASK_CENTER)) {
|
||
e.setCancelled(true);
|
||
if (slot == 26) gui.openMain(p);
|
||
else if (slot == 10)
|
||
gui.openGiftStages(p);
|
||
else if (slot == 12)
|
||
gui.openTaskList(p, "主线任务");
|
||
else if (slot == 14)
|
||
gui.openTaskList(p, "支线任务");
|
||
else if (slot == 16) {
|
||
if (dailySign(p)) {
|
||
gui.openTaskCenter(p);
|
||
playSuccessSound(p);
|
||
} else
|
||
p.sendMessage(config.msg(
|
||
"checkin_already"));
|
||
}
|
||
else if (slot == 21) {
|
||
handleCheckInReport(p);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 工单GUI
|
||
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我处理中的工单")) {
|
||
e.setCancelled(true);
|
||
ticket.handleClick(
|
||
p, title, slot);
|
||
return;
|
||
}
|
||
|
||
// 垃圾回收站
|
||
if (title.equals("§6§l垃圾回收站")) {
|
||
if (slot < 0 || slot >= 54) return;
|
||
|
||
// 功能格 49-53
|
||
if (slot >= 49 && slot <= 53) {
|
||
e.setCancelled(true);
|
||
garbage.handleClick(
|
||
p, title, slot);
|
||
return;
|
||
}
|
||
|
||
ItemStack inSlot =
|
||
e.getInventory().getItem(slot);
|
||
ItemStack cursor = e.getCursor();
|
||
|
||
// ===== 取出 =====
|
||
if (inSlot != null
|
||
&& inSlot.hasItemMeta()
|
||
&& inSlot.getItemMeta().hasLore()) {
|
||
List<String> lore =
|
||
inSlot.getItemMeta().getLore();
|
||
if (lore != null && !lore.isEmpty()
|
||
&& lore.get(0)
|
||
.startsWith("§7ID: #")) {
|
||
e.setCancelled(true);
|
||
try {
|
||
String numStr = lore.get(0)
|
||
.replace("§7ID: #", "")
|
||
.trim();
|
||
int dbId =
|
||
Integer.parseInt(numStr);
|
||
if (garbage.removeItem(dbId)) {
|
||
ItemStack give =
|
||
inSlot.clone();
|
||
ItemMeta gm =
|
||
give.getItemMeta();
|
||
gm.setLore(null);
|
||
give.setItemMeta(gm);
|
||
p.getInventory()
|
||
.addItem(give);
|
||
e.getInventory()
|
||
.setItem(slot, null);
|
||
}
|
||
} catch (Exception ex) {
|
||
p.sendMessage("§c取出失败");
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// ===== 光标存入 =====
|
||
if (cursor != null
|
||
&& cursor.getType()
|
||
!= Material.AIR
|
||
&& !isMenuSnowball(cursor)) {
|
||
ItemStack toSave = cursor.clone();
|
||
getLogger().info(
|
||
"[垃圾站] 存入: "
|
||
+ toSave.getType()
|
||
+ " x" + toSave.getAmount());
|
||
garbage.saveItem(toSave);
|
||
e.setCancelled(true);
|
||
Bukkit.getScheduler().runTaskLater(
|
||
this, () -> {
|
||
p.getOpenInventory()
|
||
.setCursor(null);
|
||
garbage.openRecyclePage(p,
|
||
garbage.getRecyclePage(
|
||
p.getName()));
|
||
}, 1L);
|
||
return;
|
||
}
|
||
|
||
// 默认取消
|
||
e.setCancelled(true);
|
||
return;
|
||
}
|
||
|
||
// 主菜单
|
||
if (title.equals(GUIManager.T_MAIN)) {
|
||
e.setCancelled(true);
|
||
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.openTaskCenter(p);
|
||
else if (slot == 18) ticket.openMain(p);
|
||
else if (slot == 20) {
|
||
if (p.isOp() || p.getScoreboardTags()
|
||
.contains(config.adminTag)) {
|
||
garbage.openRecycle(p);
|
||
}
|
||
}
|
||
|
||
else if (slot == 22 && isAdmin(p)) {
|
||
p.closeInventory();
|
||
chatInput.getState(p).type =
|
||
ChatInputManager.InputType
|
||
.ADMIN_AUTH;
|
||
p.sendMessage("请输入管理密码:");
|
||
}
|
||
return;
|
||
}
|
||
|
||
|
||
// 我的信息
|
||
if (title.equals(GUIManager.T_MY_INFO)) {
|
||
e.setCancelled(true);
|
||
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)
|
||
gui.openTaskList(p, "主线任务");
|
||
else if (slot == 14)
|
||
gui.openTaskList(p, "支线任务");
|
||
else if (slot == 16) {
|
||
if (dailySign(p)) {
|
||
gui.openTaskCenter(p);
|
||
playSuccessSound(p);
|
||
} else
|
||
p.sendMessage(config.msg(
|
||
"checkin_already"));
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 新人任务
|
||
if (title.equals(
|
||
GUIManager.T_GIFT_STAGES)) {
|
||
e.setCancelled(true);
|
||
if (slot == 53) {
|
||
gui.openTaskCenter(p);
|
||
} else if (slot == 52) {
|
||
// 打卡上报
|
||
handleCheckInReport(p);
|
||
} else if (slot == 4) {
|
||
// 进度标题
|
||
} else if (slot >= 10 && slot < 53) {
|
||
int idx = slot - 10;
|
||
QuestTracker qt = questTracker;
|
||
List<QuestTracker.QuestFile> quests =
|
||
qt.getQuests("新人任务");
|
||
if (idx < quests.size()) {
|
||
QuestTracker.QuestFile qf =
|
||
quests.get(idx);
|
||
boolean done =
|
||
qt.isStageCompleted(
|
||
p.getName(), qf);
|
||
if (done
|
||
&& !qt.hasClaimed(
|
||
p.getName(), qf)) {
|
||
qt.markClaimed(
|
||
p.getName(), qf);
|
||
qt.claimRewards(p, qf);
|
||
gui.openGiftStages(p);
|
||
} else if (done
|
||
&& qt.hasClaimed(
|
||
p.getName(), qf)) {
|
||
p.sendMessage(
|
||
"§c该阶段奖励已领取");
|
||
} else {
|
||
p.sendMessage(
|
||
"§c该阶段尚未完成");
|
||
}
|
||
}
|
||
}
|
||
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 == 13) {
|
||
gui.openCheckInRecords(p, 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) {
|
||
|
||
} 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;
|
||
}
|
||
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("§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);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 主线/支线任务
|
||
if (title.equals("§d§l主线任务")
|
||
|| title.equals("§d§l支线任务")) {
|
||
e.setCancelled(true);
|
||
if (slot == e.getInventory().getSize() - 1)
|
||
gui.openTaskCenter(p);
|
||
return;
|
||
}
|
||
}
|
||
/**
|
||
* 打卡上报:验证当前阶段条件
|
||
*/
|
||
|
||
private void handleCheckInReport(Player p) {
|
||
QuestTracker qt = questTracker;
|
||
if (qt == null) {
|
||
p.sendMessage("§c任务系统未加载");
|
||
return;
|
||
}
|
||
|
||
p.sendMessage("§e§l[打卡] §f正在检测环境...");
|
||
|
||
// === 检测当前群系 ===
|
||
Location loc = p.getLocation();
|
||
String biomeKey = loc.getBlock()
|
||
.getBiome().getKey().getKey();
|
||
String worldName = loc.getWorld().getName();
|
||
|
||
List<String> biomeMatched =
|
||
qt.checkInBiome(
|
||
p.getName(),
|
||
biomeKey, worldName,
|
||
loc.getX(), loc.getY(),
|
||
loc.getZ());
|
||
|
||
qt.recordCheckIn(
|
||
p.getName(), biomeKey,
|
||
qt.mapBiomeCategory(
|
||
biomeKey, worldName),
|
||
worldName,
|
||
loc.getX(), loc.getY(),
|
||
loc.getZ());
|
||
|
||
p.sendMessage("§7 群系: §e"
|
||
+ biomeKey);
|
||
p.sendMessage("§7 分类: §e"
|
||
+ qt.mapBiomeCategory(
|
||
biomeKey, worldName));
|
||
p.sendMessage("§7 世界: §e"
|
||
+ worldName);
|
||
p.sendMessage("§7 坐标: §e"
|
||
+ String.format("%.0f, %.0f, %.0f",
|
||
loc.getX(), loc.getY(),
|
||
loc.getZ()));
|
||
|
||
if (!biomeMatched.isEmpty()) {
|
||
for (String m : biomeMatched) {
|
||
p.sendMessage("§7 §a✓ " + m);
|
||
}
|
||
}
|
||
|
||
// === 检测附近村庄(50格内有村民) ===
|
||
boolean nearVillage = false;
|
||
for (Entity entity
|
||
: p.getNearbyEntities(50, 50, 50)) {
|
||
if (entity instanceof Villager) {
|
||
nearVillage = true;
|
||
break;
|
||
}
|
||
}
|
||
if (nearVillage) {
|
||
boolean counted =
|
||
qt.onReportVillage(
|
||
p.getName(),
|
||
worldName,
|
||
loc.getX(),
|
||
loc.getY(),
|
||
loc.getZ());
|
||
if (counted) {
|
||
p.sendMessage(
|
||
"§7 §a村庄上报计数成功!");
|
||
} else {
|
||
p.sendMessage(
|
||
"§7 §e村庄32格内已上报过");
|
||
}
|
||
}
|
||
|
||
|
||
// === 验证所有未完成的任务 ===
|
||
p.sendMessage("");
|
||
p.sendMessage("§e§l[打卡] §f正在验证"
|
||
+ "未完成的任务...");
|
||
|
||
List<String> categories =
|
||
Arrays.asList(
|
||
"新人任务",
|
||
"主线任务",
|
||
"支线任务");
|
||
|
||
int totalMet = 0;
|
||
int totalChecked = 0;
|
||
int finishedCount = 0;
|
||
|
||
for (String cat : categories) {
|
||
List<QuestTracker.QuestFile> quests =
|
||
qt.getQuests(cat);
|
||
for (QuestTracker.QuestFile qf
|
||
: quests) {
|
||
if (qt.isStageCompleted(
|
||
p.getName(), qf)) {
|
||
finishedCount++;
|
||
continue;
|
||
}
|
||
if (!qf.hasConditions())
|
||
continue;
|
||
|
||
p.sendMessage("§e§l["
|
||
+ qf.displayName + "]");
|
||
|
||
for (String cond
|
||
: qf.conditions) {
|
||
if (qt.isOptional(cond))
|
||
continue;
|
||
totalChecked++;
|
||
|
||
boolean met =
|
||
qt.isConditionMet(
|
||
p.getName(),
|
||
cond);
|
||
|
||
if (met) {
|
||
totalMet++;
|
||
p.sendMessage("§7 §a✓ "
|
||
+ qt.getProgress(
|
||
p.getName(),
|
||
cond));
|
||
} else {
|
||
p.sendMessage("§7 §c✗ "
|
||
+ qt.getProgress(
|
||
p.getName(),
|
||
cond));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
p.sendMessage("");
|
||
if (finishedCount > 0) {
|
||
p.sendMessage("§7已跳过 "
|
||
+ finishedCount
|
||
+ " 个已完成的阶段");
|
||
}
|
||
if (totalChecked == 0) {
|
||
p.sendMessage(
|
||
"§a§l[打卡] §f没有"
|
||
+ "未完成的条件");
|
||
} else if (totalMet >= totalChecked) {
|
||
p.sendMessage(
|
||
"§a§l[打卡] §f全部通过!("
|
||
+ totalMet + "/"
|
||
+ totalChecked + ")");
|
||
playSuccessSound(p);
|
||
} else {
|
||
p.sendMessage(
|
||
"§c§l[打卡] §f未通过 ("
|
||
+ totalMet + "/"
|
||
+ totalChecked
|
||
+ ") §c请继续完成");
|
||
}
|
||
|
||
gui.openTaskCenter(p);
|
||
}
|
||
|
||
|
||
|
||
@EventHandler
|
||
public void onInvDrag(InventoryDragEvent e) {
|
||
if (!(e.getWhoClicked() instanceof Player))
|
||
return;
|
||
Player p = (Player) e.getWhoClicked();
|
||
if (isFrozen(p)) {
|
||
frozenFlightPlayers.add(p.getName());
|
||
e.setCancelled(true);
|
||
return;
|
||
}
|
||
String t = e.getView().getTitle();
|
||
|
||
// 回收站拖拽
|
||
if (t.equals("§6§l垃圾回收站")) {
|
||
for (int rawSlot :
|
||
e.getRawSlots()) {
|
||
if (rawSlot >= 49) {
|
||
e.setCancelled(true);
|
||
return;
|
||
}
|
||
}
|
||
// 0-48允许拖入,不取消
|
||
// 延迟2tick后扫描格子
|
||
final Player fp = p;
|
||
Bukkit.getScheduler()
|
||
.runTaskLater(this,
|
||
() -> {
|
||
Inventory top = fp
|
||
.getOpenInventory()
|
||
.getTopInventory();
|
||
for (int rawSlot :
|
||
e.getRawSlots()) {
|
||
if (rawSlot < 0
|
||
|| rawSlot >= 49)
|
||
continue;
|
||
ItemStack item =
|
||
top.getItem(rawSlot);
|
||
if (item != null
|
||
&& item.getType()
|
||
!= Material.AIR
|
||
&& !isMenuSnowball(
|
||
item)) {
|
||
garbage.saveItem(
|
||
item.clone());
|
||
top.setItem(
|
||
rawSlot, null);
|
||
}
|
||
}
|
||
}, 2L);
|
||
return;
|
||
}
|
||
|
||
//服务商工单系统
|
||
if (t.equals(TicketManager.T_TICKET_MAIN)
|
||
|| t.equals(
|
||
TicketManager.T_TICKET_CREATE)
|
||
|| 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);
|
||
}
|
||
}
|
||
|
||
// ===== Commands =====
|
||
@Override
|
||
public boolean onCommand(CommandSender sender,
|
||
Command cmd, String label,
|
||
String[] args) {
|
||
|
||
String cmdName = cmd.getName().toLowerCase();
|
||
|
||
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 (cmd.getName().equalsIgnoreCase("radio")) {
|
||
getLogger().info(
|
||
"[Radio] 收到命令: "
|
||
+ Arrays.toString(args));
|
||
|
||
// 统一交给 RadioManager 处理
|
||
radio.handleCommand(sender, args);
|
||
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;
|
||
}
|
||
// /reg
|
||
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);
|
||
} catch (Exception e) {
|
||
getLogger().severe(
|
||
"[Sdf1_login] reg命令异常: "
|
||
+ e.getMessage());
|
||
e.printStackTrace();
|
||
p.sendMessage("§c注册出错,请联系管理员");
|
||
}
|
||
// 注册/登录成功后统一处理
|
||
if (loggedIn.contains(p.getName())) {
|
||
frozenFlightPlayers.remove(p.getName());
|
||
p.setAllowFlight(false);
|
||
p.setFlying(false);
|
||
// 仅关闭冻结期间的飞行
|
||
if (frozenFlightPlayers.remove(
|
||
p.getName())) {
|
||
p.setAllowFlight(false);
|
||
p.setFlying(false);
|
||
removeFlightOnce.add(p.getName());
|
||
if (questTracker != null) {
|
||
questTracker.markConditionComplete(
|
||
p.getName(),
|
||
"完成注册登录");
|
||
}
|
||
this.getWelcome().onLogin(p);
|
||
restoreInventory(p);
|
||
giveMenuSnowball(p);
|
||
}
|
||
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())) {
|
||
frozenFlightPlayers.remove(p.getName());
|
||
p.setAllowFlight(false);
|
||
p.setFlying(false);
|
||
removeFlightOnce.add(p.getName());
|
||
p.sendMessage("§c您已登录");
|
||
return true;
|
||
}
|
||
try {
|
||
loginMgr.handleLogin(p, args);
|
||
} catch (Exception e) {
|
||
getLogger().severe(
|
||
"[Sdf1_login] login命令异常: "
|
||
+ e.getMessage());
|
||
e.printStackTrace();
|
||
p.sendMessage("§c登录出错,请联系管理员");
|
||
}
|
||
// 登录成功后统一处理
|
||
if (loggedIn.contains(p.getName())) {
|
||
if (questTracker != null) {
|
||
questTracker.markConditionComplete(
|
||
p.getName(),
|
||
"完成注册登录");
|
||
}
|
||
this.getWelcome().onLogin(p);
|
||
restoreInventory(p);
|
||
giveMenuSnowball(p);
|
||
}
|
||
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;
|
||
}
|
||
|
||
|
||
// /sdf1debug
|
||
if (cmdName.equals("sdf1debug")) {
|
||
if (!(sender instanceof Player)) {
|
||
sender.sendMessage("§c仅玩家可用");
|
||
return true;
|
||
}
|
||
Player p2 = (Player) sender;
|
||
if (!isAdmin(sender)) {
|
||
p2.sendMessage("§c权限不足");
|
||
return true;
|
||
}
|
||
if (args.length < 2) {
|
||
p2.sendMessage("§e用法: /sdf1debug <玩家> <阶段>");
|
||
return true;
|
||
}
|
||
String tgt = args[0];
|
||
try {
|
||
int stage = Integer.parseInt(args[1]);
|
||
db.setField(tgt, "gift_stage", stage);
|
||
p2.sendMessage("§a已将 " + tgt
|
||
+ " 的任务阶段设为 " + stage);
|
||
} catch (NumberFormatException ex) {
|
||
p2.sendMessage("§c请输入有效数字");
|
||
}
|
||
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;
|
||
}
|
||
|
||
String sub = args[0].toLowerCase();
|
||
// update - 检查更新(控制台+玩家均可)
|
||
if (sub.equals("update")) {
|
||
if (!isAdmin(sender)) {
|
||
sender.sendMessage("§c权限不足");
|
||
return true;
|
||
}
|
||
sender.sendMessage("§e[更新] 正在检测...");
|
||
Bukkit.getScheduler()
|
||
.runTaskAsynchronously(this,
|
||
() -> updateChecker
|
||
.checkUpdate(sender));
|
||
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();
|
||
sender.sendMessage("§a配置已重载!");
|
||
return true;
|
||
}
|
||
// back - 恢复背包备份
|
||
if (sub.equals("back")) {
|
||
if (!isAdmin(sender)) {
|
||
sender.sendMessage("§c权限不足");
|
||
return true;
|
||
}
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§e用法: /sdf1_login back <玩家> [#编号]");
|
||
return true;
|
||
}
|
||
String backTarget = args[1];
|
||
if (args.length >= 3
|
||
&& args[2].startsWith("#")) {
|
||
// /sdf1_login back Aplayer #1
|
||
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 {
|
||
// /sdf1_login back Aplayer
|
||
// 罗列最近5条备份
|
||
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恢复");
|
||
sender.sendMessage(
|
||
"§e§l================================");
|
||
}
|
||
}
|
||
|
||
// 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());
|
||
}
|
||
frozenFlightPlayers.remove(p2.getName());
|
||
p2.setAllowFlight(false);
|
||
p2.setFlying(false);
|
||
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;
|
||
}
|
||
|
||
// 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 (!PasswordUtils.validate(tempPwd)) {
|
||
sender.sendMessage("§c密码格式不符合要求");
|
||
return true;
|
||
}
|
||
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);
|
||
sender.sendMessage("§a已为 " + tgt
|
||
+ " 设置临时密码");
|
||
Player tp = Bukkit.getPlayer(tgt);
|
||
if (tp != null && tp.isOnline())
|
||
tp.kickPlayer(
|
||
"§c管理员已设置临时密码");
|
||
// 邮件通知
|
||
String emailAddr = (String) db.getField(
|
||
tgt, "email");
|
||
if (emailAddr != null
|
||
&& !emailAddr.isEmpty()) {
|
||
String time =
|
||
new java.text.SimpleDateFormat(
|
||
"yyyy-MM-dd HH:mm:ss")
|
||
.format(new java.util.Date());
|
||
String body = "玩家 " + tgt
|
||
+ ":\n您的密码已于 " + time
|
||
+ " 被管理员重置为临时密码。\n"
|
||
+ "如果是本人操作请忽略。\n"
|
||
+ "如非本人操作请立即联系管理员。";
|
||
final String to = emailAddr;
|
||
final String fName = tgt;
|
||
final String bodyF = body;
|
||
Bukkit.getScheduler()
|
||
.runTaskAsynchronously(this,
|
||
() -> email
|
||
.sendTempPassword(
|
||
to, fName,
|
||
bodyF));
|
||
}
|
||
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;
|
||
}
|
||
|
||
// del
|
||
if (sub.equals("del")) {
|
||
if (!isAdmin(sender)) {
|
||
sender.sendMessage("§c权限不足");
|
||
return true;
|
||
}
|
||
if (args.length < 2) {
|
||
sender.sendMessage(
|
||
"§e用法: /sdf1_login del <玩家>");
|
||
return true;
|
||
}
|
||
db.deleteUser(args[1]);
|
||
sender.sendMessage("§a已删除 " + args[1]);
|
||
Player tp = Bukkit.getPlayer(args[1]);
|
||
if (tp != null && tp.isOnline())
|
||
tp.kickPlayer("§c账号已被管理员删除");
|
||
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;
|
||
}
|
||
/**
|
||
* 是否是垃圾站展示物品(带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 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","update"));
|
||
} else {
|
||
list.addAll(Arrays.asList(
|
||
"pw", "email", "sign",
|
||
"reset", "undo",
|
||
"ticket", "oa", "stop"));
|
||
}
|
||
}
|
||
if (cmd.getName().equalsIgnoreCase("chat")
|
||
&& args.length == 1
|
||
&& isAdmin(sender)) {
|
||
list.addAll(Arrays.asList(
|
||
"reload", "add", "remove",
|
||
"addplayer", "takeplayer",
|
||
"unmute", "reset"));
|
||
}
|
||
return list;
|
||
}
|
||
|
||
} |