diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 9df8765..9b58390 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -7,6 +7,8 @@ on: jobs: build: runs-on: self-hosted + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -14,13 +16,8 @@ jobs: - name: Setup JDK 25 (Temurin) uses: actions/setup-java@v4 with: - java-version: '25' - distribution: 'temurin' - - - name: Verify Java - run: | - echo "JAVA_HOME=$JAVA_HOME" - java -version + java-version: "25" + distribution: "temurin" - name: Build with Maven run: mvn -B clean package -DskipTests @@ -33,29 +30,26 @@ jobs: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN }} run: | set -e - # 从 plugin.yml 提取版本号作为标签 - VERSION=$(grep '^version:' src/main/resources/plugin.yml | awk '{print $2}' | tr -d '\r\n ') + VERSION=$(grep "^version:" src/main/resources/plugin.yml | awk "{print \$2}" | tr -d "\r\n ") TAG="v${VERSION}" BASE="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}" echo "Version: ${VERSION}, Tag: ${TAG}" echo "Token prefix: ${GITEA_TOKEN:0:8}..." - # 测试 token 权限 echo "=== Testing API access ===" curl -s -w "\nHTTP_CODE:%{http_code}\n" \ -H "Authorization: token $GITEA_TOKEN" \ "${BASE}" | tail -5 - # 如果同版本标签已存在,先删除旧 release echo "=== Checking existing tag ${TAG} ===" RESP=$(curl -s -w "\nHTTP_CODE:%{http_code}" \ -H "Authorization: token $GITEA_TOKEN" \ "${BASE}/releases/tags/${TAG}") - HTTP_CODE=$(echo "$RESP" | grep -o 'HTTP_CODE:[0-9]*' | cut -d: -f2) + HTTP_CODE=$(echo "$RESP" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2) echo "Check tag HTTP: ${HTTP_CODE}" if [ "$HTTP_CODE" = "200" ]; then - OLD_ID=$(echo "$RESP" | head -1 | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) + OLD_ID=$(echo "$RESP" | head -1 | grep -o "\"id\":[0-9]*" | head -1 | cut -d: -f2) echo "Tag ${TAG} exists (release #${OLD_ID}), deleting..." curl -s -X DELETE \ -H "Authorization: token $GITEA_TOKEN" \ @@ -70,14 +64,13 @@ jobs: echo "Warning: unexpected response checking tag" fi - # 创建新 release echo "=== Creating release ${TAG} ===" RELEASE_RESP=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ - -d "{\"tag_name\":\"${TAG}\",\"name\":\"Sdf1_login ${TAG}\",\"body\":\"Version: ${VERSION}\\nCommit: ${{ gitea.sha }}\\nBranch: ${{ gitea.ref_name }}\",\"draft\":false,\"prerelease\":false}" \ + -d "{\"tag_name\":\"${TAG}\",\"name\":\"Sdf1_login ${TAG}\",\"body\":\"Version: ${VERSION}\\\\nCommit: \${{ gitea.sha }}\\\\nBranch: \${{ gitea.ref_name }}\",\"draft\":false,\"prerelease\":false}" \ "${BASE}/releases") - RELEASE_HTTP=$(echo "$RELEASE_RESP" | grep -o 'HTTP_CODE:[0-9]*' | cut -d: -f2) + RELEASE_HTTP=$(echo "$RELEASE_RESP" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2) echo "Create release HTTP: ${RELEASE_HTTP}" if [ "$RELEASE_HTTP" != "201" ]; then @@ -86,15 +79,14 @@ jobs: exit 1 fi - # 上传 JAR 资产 - RELEASE_ID=$(echo "$RELEASE_RESP" | head -1 | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) + RELEASE_ID=$(echo "$RELEASE_RESP" | head -1 | grep -o "\"id\":[0-9]*" | head -1 | cut -d: -f2) if [ -n "$RELEASE_ID" ]; then echo "=== Uploading JAR to release #${RELEASE_ID} ===" curl -s -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -F "attachment=@target/Sdf1_login.jar" \ "${BASE}/releases/${RELEASE_ID}/assets" - echo "✅ Release ${TAG} created with JAR" + echo "Release ${TAG} created with JAR" else - echo "⚠️ Release created but could not get release ID for JAR upload" + echo "Release created but could not get release ID for JAR upload" fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99df2e7..ea868ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,4 +36,4 @@ jobs: tag_name: v${{ steps.version.outputs.version }} name: v${{ steps.version.outputs.version }} files: target/Sdf1_login.jar - generate_release_notes: true + generate_release_notes: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4b522b0..03c873f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ build/ .DS_Store # 开发辅助文件(本地保留,不推送到仓库) +/代码关联关系.md txt/ *.xlsx *.patch @@ -92,3 +93,10 @@ lpt8 lpt9hs_err_pid*.log hs_err_pid*.log replay_pid*.log + +# 屏蔽patch文件 +*.patch +# 屏蔽wb产生的文件 +.workbuddy/ +# 屏蔽备份文件 +backup/ \ No newline at end of file diff --git a/README.md b/README.md index 5d6922b..03157f3 100644 --- a/README.md +++ b/README.md @@ -610,3 +610,5 @@ http-port=443 - GitHub:[https://github.com/ypsdf1/sdf1_login](https://github.com/ypsdf1/sdf1_login) - Gitee:[https://gitee.com/nihaoshidifu/sdf1_login](https://gitee.com/nihaoshidifu/sdf1_login) - gitcode: [https://gitcode.com/ypsdf1/Sdf1_login](https://gitcode.com/ypsdf1/Sdf1_login) +trigger +# test diff --git a/backup/20260605_152529/.gitattributes b/backup/20260605_152529/.gitattributes deleted file mode 100644 index d49c874..0000000 --- a/backup/20260605_152529/.gitattributes +++ /dev/null @@ -1,5 +0,0 @@ -*.md text=auto encoding=UTF-8 -*.png binary -*.jpg binary -*.jpeg binary -*.gif binary \ No newline at end of file diff --git a/backup/20260605_152529/.gitignore b/backup/20260605_152529/.gitignore deleted file mode 100644 index fe405f6..0000000 --- a/backup/20260605_152529/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ -.kotlin - -### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ -*.iws -*.iml -*.ipr - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store - -# 开发辅助文件(本地保留,不推送到仓库) -txt/ -*.xlsx -*.patch -新建*.txt -群戏归类.txt \ No newline at end of file diff --git a/backup/20260605_152529/AreaProtection.java.backup b/backup/20260605_152529/AreaProtection.java.backup deleted file mode 100644 index bc53d53..0000000 --- a/backup/20260605_152529/AreaProtection.java.backup +++ /dev/null @@ -1,4907 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.event.block.BlockBurnEvent; -import org.bukkit.event.block.BlockIgniteEvent; -import org.bukkit.event.block.BlockSpreadEvent; -import org.bukkit.entity.Entity; -import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import java.sql.*; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.GameMode; -import org.bukkit.event.EventPriority; - - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; -import java.util.Iterator; -import java.util.Set; -import java.util.HashSet; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.event.entity.EntitySpawnEvent; -import org.bukkit.entity.Item; - - - - -public class AreaProtection implements Listener { - - private final Main plugin; - public List giveEffects = new ArrayList<>(); - private final Map> playerAreas - = new HashMap<>(); - private final Map areas = - new ConcurrentHashMap<>(); - private Connection dbConnection; - - // 全局白名单(玩家) - private final Set globalPlayerWhitelist = - ConcurrentHashMap.newKeySet(); - // 区域白名单 key=区域名 value=玩家名集合 - private final Map> areaPlayerWhitelist = - new ConcurrentHashMap<>(); - // 全局黑名单(物品ID) - private final Set globalItemBlacklist = - ConcurrentHashMap.newKeySet(); - // 区域黑名单 - private final Map> areaItemBlacklist = - new ConcurrentHashMap<>(); - - // 选地 - private static final Material WAND = Material.BREEZE_ROD; - - private final Map pos1 = - new ConcurrentHashMap<>(); - private final Map pos2 = - new ConcurrentHashMap<>(); - private final Set selecting = - ConcurrentHashMap.newKeySet(); - public boolean denyRaid = false; - // ===== 统一文件夹 ===== - private final File rootDir; - private final File whitelistDir; - private final File globalWhitelistFile; - private final File oldDbFile; - private final File newDbFile; - - // 边框显示 - private final Map displayTaskIds = - new ConcurrentHashMap<>(); - private final Map> globalWhitelist - = new HashMap<>(); - private final Map> areaWhitelist - = new HashMap<>(); - private final Map protectedEntities - = new ConcurrentHashMap<>(); - // 区域给予的效果记录(贴标) - private final Map> playerAppliedEffects = new HashMap<>(); - // 延时清理任务 - private static final long MOVE_DEBOUNCE_MS = 100; - // 新:全部改为线程安全 - private final Map playerCurrentArea - = new ConcurrentHashMap<>(); - private final Map> playerMarkedEffects - = new ConcurrentHashMap<>(); - private final Map pendingClearTask - = new ConcurrentHashMap<>(); - private final Map pendingClearArea - = new ConcurrentHashMap<>(); - private final Map lastMoveProcess - = new ConcurrentHashMap<>(); - // 防止传送时重复处理 - private final Set teleporting - = ConcurrentHashMap.newKeySet(); - // 待清理的效果列表(与延时任务配合) - private final Map> pendingClearEffects - = new ConcurrentHashMap<>(); - - - -// ==================== 强制游戏模式 ==================== - - /** - * 监听玩家尝试切换游戏模式 - * 如果在强制游戏模式区域且不在豁免名单中,则取消切换 - */ - // 防止递归的标记 - private final Set modeSwitching - = ConcurrentHashMap.newKeySet(); - @EventHandler(priority = EventPriority.HIGHEST) - public void onGameModeChange( - PlayerGameModeChangeEvent event) { - if (event.isCancelled()) return; - - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - if (modeSwitching.contains(uid)) return; - - String areaName = playerCurrentArea.get(uid); - if (areaName == null) return; - - AreaConfig ac = areas.get(areaName); - if (ac == null) return; - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) return; - if (isExemptFromGameMode(p, areaName)) return; - - event.setCancelled(true); - - modeSwitching.add(uid); - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } finally { - modeSwitching.remove(uid); - } - - // ★ 切换后发一次中文提示 - p.sendMessage(formatAreaMsg( - "&c该区域强制 " + ac.enforceGameMode - + " 模式,无法切换")); - } - - public Set getAlreadyForced() { - return alreadyForced; - } - - - // ==================== 定时强制游戏模式 ==================== - - private BukkitTask enforceTask; - - /** - * 启动定时强制任务 - * 每秒检查一次所有在线玩家 - */ - private BukkitTask enforceTask1; - - // 记录上次强制模式的玩家(避免重复发消息) - private final Set alreadyForced - = ConcurrentHashMap.newKeySet(); - - public void startEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - } - enforceTask = new BukkitRunnable() { - @Override - public void run() { - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String areaName = - playerCurrentArea.get(uid); - if (areaName == null) { - // 不在任何区域,清除标记 - alreadyForced.remove(uid); - continue; - } - - AreaConfig ac = areas.get(areaName); - if (ac == null) continue; - if (ac.enforceGameMode == null - || ac.enforceGameMode - .isEmpty()) continue; - if (isExemptFromGameMode( - p, areaName)) { - alreadyForced.remove(uid); - continue; - } - - GameMode target = - GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - modeSwitching.add(uid); - try { - p.setGameMode(target); - } finally { - modeSwitching.remove(uid); - } - - // ★ 只在玩家已被强制过时才发消息 - // 首次不发,后续才提醒 - if (alreadyForced.contains(uid)) { - p.sendMessage(formatAreaMsg( - "&c该区域强制 " - + ac.enforceGameMode - + " 模式,无法切换")); - } - alreadyForced.add(uid); - } else { - // 模式一致,清除标记 - alreadyForced.remove(uid); - } - } - } - }.runTaskTimer(plugin, 20L, 20L); - } - - public void stopEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - enforceTask = null; - } - } - - - - /** - * 检查玩家是否豁免于游戏模式强制 - * 豁免条件:全局白名单 / 区域白名单 / 模式排除名单 - */ - private boolean isExemptFromGameMode( - Player p, String areaName) { - String name = p.getName(); - - /* if (p.isOp()) { - plugin.getLogger().info( - "[防护-调试] 豁免原因: OP, 玩家=" + name); - return true; - }*/ - if (globalPlayerWhitelist.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 全局白名单, 玩家=" - + name);*/ - return true; - } - Set aw = - areaPlayerWhitelist.get(areaName); - if (aw != null && aw.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 区域白名单, 玩家=" - + name + " 区域=" + areaName);*/ - return true; - } - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 模式排除名单, 玩家=" - + name);*/ - return true; - } - /* plugin.getLogger().info( - "[防护-调试] 无豁免, 玩家=" + name);*/ - return false; - } - - /** - * 玩家进入区域时,如果该区域强制游戏模式, - * 则立即设置(排除豁免玩家) - */ - private void applyEnforcedGameMode(Player p, - String areaName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: ac=null, area=" - + areaName);*/ - return; - } - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: 未配置强制模式, area=" - + areaName);*/ - return; - } - - boolean exempt = isExemptFromGameMode( - p, areaName); - /* plugin.getLogger().info( - "[防护-调试] applyMode: 玩家=" + p.getName() - + " 区域=" + areaName - + " 强制=" + ac.enforceGameMode - + " 当前=" + p.getGameMode() - + " 豁免=" + exempt - + " OP=" + p.isOp() - + " 全白=" + globalPlayerWhitelist - .contains(p.getName()));*/ - - if (exempt) return; - - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - /* plugin.getLogger().info( - "[防护] 强制切换 " + p.getName() - + " → " + ac.enforceGameMode);*/ - } - } catch (Exception e) { - /* plugin.getLogger().warning( - "[防护] 强制模式失败: " - + e.getMessage());*/ - } - } - - public AreaProtection(Main plugin) { - this.plugin = plugin; - - // 统一根目录 - rootDir = new File(plugin.getDataFolder(), "区域防护"); - if (!rootDir.exists()) rootDir.mkdirs(); - - // 白名单子目录 - whitelistDir = new File(rootDir, "whitelists"); - if (!whitelistDir.exists()) whitelistDir.mkdirs(); - globalWhitelistFile = new File( - whitelistDir, "全局白名单.txt"); - - // 数据库迁移 - oldDbFile = new File( - plugin.getDataFolder(), "area_effects.db"); - newDbFile = new File(rootDir, "area_effects.db"); - migrateDatabase(); - - // 初始化 - initDatabase(); - writeDefaultConfig(); - loadAllAreas(); - loadWhitelists(); - recoverPendingEffects(); - } - public void recoverPendingEffects() { - try { - // 1. 遍历所有在线玩家(而不是遍历DB记录) - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String playerName = p.getName(); - - // 2. 获取玩家当前所在区域 - AreaConfig currentAc = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - - // 3. 获取该玩家在DB中的所有历史记录 - List recordedAreas = - getPlayerAreas(uid); - - // 4. 清理不在当前区域的旧记录 - for (String recordedArea : recordedAreas) { - if (currentAc == null - || !currentAc.name - .equals(recordedArea)) { - // 玩家不在这个区域了 → 清DB - removePlayerEffects( - uid, recordedArea); - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在" + recordedArea - + ", 清理旧记录"); - } - } - - // 5. 如果在区域中 → 恢复/给予效果 - if (currentAc != null) { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 在" + currentAc.name - + ", 恢复效果"); - applyRegionEffects(p, uid, - currentAc.name, currentAc); - } else { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在任何区域, 跳过"); - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 恢复效果失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - // 取消延时清理任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 清标记 - playerMarkedEffects.remove(uid); - playerCurrentArea.remove(uid); - } - - -// ==================== 数据库迁移 ==================== - - /** - * 如果旧位置有 db 文件,移到新位置 - * 如果新位置已有,不动 - * 如果旧位置也没有,在新位置创建 - */ - - private void migrateDatabase() { - if (newDbFile.exists()) { - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - oldDbFile.delete(); - } - return; - } - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - try { - java.nio.file.Files.move( - oldDbFile.toPath(), - newDbFile.toPath(), - java.nio.file.StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 迁移数据库失败: " - + e.getMessage()); - } - } - } - - - // 保存玩家进入区域时给予的效果 - private void savePlayerEffects(UUID uid, String playerName, - String areaName, List effects) { - try { - // 先删除该区域旧记录,避免重复 - PreparedStatement del = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - del.setString(1, uid.toString()); - del.setString(2, areaName); - del.executeUpdate(); - del.close(); - - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)"); - long now = System.currentTimeMillis(); - for (PotionEffectType type : effects) { - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, type.getName()); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, now); - ps.addBatch(); - } - ps.executeBatch(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 保存效果失败: " + e.getMessage()); - } - } - - // 查询玩家在某个区域获得的效果 - private List getPlayerEffects( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - PotionEffectType type = PotionEffectType - .getByName(rs.getString("effect_type")); - if (type != null) list.add(type); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - // 删除玩家在某个区域的效果记录 - private void removePlayerEffects(UUID uid, String areaName) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除效果失败: " + e.getMessage()); - } - } - - // 删除玩家所有效果记录 - private void removeAllPlayerEffects(UUID uid) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除全部效果失败: " + e.getMessage()); - } - } - - // 获取玩家所有记录的区域 - private List getPlayerAreas(UUID uid) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT DISTINCT area_name " - + "FROM player_effects WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("area_name")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询区域失败: " + e.getMessage()); - } - return list; - } - - // 查询所有待处理的效果(跨服重启也能恢复) - private List getAllPendingEffects() { - List list = new ArrayList<>(); - try { - Statement stmt = dbConnection.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT uuid, player_name, area_name, " - + "effect_type FROM player_effects"); - while (rs.next()) { - list.add(new String[]{ - rs.getString("uuid"), - rs.getString("player_name"), - rs.getString("area_name"), - rs.getString("effect_type")}); - } - rs.close(); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询全部效果失败: " + e.getMessage()); - } - return list; - } - - - private void writeDefaultConfig() { - File f = new File(rootDir, "末地保护区.txt"); - if (f.exists()) return; - - String endWorld = "world_the_end"; - for (org.bukkit.World w : Bukkit.getWorlds()) { - if (w.getEnvironment() - == org.bukkit.World.Environment.THE_END) { - endWorld = w.getName(); - break; - } - } - - final String ew = endWorld; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 末地保护区"); - pw.println("起点: " + ew + ",-30000000,-30000000"); - pw.println("终点: " + ew + ",30000000,30000000"); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("没收物品: BROWN_MUSHROOM,RED_MUSHROOM,MUSHROOM_STEW"); - pw.println("没收提示: 你携带了违禁品「蘑菇」,已被全部没收!"); - pw.println("通报批评: {player} 在末地违规携带蘑菇!"); - pw.println(); - pw.println("禁止放置方块"); - pw.println("禁止破坏方块"); - pw.println("禁止PVP"); - pw.println("禁止袭击"); - pw.println(); - pw.println("效果: 夜视 1 999"); - pw.println("进入提示: 末地保护区 - 请遵守规则"); - pw.println("离开提示: 已离开末地保护区"); - pw.close(); - plugin.getLogger().info("[防护] 生成末地配置, 世界: " + ew); - } catch (IOException ignored) { - } - } - - public void reload() { - loadAllAreas(); - loadWhitelists(); - // 强制输出醒目的中文日志 - plugin.getLogger().info("========================================"); - plugin.getLogger().info("[防护] 已重载 " + areas.size() + " 个区域"); - plugin.getLogger().info("[防护] 已重载白名单"); - plugin.getLogger().info("========================================"); - } - -// ==================== 消息格式化 ==================== - - /** - * 格式化区域消息 - * 1. & → §(颜色符号转换) - * 2.
和 \n → 换行 - * 3. 内容 → §l内容§r(加粗) - * 4. 自动添加 [区域防护] 前缀 - */ - private String formatAreaMsg(String raw) { - if (raw == null || raw.isEmpty()) return ""; - - String msg = raw; - - // & → § - for (int i = 0; i <= 9; i++) { - msg = msg.replace("&" + i, "§" + i); - } - for (char c : "abcdefklnor".toCharArray()) { - msg = msg.replace("&" + c, "§" + c); - } - - // 换行 - msg = msg.replace("
", "\n"); - msg = msg.replace("\\n", "\n"); - - // HTML → Minecraft格式(栈式解析,支持嵌套) - msg = convertHtmlTags(msg); - - // 前缀 - msg = "§c§l【区域防护】§r " + msg; - - return msg; - } - - /** - * 栈式HTML标签解析 - * 处理 text rest 这样的嵌套标签 - * 内层关闭时重新应用外层格式 - */ - private String convertHtmlTags(String msg) { - StringBuilder sb = new StringBuilder(); - java.util.Deque stack = - new ArrayDeque<>(); - int i = 0; - - while (i < msg.length()) { - if (msg.charAt(i) == '<' - && i + 1 < msg.length()) { - if (msg.charAt(i + 1) == '/') { - // 关闭标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 2, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0 && !stack.isEmpty() - && stack.peek() == code) { - stack.pop(); - if (!stack.isEmpty()) { - // 重置后重新应用外层格式 - sb.append("§r"); - for (char c : stack) { - sb.append("§").append(c); - } - } else { - sb.append("§r"); - } - } - i = end + 1; - } else { - // 开放标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 1, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0) { - stack.push(code); - sb.append("§").append(code); - } - i = end + 1; - } - } else { - sb.append(msg.charAt(i)); - i++; - } - } - return sb.toString(); - } - - private char htmlToMc(String tag) { - switch (tag) { - case "b": return 'l'; - case "i": return 'o'; - case "u": return 'n'; - case "s": return 'm'; - default: return 0; - } - } - -// =============== 加载 ==================== - - public void loadAllAreas() { - areas.clear(); - File[] files = rootDir.listFiles( - (File d, String n) -> n.endsWith(".txt")); - if (files == null) return; - for (File f : files) { - try { - String name = f.getName() - .replace(".txt", ""); - AreaConfig ac = parseArea(name, f); - areas.put(name, ac); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载失败: " + f.getName()); - } - } - } - - - private void initDatabase() { - try { - dbConnection = DriverManager.getConnection( - "jdbc:sqlite:" + newDbFile.getAbsolutePath()); - Statement stmt = dbConnection.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS player_effects (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "uuid TEXT NOT NULL," - + "player_name TEXT," - + "area_name TEXT NOT NULL," - + "effect_type TEXT NOT NULL," - + "effect_level INTEGER DEFAULT 0," - + "effect_duration INTEGER DEFAULT 999," - + "enter_time INTEGER NOT NULL)"); - stmt.executeUpdate( - "CREATE INDEX IF NOT EXISTS idx_uuid " - + "ON player_effects(uuid)"); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().severe( - "[防护] 数据库初始化失败: " - + e.getMessage()); - } - } - - -// ==================== 效果名称中英文映射 ==================== - - /** - * 将中文效果名转为 Bukkit 能识别的英文名 - */ - private String resolveEffectName(String name) { - if (name == null) return null; - String n = name.trim(); - // 如果已经是英文或命名空间格式,直接用 - if (n.contains(":") - || n.equals(n.toUpperCase())) { - return n; - } - // 中文 → 英文映射 - switch (n) { - case "伤害吸收": return "ABSORPTION"; - case "生命提升": return "HEALTH_BOOST"; - case "抗性提升": return "DAMAGE_RESISTANCE"; - case "抗火": return "FIRE_RESISTANCE"; - case "夜视": return "NIGHT_VISION"; - case "力量": return "INCREASE_DAMAGE"; - case "速度": return "SPEED"; - case "急迫": return "FAST_DIGGING"; - case "跳跃提升": return "JUMP"; - case "再生": return "REGENERATION"; - case "生命恢复": return "REGENERATION"; - case "治疗": return "HEAL"; - case "瞬间治疗": return "HEAL"; - case "伤害药水": return "HARM"; - case "瞬间伤害": return "HARM"; - case "虚弱": return "WEAKNESS"; - case "中毒": return "POISON"; - case "缓慢": return "SLOW"; - case "挖掘疲劳": return "SLOW_DIGGING"; - case "失明": return "BLINDNESS"; - case "隐身": return "INVISIBILITY"; - case "发光": return "GLOWING"; - // 村庄英雄 - case "村庄英雄": return "HERO_OF_THE_VILLAGE"; - case "幸运": return "LUCK"; - case "不幸": return "UNLUCK"; - case "水下呼吸": return "WATER_BREATHING"; - case "水下速掘": return "DOLPHINS_GRACE"; - case "缓降": return "LEVITATION"; - case "漂浮": return "LEVITATION"; - case "饱和": return "SATURATION"; - // 无效效果 - case "伤害": return "HARM"; - case "治疗药水": return "HEAL"; - default: - // 尝试直接用英文匹配 - return n; - } - } - - /** - * 全量容错:把全角字符统一转半角, - * 去掉零宽字符/不可见字符, - * 让用户怎么写都能正确解析。 - */ - private String normalizeLine(String input) { - if (input == null || input.isEmpty()) return ""; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - - // 跳过零宽字符和不可见字符 - if (c == '\u200B' // 零宽空格 - || c == '\u200C' // 零宽非连接 - || c == '\u200D' // 零宽连接 - || c == '\uFEFF' // BOM - || c == '\u00A0' // 不间断空格 - || c == '\u2028' // 行分隔符 - || c == '\u2029' // 段分隔符 - || c == '\u200E' // LTR标记 - || c == '\u200F' // RTL标记 - || c == '\u2060' // 字连接符 - || (c >= '\u2000' && c <= '\u200A')) { // 各种空格 - continue; - } - - // 全角→半角映射 - if (c >= '\uFF01' && c <= '\uFF5E') { - c = (char) (c - 0xFEE0); - } - - // 全角空格→半角空格 - if (c == '\u3000') { - c = ' '; - } - - sb.append(c); - } - - return sb.toString(); - } - - - private AreaConfig parseArea(String name, File f) - throws IOException { - AreaConfig ac = new AreaConfig(); - ac.name = name; - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean inBlockComment = false; - boolean inHtmlComment = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty()) continue; -// ===== 全量容错预处理:全角→半角,去零宽字符 ===== - line = normalizeLine(line); - if (line.isEmpty()) continue; -// ===== 暴力调试:输出所有包含关键词的行 ===== - if (line.indexOf("游戏") >= 0 - || line.indexOf("模式") >= 0 - || line.indexOf("生存") >= 0 - || line.indexOf("创造") >= 0 - || line.indexOf("排除") >= 0 - || line.indexOf("豁免") >= 0 - || line.indexOf("强制") >= 0 - || line.indexOf("GameMode") >= 0 - || line.indexOf("SURVIVAL") >= 0 - || line.indexOf("CREATIVE") >= 0) { - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < line.length(); i++) { - hex.append(String.format("U+%04X ", (int) line.charAt(i))); - } - /* plugin.getLogger().info("[暴力调试] 行=[" + line - + "] 长度=" + line.length() - + " 字符码=" + hex.toString());*/ - } -// ===== 兜底扫描:确保和平白名单一定能解析 ===== - if (line.indexOf("白名单") >= 0 - || line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - int ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf('='); - if (ci < 0) ci = line.indexOf(' '); - - // 和平白名单 - if (line.indexOf("和平") >= 0 - && line.indexOf("白名单") >= 0 - && ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.peaceWhitelist.add(nm); - } - plugin.getLogger().info("[解析兜底] 和平白名单=" - + ac.peaceWhitelist); - } - - // 排除名单 - if (line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - if (ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.modeExempt.add(nm); - } - plugin.getLogger().info("[解析兜底] 排除名单=" - + ac.modeExempt); - } - } - } -// ===== 兜底结束 ===== - - if (inBlockComment) { - int endIdx = line.indexOf("*/"); - if (endIdx >= 0) { - inBlockComment = false; - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("/*")) { - int endIdx = line.indexOf("*/", 2); - if (endIdx >= 0) { - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - inBlockComment = true; - continue; - } - } - if (inHtmlComment) { - int endIdx = line.indexOf("-->"); - if (endIdx >= 0) { - inHtmlComment = false; - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("", 4); - if (endIdx >= 0) { - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - inHtmlComment = true; - continue; - } - } - if (line.startsWith("#") || line.startsWith("//")) { - continue; - } - int hashIdx = line.indexOf("#"); - if (hashIdx > 0) { - line = line.substring(0, hashIdx).trim(); - } - int slashIdx = line.indexOf("//"); - if (slashIdx > 0) { - line = line.substring(0, slashIdx).trim(); - } - if (line.isEmpty()) continue; - - if (line.startsWith("起点:")) { - String[] pp = line.substring("起点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x1 = Integer.parseInt(pp[1].trim()); - ac.z1 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("终点:")) { - String[] pp = line.substring("终点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x2 = Integer.parseInt(pp[1].trim()); - ac.z2 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("高度范围:")) { - String[] pp = line.substring("高度范围:".length()) - .trim().split("-"); - if (pp.length == 2) { - ac.yMin = Integer.parseInt(pp[0].trim()); - ac.yMax = Integer.parseInt(pp[1].trim()); - } - } else if (line.startsWith("没收物品:")) { - for (String s : line.substring("没收物品:".length()) - .trim().split("[,,]")) - ac.confiscateItems.add( - s.trim().toUpperCase()); - } else if (line.startsWith("禁止使用物品:")) { - for (String s : line.substring("禁止使用物品:".length()) - .trim().split("[,,]")) - ac.denyUseItems.add( - s.trim().toUpperCase()); - } else if (line.equals("禁止放置方块")) { - ac.denyBlockPlace = true; - } else if (line.equals("禁止破坏方块")) { - ac.denyBlockBreak = true; - } else if (line.equals("禁止PVP")) { - ac.denyPVP = true; - } else if (line.equals("禁止摔伤")) { - ac.denyFallDamage = true; - } else if (line.equals("禁止饥饿")) { - ac.denyHunger = true; - } else if (line.equals("禁止一切伤害")) { - ac.denyAllDamage = true; - } else if (line.equals("禁止丢弃物品")) { - ac.denyDrop = true; - } else if (line.equals("禁止末影珍珠")) { - ac.denyEnderPearl = true; - } else if (line.equals("禁止使用弓箭")) { - ac.denyBow = true; - } else if (line.equals("禁止骑乘")) { - ac.denyMount = true; - } else if (line.equals("禁止袭击")) { - ac.denyRaid = true; - } else if (line.equals("禁止药水")) { - ac.denyPotion = true; - } else if (line.equals("禁止爆炸")) { - ac.denyExplosion = true; - } else if (line.equals("禁止燃烧") - || line.equals("禁止火焰蔓延")) { - ac.denyFireSpread = true; - } else if (line.equals("清除所有效果") - || line.equals("清理所有效果")) { - ac.clearAllBadEffects = true; - } else if (line.startsWith("清除") - || line.startsWith("清理")) { - String effName = line.substring(2).trim(); - if (effName.endsWith("效果")) - effName = effName.substring(0, - effName.length() - 2); - if (!effName.isEmpty()) - ac.clearEffects.add(effName); - } else if (line.contains("和平模式") - && !line.contains("白名单") - && !line.contains("定时") - && !line.contains("开关")) { - ac.peaceMode = true; - plugin.getLogger().info("[解析] 和平模式已识别"); - - } else if (line.startsWith("效果")) { - String rest = line.substring(2).trim(); - // 去掉前导冒号、空格 - while (rest.length() > 0 - && (rest.charAt(0) == ':' - || rest.charAt(0) == ':' - || rest.charAt(0) == ' ' - || rest.charAt(0) == '\t')) { - rest = rest.substring(1); - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - // 第一个是效果名(可能是中文) - String effName = parts[0]; - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) {} - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) {} - } - - // ★ 用映射方法转为英文名 - String resolved = resolveEffectName(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → " + resolved - + " 等级=" + effLv - + " 时长=" + effDur); - - ac.giveEffects.add(new String[]{ - resolved, - String.valueOf(effLv), - String.valueOf(effDur)}); - // ★ 加日志确认解析结果 - PotionEffectType checkType = - resolveEffectType(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → type=" - + (checkType != null - ? checkType.getName() - : "NULL(失败)") - + " 等级=" + effLv - + " 时长=" + effDur); - - - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) { - } - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) { - } - } - ac.giveEffects.add(new String[]{ - parts[0], - String.valueOf(effLv), - String.valueOf(effDur)}); - } - } else if (line.startsWith("通报批评:")) { - ac.announceTemplate = line.substring( - "通报批评:".length()).trim(); - ac.enableAnnounce = true; - } else if (line.startsWith("没收提示:")) { - ac.confiscateMsg = line.substring( - "没收提示:".length()).trim(); - } else if (line.startsWith("进入提示:")) { - String raw = line.substring("进入提示:".length()).trim(); - ac.enterMsg = raw; - - // ★ 调试:打印原文和Unicode - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★进入提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("离开提示:")) { - String raw = line.substring("离开提示:".length()).trim(); - ac.leaveMsg = raw; - - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★离开提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("惩罚命令:")) { - ac.punishCommands.add(line.substring( - "惩罚命令:".length()).trim()); - } else if (line.startsWith("强制游戏模式:") - || line.startsWith("游戏模式:")) { - String prefix = line.startsWith("强制游戏模式:") - ? "强制游戏模式:" : "游戏模式:"; - String modeStr = line.substring(prefix.length()) - .trim().toUpperCase(); - // 容错:支持多种写法 - if (modeStr.equals("CREATIVE") || modeStr.equals("创造") - || modeStr.equals("1")) { - ac.enforceGameMode = "CREATIVE"; - } else if (modeStr.equals("SURVIVAL") || modeStr.equals("生存") - || modeStr.equals("0")) { - ac.enforceGameMode = "SURVIVAL"; - } else if (modeStr.equals("ADVENTURE") || modeStr.equals("冒险") - || modeStr.equals("2")) { - ac.enforceGameMode = "ADVENTURE"; - } else if (modeStr.equals("SPECTATOR") || modeStr.equals("旁观") - || modeStr.equals("spectator") - || modeStr.equals("3")) { - ac.enforceGameMode = "SPECTATOR"; - } - } else if (line.contains("排除名单") - || line.contains("模式排除") - || line.contains("模式豁免")) { - int colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf('='); - if (colonIdx >= 0) { - String rest = line.substring(colonIdx + 1).trim(); - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playerName = s.trim(); - if (!playerName.isEmpty()) { - ac.modeExempt.add(playerName); - } - } - } - - String rest = line.substring(/*idx*/ + 4).trim(); - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playername = s.trim(); - if (!name.isEmpty()) ac.modeExempt.add(name); - } - } - - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String wlName = s.trim(); - if (!wlName.isEmpty()) { - ac.peaceWhitelist.add(wlName); - } - } - } - - - - } else if (line.equals("禁止交互展示框") - || line.equals("禁止展示框")) { - ac.denyItemFrame = true; - - } - } - br.close(); - return ac; - } -// ===== 和平白名单操作 ===== - - // 添加生物名字到区域和平白名单 - public boolean addPeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.add(creatureName); - savePeaceWhitelist(ac); - return true; - } - - // 从区域和平白名单移除生物名字 - public boolean removePeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.remove(creatureName); - savePeaceWhitelist(ac); - return true; - } - -// ===== 模式排除名单操作 ===== - - - @EventHandler - public void onCreatureSpawn(CreatureSpawnEvent e) { - Entity entity = e.getEntity(); - if (!isHostile(entity.getType().name())) return; - - Location loc = entity.getLocation(); - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - - if (ac == null || !ac.peaceMode) return; - - protectedEntities.put( - entity.getUniqueId(), - System.currentTimeMillis() + 5000); - - /* plugin.getLogger().info("[和平] " - + entity.getType().name() - + " 获得5秒保护期" - + " 区域=" + ac.name - + " 白名单=" + ac.peaceWhitelist - + " 白名单数量=" + ac.peaceWhitelist.size());*/ - } - - - public void cleanupProtectedEntities() { - long now = System.currentTimeMillis(); - int cleaned = 0; - Iterator> it = - protectedEntities.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (now > entry.getValue()) { - it.remove(); - cleaned++; - } - } - if (cleaned > 0) { - /* plugin.getLogger().info("[和平模式] 清理过期保护: " - + cleaned + "个 剩余:" - + protectedEntities.size() + "个");*/ - } - } - - - public void banHostilesWithWhitelist(Player p, AreaConfig ac) { - long now = System.currentTimeMillis(); - // 看看附近到底有什么实体 - List nearby = p.getNearbyEntities(48, 48, 48); - /* plugin.getLogger().info("[和平] " + p.getName() - + " 附近实体总数=" + nearby.size());*/ - for (Entity ent : nearby) { - String typeName = ent.getType().name(); - /* plugin.getLogger().info("[和平] 实体: " - + typeName + " isHostile=" + isHostile(typeName));*/ - } - int scanned = 0; - int waiting = 0; - int saved = 0; - int banished = 0; - - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (!isHostile(ent.getType().name())) continue; - scanned++; - - UUID entUid = ent.getUniqueId(); - Long protectUntil = protectedEntities.get(entUid); - String customName = ent.getCustomName(); - - // 有保护期记录 - if (protectUntil != null) { - long remain = protectUntil - now; - - if (remain > 0) { - // 保护期内,精确剩余秒数 - int secondsLeft = (int) Math.ceil(remain / 1000.0); - waiting++; - - // 保护期内也检查名字(玩家可能刚贴上) - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - protectedEntities.remove(entUid); - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 白名单匹配,永久保留");*/ - continue; - } - - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 保护期剩余约" + secondsLeft + "秒");*/ - continue; - } - - // 保护期到了,最终检查 - protectedEntities.remove(entUid); - /* plugin.getLogger().info("[和平] " - + ent.getType().name() + " 5秒到期");*/ - - // 到期时再查一次白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 到期但白名单匹配,保留");*/ - continue; - } - - // 没名字或不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名=[" + customName + "] 传送虚空");*/ - continue; - } - - // 没有保护期记录(非刷怪蛋生成的或记录被清了) - // 也检查一下白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 无保护期但白名单匹配,保留");*/ - continue; - } - - // 没有保护期且不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 无保护期,命名=[" + customName + "] 传送虚空");*/ - } - - /* plugin.getLogger().info("[和平] " - + " 扫描=" + scanned - + " 等待=" + waiting - + " 保留=" + saved - + " 传送=" + banished - + " 白名单=" + ac.peaceWhitelist);*/ - } - - private static final Set HOSTILE_TYPES = new HashSet<>(Arrays.asList( - "ZOMBIE", "ZOMBIE_VILLAGER", "HUSK", "DROWNED", - "SKELETON", "STRAY", "BOGGED", "WITHER_SKELETON", - "CREEPER", "SPIDER", "CAVE_SPIDER", "ENDERMITE", - "SILVERFISH", "BLAZE", "GHAST", "BIG_GHAST", - "MAGMA_CUBE", "SLIME", "WITCH", - "GUARDIAN", "ELDER_GUARDIAN", - "PILLAGER", "VINDICATOR", "EVOKER", "RAVAGER", - "ILLUSIONER", "VEX", - "PHANTOM", "SHULKER", "WARDEN", "BREEZE", - "PIGLIN", "PIGLIN_BRUTE", "ZOGLIN", - "ZOMBIFIED_PIGLIN", "ENDER_DRAGON" - )); - - private boolean isHostile(String typeName) { - return HOSTILE_TYPES.contains(typeName); - } - - // ==================== 白名单持久化 ==================== - - /** - * 保存白名单到文件 - * 路径:区域防护/whitelists/全局白名单.txt - * 路径:区域防护/whitelists/区域名.txt - */ - public void saveWhitelists() { - try { - // 1. 保存全局白名单 - PrintWriter pwGlobal = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(globalWhitelistFile), - StandardCharsets.UTF_8)); - for (String name : globalPlayerWhitelist) { - pwGlobal.println(name); - } - pwGlobal.close(); - - // 2. 删除旧的区域白名单文件(防止残留) - File[] oldFiles = whitelistDir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (oldFiles != null) { - for (File f : oldFiles) f.delete(); - } - - // 3. 保存各区域白名单 - for (Map.Entry> entry - : areaPlayerWhitelist.entrySet()) { - String name = entry.getKey(); - File areaFile = new File( - whitelistDir, name + ".txt"); - PrintWriter pwArea = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(areaFile), - StandardCharsets.UTF_8)); - for (String playerName : entry.getValue()) { - pwArea.println(playerName); - } - pwArea.close(); - } - - plugin.getLogger().info("[防护] 白名单已保存"); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 保存白名单失败: " - + e.getMessage()); - } - } - - - - // ==================== 区域检测 ==================== - - public AreaConfig getArea(String worldName, - int x, int y, int z) { - for (AreaConfig ac : areas.values()) { - if (ac.world.equalsIgnoreCase(worldName) - && inRect(x, z, ac) - && y >= ac.yMin - && y <= ac.yMax) { - return ac; - } - } - return null; - } - - private boolean inRect(int x, int z, AreaConfig ac) { - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - return x >= minX && x <= maxX - && z >= minZ && z <= maxZ; - } - - // ==================== 白名单检查 ==================== - - private boolean isPlayerWhitelisted(String player, - AreaConfig ac) { - if (globalPlayerWhitelist.contains( - player.toLowerCase())) - return true; - Set areaList = - areaPlayerWhitelist.get(ac.name); - return areaList != null - && areaList.contains(player.toLowerCase()); - } - - private boolean isItemBlacklisted(String itemId, - AreaConfig ac) { - if (globalItemBlacklist.contains( - itemId.toUpperCase())) - return true; - Set areaList = - areaItemBlacklist.get(ac.name); - return areaList != null - && areaList.contains(itemId.toUpperCase()); - } - - // ==================== 没收 ==================== - - private int confiscateItem(Player p, String itemTypeId) { - Material mat = Material.matchMaterial(itemTypeId); - if (mat == null) return 0; - ItemStack[] contents = p.getInventory().getContents(); - int removed = 0; - for (int i = 0; i < contents.length; i++) { - if (contents[i] != null - && contents[i].getType() == mat) { - removed += contents[i].getAmount(); - contents[i] = null; - } - } - p.getInventory().setContents(contents); - return removed; - } - - // ==================== 边框显示 ==================== - private void showBorder(Player p, AreaConfig ac, boolean temp) { - cancelBorder(p); - int taskId = Bukkit.getScheduler() - .runTaskTimer(plugin, new Runnable() { - @Override - public void run() { - if (!p.isOnline()) return; - Location loc = p.getLocation(); - int px = loc.getBlockX(); - int py = loc.getBlockY(); - int pz = loc.getBlockZ(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - for (int dy = 5; dy <= 30; dy += 5) { - for (int x = minX; x <= maxX; x += 4) { - for (int z = minZ; z <= maxZ; z += 4) { - if (x == minX || x == maxX - || z == minZ || z == maxZ) { - p.spawnParticle(Particle.END_ROD, - x + 0.5, py + dy, z + 0.5, - 1, 0, 0, 0, 0); - } - } - } - } - } - }, 0L, 20L).getTaskId(); - displayTaskIds.put(p.getUniqueId(), taskId); // taskId 是 int - if (temp) { - Bukkit.getScheduler().runTaskLater(plugin, - () -> cancelBorder(p), 300L); - } - } - - - private void cancelBorder(Player p) { - Integer taskId = displayTaskIds.remove(p.getUniqueId()); - if (taskId != null) { - Bukkit.getScheduler().cancelTask(taskId); - } - } - - public void onPlayerQuit(String playerName) { - playerCurrentArea.remove(playerName); - } - - public void clearAllPlayerAreas() { - playerCurrentArea.clear(); - } - private void saveModeExempt(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("排除名单") - || trimmed.contains("模式排除") - || trimmed.contains("模式豁免")) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.modeExempt.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存模式排除名单失败: " + e.getMessage()); - } - } - - // ==================== 事件监听 ==================== - @EventHandler - public void onPlayerMove(PlayerMoveEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - Location to = event.getTo(); - if (to == null) return; - - Location from = event.getFrom(); - if (from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ()) { - return; - } - - // 防抖 - Long lastTime = lastMoveProcess.get(uid); - long now = System.currentTimeMillis(); - if (lastTime != null - && now - lastTime < MOVE_DEBOUNCE_MS) { - return; - } - lastMoveProcess.put(uid, now); - - // 检测区域 - String oldArea = playerCurrentArea.get(uid); - String newArea = null; - String forcedMode = null; - - for (Map.Entry entry - : areas.entrySet()) { - AreaConfig ac = entry.getValue(); - if (!ac.world.equals(to.getWorld().getName())) - continue; - int px = to.getBlockX(); - int pz = to.getBlockZ(); - int py = to.getBlockY(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - if (px >= minX && px <= maxX - && pz >= minZ && pz <= maxZ - && py >= ac.yMin && py <= ac.yMax) { - newArea = entry.getKey(); - forcedMode = ac.enforceGameMode; - break; - } - } - - /* plugin.getLogger().info( - "[防护-移动] " + p.getName() - + " 旧=[" + oldArea - + "] 新=[" + newArea + "]" - + " areas.size=" + areas.size() - + " world=" + to.getWorld().getName());*/ - - // ===== 2. 区域没变 ===== - if (Objects.equals(oldArea, newArea)) { - if (newArea != null) { - AreaConfig ac = areas.get(newArea); - if (ac != null) { - // ★ 每次移动都清除指定效果 - clearBadEffects(p, ac); - - // ★ 每次移动都强制游戏模式 - if (ac.enforceGameMode != null - && !ac.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - } - return; - } - - // 离开旧区域 - if (oldArea != null) { - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★开始离开处理: - + oldArea);*/ - - AreaConfig oldAc = areas.get(oldArea); - - // 离开提示 - if (oldAc != null && oldAc.leaveMsg != null - && !oldAc.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(oldAc.leaveMsg)); - } - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★收标: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - plugin.getLogger().info( - "[防护-移动-离开] 标记项: " + t.getName()); - } - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - plugin.getLogger().info( - "[防护-移动-离开] 取消旧延时"); - } - - // 创建延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = oldArea; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★创建延时任务: " - + toClear.size() + "个");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-移动-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); - - plugin.getLogger().info( - "[防护-移动-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size()); - - if (online == null || !online.isOnline()) { - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-移动-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-移动-清理] + cleared" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - } - } - - - // 进入新区域 - if (newArea != null) { - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - if (oldP != null) { - diffEffects(p, uid, oldP, newArea); - } - } - - AreaConfig newAc = areas.get(newArea); - if (newAc != null) { - if (newAc.enterMsg != null - && !newAc.enterMsg.isEmpty()) { - p.sendMessage( - formatAreaMsg(newAc.enterMsg)); - } - applyRegionEffects(p, uid, newArea, newAc); - // 清除效果 - clearBadEffects(p, newAc); - - } -// ===== 清除指定效果 ===== - if (newAc.clearEffects != null - && !newAc.clearEffects.isEmpty()) { - for (String effName : newAc.clearEffects) { - PotionEffectType type = - resolveEffectType(effName); - if (type != null && p.getPotionEffect(type) != null) { - p.removePotionEffect(type); - plugin.getLogger().info( - "[防护] ★清除效果: " + effName - + " 玩家=" + p.getName()); - } - } - } - -// ===== 清除所有效果 ===== - if (newAc.clearAllBadEffects) { - for (PotionEffect eff : p.getActivePotionEffects()) { - p.removePotionEffect(eff.getType()); - } - plugin.getLogger().info( - "[防护] ★清除所有效果, 玩家=" + p.getName()); - } - - if (forcedMode != null - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(forcedMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - if (newArea != null) { - playerCurrentArea.put(uid, newArea); - } else { - playerCurrentArea.remove(uid); - } - } -// ===== 清理袭击生物 ===== - - // 需要清理的袭击生物类型 - private static final Set RAID_MOBS = new HashSet<>(Arrays.asList( - "VEX", - "EVOKER", - "PILLAGER", - "VINDICATOR", - "WITCH", - "RAVAGER", - "PILLAGER" - )); - - @EventHandler - public void onEntitySpawn(EntitySpawnEvent event) { - Entity entity = event.getEntity(); - Location loc = entity.getLocation(); - - // 检查是否在防护区域内 - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - if (ac == null) return; - - // 禁止袭击:取消袭击事件 - if (ac.denyRaid) { - // 清理袭击生物 - String typeName = entity.getType().name(); - if (RAID_MOBS.contains(typeName)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] ★清理袭击生物: " + typeName - + " 区域=" + ac.name); - } - } - - // 没收物品检测 - if (ac.confiscateItems != null - && !ac.confiscateItems.isEmpty()) { - if (entity instanceof Item) { - Item item = (Item) entity; - String itemType = item.getItemStack() - .getType().name(); - if (ac.confiscateItems.contains(itemType)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] 没收违禁掉落物: " - + itemType); - } - } - } - } - - - /** - * 进入区域时发放效果 - * 流程:查DB → 有记录跳过写入 → 无记录写入DB - * → 给效果 → 贴标 - */ - private void applyRegionEffects(Player p, UUID uid, - String areaName, AreaConfig ac) { - /* plugin.getLogger().info( - "[防护-贴标] ★★★方法被调用! 玩家=" - + p.getName() + " 区域=" + areaName - + " ac=" + (ac != null) - + " 效果配置数=" - + (ac != null ? ac.giveEffects.size() : "null"));*/ - - if (ac == null || ac.giveEffects.isEmpty()) return; - if (ac.giveEffects.isEmpty()) return; - // 查DB:该玩家在此区域是否已有效果记录 - List dbEffects = - getPlayerEffectNames(uid, areaName); - - if (dbEffects.isEmpty()) { - // 无记录 → 写入DB - for (String[] eff : ac.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t == null) continue; - saveSingleEffect(uid, p.getName(), - areaName, t.getName()); - } - plugin.getLogger().info( - "[防护] DB写入, 玩家=" + p.getName() - + " 区域=" + areaName); - } else { - plugin.getLogger().info( - "[防护] DB已有记录, 跳过写入, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - - // 给效果 + 贴标 - List applied = new ArrayList<>(); - for (String[] eff : ac.giveEffects) { - try { - PotionEffectType type = - resolveEffectType(eff[0]); - if (type == null) continue; - int lv = Integer.parseInt(eff[1]) - 1; - int dur = Integer.parseInt(eff[2]) * 20; - - PotionEffect existing = - p.getPotionEffect(type); - if (existing != null - && existing.getAmplifier() >= lv) { - continue; - } - p.addPotionEffect( - new PotionEffect(type, dur, lv)); - applied.add(type); - } catch (Exception ignored) { - } - } - - // 贴标 - // 新代码(合并,不覆盖): - List existingMarks = - playerMarkedEffects.get(uid); - if (existingMarks != null && !existingMarks.isEmpty()) { - // 已有标记,合并新增的 - for (PotionEffectType t : applied) { - if (!existingMarks.contains(t)) { - existingMarks.add(t); - } - } - /* plugin.getLogger().info( - "[防护-贴标] 合并标记: " - + existingMarks.size() - + " 个, 玩家=" + p.getName());*/ - } else { - // 无标记,新建 - playerMarkedEffects.put(uid, applied); - /* plugin.getLogger().info( - "[防护-贴标] 新建标记: " - + applied.size() - + " 个, 玩家=" + p.getName());*/ - } - } - - /** - * 离开区域时,延时15秒清理效果 - * 流程:收标 → 15秒后清效果 → 确认清空 → 清DB - */ - private void scheduleEffectClear(Player p, UUID uid, - String areaName) { - // 收标 - List marked = - playerAppliedEffects.remove(uid); - - plugin.getLogger().info( - "[防护] 收标 " + (marked != null ? marked.size() : 0) - + " 个, 玩家=" + p.getName() - + " 区域=" + areaName); - - // 启动15秒延时 - pendingClearArea.put(uid, areaName); - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - - // 清除效果 - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - } - - // 确认清空后清DB - removePlayerEffects(uid, areaName); - plugin.getLogger().info( - "[防护] 15秒已到, 清除 " - + (marked != null ? marked.size() : 0) - + " 个效果 + DB, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - }.runTaskLater(plugin, 300L); // 15秒 = 300 tick - - pendingClearTask.put(uid, task); - } - public Set getAllAreaNames() { - return areas.keySet(); - } - - /** - * 15秒内进了新区域: - * 清除旧区域有、新区域没有的效果 - */ - private void diffEffects(Player p, UUID uid, - String oldAreaName, String newAreaName) { - AreaConfig oldAc = areas.get(oldAreaName); - AreaConfig newAc = areas.get(newAreaName); - if (oldAc == null || newAc == null) return; - - // ★ 从 pendingClearEffects 获取旧标记 - List oldMarked = - pendingClearEffects.remove(uid); - - if (oldMarked == null || oldMarked.isEmpty()) { - plugin.getLogger().info( - "[防护-跨区域] 无待清理效果"); - return; - } - - // 新区域的效果集合 - Set newTypes = new HashSet<>(); - if (newAc != null) { - for (String[] eff : newAc.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t != null) newTypes.add(t); - } - } - - int cleared = 0; - for (PotionEffectType type : oldMarked) { - if (!newTypes.contains(type)) { - p.removePotionEffect(type); - cleared++; - plugin.getLogger().info( - "[防护-跨区域] 清除: " - + type.getName()); - } else { - plugin.getLogger().info( - "[防护-跨区域] 保留: " - + type.getName() - + " (新区域也有)"); - } - } - - removePlayerEffects(uid, oldAreaName); - plugin.getLogger().info( - "[防护-跨区域] 完成: 清除" - + cleared + "/" + oldMarked.size() - + " 个, " + oldAreaName - + " → " + newAreaName); - } - -// ===== DB 辅助方法(简化版)===== - - /** - * 查询玩家在某个区域的效果记录(返回英文名列表) - */ - private List getPlayerEffectNames( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid=? AND area_name=?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("effect_type")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - /** - * 写入单条效果记录 - */ - private void saveSingleEffect(UUID uid, - String playerName, String areaName, - String effectType) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?,?,?,?,?,?,?)"); - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, effectType); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 写入效果失败: " + e.getMessage()); - } - } - - // ==================== 违禁品没收 ==================== - - /** - * 检查玩家是否豁免于没收 - * 全局白名单 / 区域白名单 / modeExempt / OP 都跳过 - */ - private boolean isExemptFromConfiscation( - Player p, String areaName) { - String name = p.getName(); - - if (globalPlayerWhitelist.contains(name)) - return true; - - Set areaWl = - areaPlayerWhitelist.get(areaName); - if (areaWl != null && areaWl.contains(name)) - return true; - - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) - return true; - - return false; - } - - /** - * 没收玩家背包中的违禁品 - */ - private void confiscateItems(Player p, - AreaConfig ac) { - boolean confiscated = false; - - for (int i = 0; - i < p.getInventory().getSize(); i++) { - ItemStack item = p.getInventory().getItem(i); - if (item == null) continue; - - String typeName = item.getType().name(); - if (ac.confiscateItems - .contains(typeName)) { - p.getInventory().setItem(i, null); - confiscated = true; - } - } - - if (confiscated) { - String msg = ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty() - ? ac.confiscateMsg - : "§c你的违禁品已被没收"; - p.sendMessage(msg); - - // 通报批评 - if (ac.enableAnnounce - && ac.announceTemplate != null) { - String text = ac.announceTemplate - .replace("{player}", - p.getName()); - Bukkit.broadcastMessage(text); - } - } - } - - - - private String getChineseGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: return "生存模式"; - case CREATIVE: return "创造模式"; - case ADVENTURE: return "冒险模式"; - case SPECTATOR: return "旁观模式"; - default: return mode.name(); - } - } - - private void handleEnter(Player p, AreaConfig ac) { - UUID uid = p.getUniqueId(); - - // 清除负面效果 - clearBadEffects(p, ac); - - // 给予效果 - List given = new ArrayList<>(); - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - given.add(type); - } - } - - removePlayerEffects(uid, ac.name); - if (!given.isEmpty()) { - // savePlayerEffects(uid, p.getName(), ac.name, given); - } - - // 白名单检查(最先执行) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 游戏模式切换(只对非白名单玩家) - if (ac.enforceGameMode != null) { - boolean exempt = isPlayerExemptFromModeChange(p, ac); - if (!exempt) { - GameMode target = GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - p.sendMessage("§e§l[区域防护] §f游戏模式已切换为: " - + getChineseGameMode(target)); - } - } - } - - // 和平模式/袭击检查 - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - handleConfiscate(p, ac); - } - - private boolean isPlayerExemptFromModeChange( - Player p, AreaConfig ac) { - // if (p.isOp()) return true; - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return true; - } - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return true; - } - if (ac.modeExempt.contains(p.getName())) { - return true; - } - return false; - } - - private void banRangedHostiles(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (isRangedHostile(ent.getType().name())) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - /** - * 按配置清除效果 - * denyRaid → 清除袭击相关 - * denyAllEffects → 清除所有负面+中性 - * denyAllEffects > denyRaid(全清优先) - */ - private void clearBadEffects(Player p, AreaConfig ac) { - // 和平模式 > 清除所有效果 > 禁止单个 - if (ac.peaceMode || ac.clearAllBadEffects) { - PotionEffectType[] allBad = { - PotionEffectType.SLOWNESS, - PotionEffectType.MINING_FATIGUE, - PotionEffectType.INSTANT_DAMAGE, - PotionEffectType.NAUSEA, - PotionEffectType.BLINDNESS, - PotionEffectType.HUNGER, - PotionEffectType.WEAKNESS, - PotionEffectType.POISON, - PotionEffectType.WITHER, - PotionEffectType.LEVITATION, - PotionEffectType.UNLUCK, - PotionEffectType.DARKNESS, - PotionEffectType.WIND_CHARGED, - PotionEffectType.WEAVING, - PotionEffectType.OOZING, - PotionEffectType.INFESTED, - PotionEffectType.BAD_OMEN, - PotionEffectType.TRIAL_OMEN, - PotionEffectType.RAID_OMEN - }; - for (PotionEffectType type : allBad) { - p.removePotionEffect(type); - } - return; - } - - // 禁止袭击 → 自动关联清除 - if (ac.denyRaid) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - p.removePotionEffect(PotionEffectType.RAID_OMEN); - } - - // 配置文件单个清除 - for (String effName : ac.clearEffects) { - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.removePotionEffect(type); - } - } - } - - - private PotionEffectType resolveEffectType(String name) { - if (name == null || name.isEmpty()) return null; - - // 去掉"效果"后缀 - String clean = name; - if (clean.endsWith("效果")) { - clean = clean.substring(0, clean.length() - 2); - } - - // 硬编码中文名→英文ID映射(不依赖任何外部Map) - switch (clean) { - // 负面 - case "缓慢": - return resolveEffectType("slowness"); - case "挖掘疲劳": - return resolveEffectType("mining_fatigue"); - case "瞬间伤害": - return resolveEffectType("instant_damage"); - case "反胃": - return resolveEffectType("nausea"); - case "失明": - return resolveEffectType("blindness"); - case "饥饿": - return resolveEffectType("hunger"); - case "虚弱": - return resolveEffectType("weakness"); - case "中毒": - return resolveEffectType("poison"); - case "凋零": - return resolveEffectType("wither"); - case "飘浮": - return resolveEffectType("levitation"); - case "霉运": - return resolveEffectType("unluck"); - case "黑暗": - return resolveEffectType("darkness"); - case "蓄风": - return resolveEffectType("wind_charged"); - case "盘丝": - return resolveEffectType("weaving"); - case "渗浆": - return resolveEffectType("oozing"); - case "寄生": - return resolveEffectType("infested"); - // 中性 - case "不祥之兆": - case "不祥征兆": - return resolveEffectType("bad_omen"); - case "袭击之兆": - case "袭击征兆": - return resolveEffectType("raid_omen"); - case "试炼之兆": - case "试炼征兆": - return resolveEffectType("trial_omen"); - // 正面 - case "迅捷": - return resolveEffectType("speed"); - case "急迫": - return resolveEffectType("haste"); - case "力量": - return resolveEffectType("strength"); - case "瞬间治疗": - return resolveEffectType("instant_health"); - case "跳跃提升": - return resolveEffectType("jump_boost"); - case "生命恢复": - return resolveEffectType("regeneration"); - case "抗性提升": - return resolveEffectType("resistance"); - case "抗火": - return resolveEffectType("fire_resistance"); - case "水下呼吸": - return resolveEffectType("water_breathing"); - case "隐身": - return resolveEffectType("invisibility"); - case "夜视": - return resolveEffectType("night_vision"); - case "发光": - return resolveEffectType("glowing"); - case "生命提升": - return resolveEffectType("health_boost"); - case "伤害吸收": - return resolveEffectType("absorption"); - case "饱和": - return resolveEffectType("saturation"); - case "幸运": - return resolveEffectType("luck"); - case "村庄英雄": - return resolveEffectType("hero_of_the_village"); - case "缓降": - return resolveEffectType("slow_falling"); - case "潮涌能量": - return resolveEffectType("conduit_power"); - case "海豚的恩惠": - return resolveEffectType("dolphins_grace"); - case "致命中毒": - return resolveEffectType("poison"); - default: - break; - } - - -// 兜底:直接用原版英文ID查(★ 注意是 PotionEffectType.getByName,不是 resolveEffectType) - PotionEffectType fallback = PotionEffectType.getByName(clean); - if (fallback != null) return fallback; - fallback = PotionEffectType.getByName(clean.toLowerCase()); - if (fallback != null) return fallback; - - plugin.getLogger().warning("[防护] 无法识别效果: " + name); - return null; - - } - - - private static final Map EFFECT_MAP - = new HashMap<>(); - - - private void clearRaidEffects(Player p) { - boolean cleared = false; - try { - if (p.hasPotionEffect(PotionEffectType.BAD_OMEN)) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - try { - if (p.hasPotionEffect(PotionEffectType.RAID_OMEN)) { - p.removePotionEffect(PotionEffectType.RAID_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - if (cleared) { - p.sendMessage("§c§l[区域防护] §f已清除袭击相关效果"); - } - } - - public Map> - getPendingClearEffects() { - return pendingClearEffects; - } - - public void onPlayerOffline(UUID uid, String playerName, - String areaName) { - // 清除该玩家在该区域的所有效果 - List given = getPlayerEffects(uid, areaName); - for (PotionEffectType type : given) { - // 只清除我们给予的效果 - Player p = Bukkit.getPlayer(uid); - if (p != null) { - p.removePotionEffect(type); - } - } - // 清除DB记录 - removePlayerEffects(uid, areaName); - plugin.getLogger().info("[防护] " + playerName - + " 下线,已清除" + given.size() - + "个效果并清理DB"); - } - - @EventHandler - public void onTeleport(PlayerTeleportEvent e) { - Location from = e.getFrom(); - Location to = e.getTo(); - if (to == null) return; - - if (from.getWorld() == to.getWorld() - && from.distance(to) < 5) return; - - Player p = e.getPlayer(); - UUID uid = p.getUniqueId(); - - // 标记传送中 - teleporting.add(uid); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - teleporting.remove(uid); - }, 5L); - - AreaConfig fromArea = getArea( - from.getWorld().getName(), - from.getBlockX(), from.getBlockY(), - from.getBlockZ()); - AreaConfig toArea = getArea( - to.getWorld().getName(), - to.getBlockX(), to.getBlockY(), - to.getBlockZ()); - - String fromName = fromArea != null - ? fromArea.name : null; - String toName = toArea != null - ? toArea.name : null; - - /* plugin.getLogger().info( - "[防护-传送] ★★★进入onTeleport: " - + p.getName() - + " [" + fromName + "] → [" + toName + "]" - + " teleporting=" + teleporting.contains(uid));*/ - - if (Objects.equals(fromName, toName)) { - /* plugin.getLogger().info( - "[防护-传送] 区域相同, 跳过");*/ - return; - } - - // ===== 离开旧区域 ===== - if (fromName != null) { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★开始离开处理: " - + fromName);*/ - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★收标结果: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - /* plugin.getLogger().info( - "[防护-传送-离开] 标记项: " - + t.getName());*/ - } - } else { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★标记为null! " - + "尝试其他key查找...");*/ - // 遍历查找可能的标记 - for (Map.Entry> entry - : playerMarkedEffects.entrySet()) { - if (entry.getKey().equals(uid)) { - /* plugin.getLogger().info( - "[防护-传送-离开] 找到标记! 效果数=" - + entry.getValue().size());*/ - } - } - /* plugin.getLogger().info( - "[防护-传送-离开] playerMarkedEffects大小=" - + playerMarkedEffects.size());*/ - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 创建15秒延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = fromName; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★创建延时任务: " - + toClear.size() + "个效果");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-传送-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); -/* - plugin.getLogger().info( - "[防护-传送-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size() - + " 区域=" + clearArea);*/ - - if (online == null - || !online.isOnline()) { - plugin.getLogger().info( - "[防护-传送-清理] 玩家不在线, 清DB"); - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-传送-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-传送-清理] ★★★完成: 清除" - + cleared + "/" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - - } - - // 离开提示 - if (fromArea != null && fromArea.leaveMsg != null - && !fromArea.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(fromArea.leaveMsg)); - } - } - - // ===== 进入新区域 ===== - if (toName != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] ★★★开始进入处理: " - + toName);*/ - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - plugin.getLogger().info( - "[防护-传送-进入] 取消旧延时, 旧区域=" - + oldP); - if (oldP != null) { - diffEffects(p, uid, oldP, toName); - } - } - - // 进入提示 - if (toArea != null && toArea.enterMsg != null - && !toArea.enterMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(toArea.enterMsg)); - } - - // 发放效果 - if (toArea != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] 调用applyRegionEffects");*/ - applyRegionEffects(p, uid, toName, toArea); -// 清除效果 - clearBadEffects(p, toArea); - - if (toArea.confiscateItems != null - && !toArea.confiscateItems.isEmpty() - && !isExemptFromConfiscation( - p, toName)) { - confiscateItems(p, toArea); - } - } - - // 强制模式 - if (toArea != null && toArea.enforceGameMode != null - && !toArea.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, toName)) { - GameMode target = GameMode.valueOf( - toArea.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - // 更新记录 - if (toName != null) { - playerCurrentArea.put(uid, toName); - } else { - playerCurrentArea.remove(uid); - } - } - - - public Map getPendingClearTasks() { - return pendingClearTask; - } - - public Map> - getPlayerMarkedEffects() { - return playerMarkedEffects; - } - - public void handleInside(Player p, AreaConfig ac) { - clearBadEffects(p, ac); - - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - // 全白或区白 → 跳过所有限制性规则(包括没收) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - handleConfiscate(p, ac); - } - - public void cleanupExpiredProtections() { - protectedEntities.entrySet().removeIf( - entry -> System.currentTimeMillis() > entry.getValue()); - } - - // 禁止袭击:只驱逐袭击怪物 - private void banRaidMobs(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(32, 32, 32)) { - String t = ent.getType().name(); - if (t.equals("PILLAGER") - || t.equals("VINDICATOR") - || t.equals("EVOKER") - || t.equals("RAVAGER") - || t.equals("VEX") - || t.equals("WITCH") - || t.equals("ILLUSIONER") - || t.equals("ZOMBIE_VILLAGER")) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(voidLoc.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - public Set getAreaNames() { - return areas.keySet(); - } - - public Collection getPendingTasks() { - return pendingClearTask.values(); - } - - - // 判断是否为远程攻击敌对生物 - private boolean isRangedHostile(String typeName) { - switch (typeName) { - // ===== 骷髅系 ===== - case "SKELETON": // 骷髅(弓箭) - case "STRAY": // 流浪者(迟缓之箭) - case "BOGGED": // 虫蚀骷髅(毒箭) - case "WITHER_SKELETON": // 凋灵骷髅(近战但算灾厄) - - // ===== 灾厄村民 ===== - case "PILLAGER": // 掠夺者(弩) - case "ILLUSIONER": // 幻术师(弓箭+失明) - case "EVOKER": // 幻魔者(唤魔) - case "VINDICATOR": // 卫道士(斧头近战) - case "VEX": // 恼鬼(剑近战,但会穿墙飞行) - - // ===== 法术/投射 ===== - case "BLAZE": // 烈焰人(火球) - case "WITCH": // 女巫(药水) - - // ===== 飞行/爆炸 ===== - case "GHAST": // 恶魂(火球) - case "PHANTOM": // 幻翼(俯冲) - case "BIG_GHAST": // 大恶魂(1.21新增) - - // ===== 水下 ===== - case "GUARDIAN": // 守卫者(激光) - case "ELDER_GUARDIAN": // 远古守卫者(激光) - - // ===== 远程特殊 ===== - case "SHULKER": // 潜影贝(潜影弹) - case "BREEZE": // 旋风人(风弹) - - // ===== 蜘蛛系(远程毒液攻击) ===== - case "SPIDER": // 蜘蛛(白天远程吐丝) - case "CAVE_SPIDER": // 洞穴蜘蛛(毒) - - // ===== 1.21新增 ===== - case "WIND_CHARGED": // 蓄风怪(风弹) - case "WEAVING": // 盘丝怪(吐丝) - case "OOZING": // 渗浆怪(史莱姆) - case "INFESTED": // 寄生怪(蠹虫) - - // ===== 盔甲/变种(部分版本) ===== - case "ARMORED_SKELETON": // 盔甲骷髅 - case "ARCHER_SKELETON": // 弓箭骷髅(部分版本) - case "SNOW_GOLEM": // 雪傀儡(不是敌对但远程) - - // ===== 亡灵骑乘 ===== - case "HUSK": // 尸壳(近战但属于灾厄系) - case "DROWNED": // 溺尸(三叉戟远程) - case "ZOMBIE_VILLAGER": // 僵尸村民 - case "ZOMBIE_PIGMAN": // 僵尸猪灵 - case "ZOMBIFIED_PIGLIN": // 僵尸猪灵(1.16+) - case "PIGLIN_BRUTE": // 猪灵蛮兵 - - // ===== 猪灵(弓箭) ===== - case "PIGLIN": // 猪灵(金弩远程) - - // ===== 末影系 ===== - case "ENDER_DRAGON": // 末影龙(远程吐息) - case "WARDEN": // 监守者(远程音波) - - // ===== 其他 ===== - case "CREPER": // 苦力怕(爆炸不算远程但危险) - case "CREEPER": // 苦力怕 - - return true; - default: - return false; - } - } - - - - private void applyEffects(Player p, AreaConfig ac) { - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - - PotionEffectType type = resolveEffectType(effName); - plugin.getLogger().info("[防护调试] 给予: " - + effName + " -> " + type); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - plugin.getLogger().info("[防护调试] 已给予: " - + effName + " 等级" + level - + " 持续" + duration + "秒"); - } - } - } - - - @EventHandler - public void onBlockPlace(BlockPlaceEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockPlace - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止放置方块"); - } - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockBreak - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏方块"); - } - } - - - @EventHandler - public void onDamage(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - // 保护性规则:对所有人无条件生效 - if (ac.denyAllDamage) { e.setCancelled(true); return; } - if (ac.denyFallDamage - && e.getCause() == EntityDamageEvent.DamageCause.FALL) - e.setCancelled(true); - } - - - @EventHandler - public void onPVP(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyPVP - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - - @EventHandler - public void onFood(FoodLevelChangeEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyHunger) - e.setCancelled(true); - } - - - - @EventHandler - public void onDrop(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyDrop - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止丢弃物品"); - } - } - - // 替换整个 onInteract 方法 - @EventHandler - public void onInteract(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (hand == null) return; - - // ===== 选区工具 ===== - if (hand.getType() == Material.BREEZE_ROD) { - ItemMeta meta = hand.getItemMeta(); - if (meta != null && meta.getDisplayName() != null - && meta.getDisplayName().contains("区域选择")) { - Action action = e.getAction(); - boolean isLeft = (action == Action.LEFT_CLICK_AIR - || action == Action.LEFT_CLICK_BLOCK); - boolean isRight = (action == Action.RIGHT_CLICK_AIR - || action == Action.RIGHT_CLICK_BLOCK); - if (isLeft || isRight) { - e.setCancelled(true); - Block block = e.getClickedBlock(); - if (block == null) { - try { block = p.getTargetBlockExact(5); } - catch (Exception ignored) {} - } - if (block == null) return; - UUID uid = p.getUniqueId(); - if (isLeft) { - pos1.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置1: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } else { - pos2.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置2: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } - } - return; - } - } - - // ===== 放置实体类物品时不检查(展示框、矿车、船、地图等)===== - Material mat = hand.getType(); - if (mat == Material.ITEM_FRAME - || mat == Material.GLOW_ITEM_FRAME - || mat == Material.MINECART - || mat == Material.TNT_MINECART - || mat == Material.CHEST_MINECART - || mat == Material.HOPPER_MINECART - || mat == Material.OAK_BOAT - || mat == Material.SPRUCE_BOAT - || mat == Material.BIRCH_BOAT - || mat == Material.JUNGLE_BOAT - || mat == Material.ACACIA_BOAT - || mat == Material.DARK_OAK_BOAT - || mat == Material.MANGROVE_BOAT - || mat == Material.CHERRY_BOAT - || mat == Material.BAMBOO_RAFT - || mat == Material.MAP - || mat == Material.ARMOR_STAND) { - return; - } - - // ===== 区域规则检查 ===== - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - - // 末影珍珠禁止 - if (ac.denyEnderPearl - && hand.getType() == Material.ENDER_PEARL) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用末影珍珠"); - return; - } - - // 弓箭禁止 - if (ac.denyBow - && (hand.getType() == Material.BOW - || hand.getType() == Material.CROSSBOW)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用弓箭"); - return; - } - - // 白名单跳过 - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 禁止使用物品 - String typeId = hand.getType().name(); - if (ac.denyUseItems.contains(typeId)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用此物品"); - return; - } - - // 没收检查 - handleConfiscate(p, ac); - } - - - - @EventHandler - public void onExplosion(EntityExplodeEvent e) { - Iterator it = e.blockList().iterator(); - while (it.hasNext()) { - Block b = it.next(); - AreaConfig ac = getArea( - b.getWorld().getName(), - b.getX(), b.getY(), b.getZ()); - if (ac != null && ac.denyExplosion) - it.remove(); - } - } - public void loadWhitelists() { - globalPlayerWhitelist.clear(); - areaPlayerWhitelist.clear(); - - if (!whitelistDir.exists()) { - whitelistDir.mkdirs(); - return; - } - - try { - // 加载全局白名单 - if (globalWhitelistFile.exists()) { - List lines = - java.nio.file.Files.readAllLines( - globalWhitelistFile.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - globalPlayerWhitelist.add(name); - } - } - } - - // 加载各区域白名单 - File[] files = whitelistDir.listFiles( - (File d, String n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (files != null) { - for (File f : files) { - String areaName = f.getName() - .replace(".txt", ""); - Set set = - ConcurrentHashMap.newKeySet(); - List lines = - java.nio.file.Files.readAllLines( - f.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - set.add(name); - } - } - if (!set.isEmpty()) { - areaPlayerWhitelist.put( - areaName, set); - } - } - } - - plugin.getLogger().info( - "[防护] 白名单加载完成,全局: " - + globalPlayerWhitelist.size() - + ",区域数: " - + areaPlayerWhitelist.size()); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载白名单失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onPotionSplash(PotionSplashEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - AreaConfig ac = getArea( - shooter.getWorld().getName(), - shooter.getLocation().getBlockX(), - shooter.getLocation().getBlockY(), - shooter.getLocation().getBlockZ()); - if (ac != null && ac.denyPotion - && !isPlayerWhitelisted(shooter.getName(), ac)) { - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止使用药水"); - } - } - - /** - * 解析区域名:先精准匹配,再模糊匹配 - * @param input 用户输入的区域名 - * @return 匹配到的区域名,无匹配返回null - */ - public String resolveAreaName(String input) { - if (input == null || input.isEmpty()) return null; - if (input.equalsIgnoreCase("global")) return "global"; - - // 第一步:精准匹配 - for (String name : areas.keySet()) { - if (name.equalsIgnoreCase(input)) { - return name; - } - } - - // 第二步:包含匹配(用户只打了部分名字) - for (String name : areas.keySet()) { - if (name.contains(input) || input.contains(name)) { - return name; - } - } - - // 第三步:编辑距离模糊匹配(著称→主城) - String bestMatch = null; - int bestDistance = Integer.MAX_VALUE; - for (String name : areas.keySet()) { - int dist = levenshteinDistance( - input.toLowerCase(), name.toLowerCase()); - if (dist < bestDistance) { - bestDistance = dist; - bestMatch = name; - } - } - - // 容错阈值:编辑距离 <= 名字长度的一半 - if (bestMatch != null) { - int threshold = Math.max(1, bestMatch.length() / 2); - if (bestDistance <= threshold) { - return bestMatch; - } - } - - return null; - } - - /** - * 判断字符串是否是区域名(精准或模糊) - */ - private boolean isAreaName(String name) { - if (name.equalsIgnoreCase("global")) return false; - return resolveAreaName(name) != null; - } - - /** - * 获取模糊匹配的实际区域名(用于提示用户) - */ - private String getResolvedName(String input) { - return resolveAreaName(input); - } - - /** - * 编辑距离算法(莱文斯坦距离) - */ - private int levenshteinDistance(String s1, String s2) { - int[][] dp = new int[s1.length() + 1][s2.length() + 1]; - for (int i = 0; i <= s1.length(); i++) dp[i][0] = i; - for (int j = 0; j <= s2.length(); j++) dp[0][j] = j; - for (int i = 1; i <= s1.length(); i++) { - for (int j = 1; j <= s2.length(); j++) { - if (s1.charAt(i - 1) == s2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], - Math.min(dp[i - 1][j], dp[i][j - 1])); - } - } - } - return dp[s1.length()][s2.length()]; - } - - /** - * 智能重排参数:找出子命令位置,重排为标准顺序 - * 标准顺序:/protect 子命令 参数1 参数2 ... - */ - private String[] smartReorderArgs(String[] original) { - if (original.length <= 1) return original; - - Set knownSubs = new HashSet<>(Arrays.asList( - "add", "remove", "additem", "removeitem", - "addname", "removename", "listname", - "addwhite", "removewhite", "listwhite", - "list", "listitem", "创建", "删除", "重载", - "工具", "expand", "contraction", - "on", "off", "tempon", "modeexempt" - )); - - String first = original[0].toLowerCase(); - if (knownSubs.contains(first)) { - return original; - } - - int subIndex = -1; - for (int i = 0; i < original.length; i++) { - if (knownSubs.contains(original[i].toLowerCase())) { - subIndex = i; - break; - } - } - - if (subIndex < 0) { - return original; - } - - String[] reordered = new String[original.length]; - reordered[0] = original[subIndex]; - int idx = 1; - for (int i = 0; i < original.length; i++) { - if (i == subIndex) continue; - reordered[idx] = original[i]; - idx++; - } - - return reordered; - } - - // ==================== 管理命令 ==================== - public boolean handleCommand(CommandSender sender, - String[] args) { - args = smartReorderArgs(args); - if (args.length == 0) { - showHelp(sender); - return true; - } - String sub = args[0].toLowerCase(); -// ===== 诊断命令 ===== - if (sub.equals("sdf1debug")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - sender.sendMessage("§e===== 调试信息 ====="); - sender.sendMessage("§7当前区域: " - + playerCurrentArea.get(uid)); - sender.sendMessage("§7标记效果数: " - + (playerMarkedEffects.containsKey(uid) - ? playerMarkedEffects.get(uid).size() - : "无标记")); - sender.sendMessage("§7待清理任务: " - + pendingClearTask.containsKey(uid)); - sender.sendMessage("§7区域数: " + areas.size()); - - if (playerMarkedEffects.containsKey(uid)) { - List marked = - playerMarkedEffects.get(uid); - for (PotionEffectType type : marked) { - sender.sendMessage("§7 标记: " - + type.getName() - + " 身上有=" - + (p.getPotionEffect(type) != null)); - } - } - return true; - } - -// ===== 强制清理命令(测试用)===== - if (sub.equals("testclear")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - List marked = - playerMarkedEffects.remove(uid); - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - sender.sendMessage("§a已强制清理 " - + marked.size() + " 个效果"); - } else { - sender.sendMessage("§c无标记效果可清理"); - } - - // 取消延时任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - sender.sendMessage("§a已取消延时任务"); - } - - removePlayerEffects(uid, - playerCurrentArea.getOrDefault(uid, "")); - sender.sendMessage("§aDB已清理"); - return true; - } - - // ===== 工具 ===== - if (sub.equals("工具") || sub.equals("wand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - ItemStack tool = new ItemStack(WAND); - ItemMeta meta = tool.getItemMeta(); - meta.setDisplayName("§a§l区域选择工具"); - meta.setLore(Arrays.asList( - "§7左键: 位置1", "§7右键: 位置2")); - tool.setItemMeta(meta); - p.getInventory().addItem(tool); - p.sendMessage("§a§l[防护] §f已获取选择工具"); - return true; - } - - // ===== 创建 ===== - if (sub.equals("创建") || sub.equals("create")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 创建 <名>"); - return true; - } - Player p = (Player) sender; - UUID u = p.getUniqueId(); - if (!pos1.containsKey(u) - || !pos2.containsKey(u)) { - p.sendMessage("§c请先用工具选点"); - return true; - } - String areaName = args[1]; - File f = new File(rootDir, areaName + ".txt"); - if (f.exists()) { - p.sendMessage("§c区域已存在"); - return true; - } - Location l1 = pos1.get(u); - Location l2 = pos2.get(u); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 区域: " + areaName); - pw.println("起点: " + l1.getWorld().getName() - + "," + l1.getBlockX() - + "," + l1.getBlockZ()); - pw.println("终点: " + l2.getWorld().getName() - + "," + l2.getBlockX() - + "," + l2.getBlockZ()); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("# 禁止放置方块"); - pw.println("# 禁止破坏方块"); - pw.println("# 禁止PVP"); - pw.println("# 效果: 夜视 1 999"); - pw.println("# 进入提示: 欢迎来到保护区"); - pw.println("# 离开提示: 已离开保护区"); - pw.close(); - p.sendMessage("§a§l[防护] §f区域 " - + areaName + " 已创建"); - loadAllAreas(); - } catch (IOException ex) { - p.sendMessage("§c创建失败"); - } - return true; - } - - // ===== 和平白名单 ===== - if (sub.equals("addname")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别: " - + args[1] + " 或 " + args[2]); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 和平白名单"); - return true; - } - sender.sendMessage("§c用法: /protect addname " - + "<区域> <名字>"); - return true; - } - if (sub.equals("removename")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " + parsed[0]); - return true; - } - sender.sendMessage("§c用法: /protect removename " - + "<区域> <名字>"); - return true; - } - if (sub.equals("listname")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listname <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 和平白名单:"); - if (ac.peaceWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.peaceWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 模式排除 ===== - if (sub.equals("addwhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect addwhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("removewhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect removewhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("listwhite")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listwhite <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 模式排除名单:"); - if (ac.modeExempt.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.modeExempt) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 列表 ===== - if (sub.equals("列表") || sub.equals("list")) { - if (args.length == 2) { - if (args[1].equalsIgnoreCase("global")) { - sender.sendMessage("§a全局白名单:"); - if (globalPlayerWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n - : globalPlayerWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - String an = args[1]; - Set wl = - areaPlayerWhitelist.get(an); - sender.sendMessage("§a" + an + " 白名单:"); - if (wl == null || wl.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : wl) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - if (areas.isEmpty()) { - sender.sendMessage("§7暂无区域"); - } else { - sender.sendMessage("§e§l区域列表:"); - for (Map.Entry en - : areas.entrySet()) { - sender.sendMessage("§a " + en.getKey() - + " §7规则:" - + en.getValue().ruleCount() - + "条"); - } - } - return true; - } - - // ===== 重载 ===== - if (sub.equals("重载") || sub.equals("reload")) { - reload(); - sender.sendMessage("§a已重载 " - + areas.size() + " 个区域"); - return true; - } - - // ===== 物品黑名单 ===== - if (sub.equals("additem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + args[1]); - return true; - } - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + itemName); - return true; - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - } else { - areaItemBlacklist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()) - .add(itemName); - sender.sendMessage("§a已加黑: " - + itemName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect additem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("removeitem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (!globalItemBlacklist - .contains(itemName)) { - sender.sendMessage( - "§c全局黑名单中没有: " - + itemName); - return true; - } - globalItemBlacklist.remove(itemName); - sender.sendMessage("§a已移除: " + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.remove(itemName); - } else { - Set list = - areaItemBlacklist.get(areaName); - if (list != null) { - list.remove(itemName); - } - } - sender.sendMessage("§a已移除: " - + itemName); - return true; - } - sender.sendMessage("§c用法: /protect removeitem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("listitem")) { - if (args.length < 2) { - sender.sendMessage( - "§a全局物品黑名单:"); - if (globalItemBlacklist.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item - : globalItemBlacklist) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - Set list = - areaItemBlacklist.get(args[1]); - sender.sendMessage("§a" + args[1] - + " 物品黑名单:"); - if (list == null || list.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item : list) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - - // ===== expand 扩建 ===== - if (sub.equals("expand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ -= amount; - else if (dir.equals("南")) maxZ += amount; - else if (dir.equals("东")) maxX += amount; - else if (dir.equals("西")) minX -= amount; - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f扩建 §e" - + amount + "§f格"); - return true; - } - - // ===== contract 收缩 ===== - if (sub.equals("contraction") - || sub.equals("contract")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ += amount; - else if (dir.equals("南")) maxZ -= amount; - else if (dir.equals("东")) maxX -= amount; - else if (dir.equals("西")) minX += amount; - if (minX > maxX || minZ > maxZ) { - p.sendMessage("§c收缩过度"); - return true; - } - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f收缩 §e" - + amount + "§f格"); - return true; - } - - // ===== 删除 ===== - if (sub.equals("删除") || sub.equals("delete")) { - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 删除 <名>"); - return true; - } - File f = new File(rootDir, args[1] + ".txt"); - if (f.exists()) { - f.delete(); - areas.remove(args[1]); - sender.sendMessage("§a已删除: " + args[1]); - } else { - sender.sendMessage("§c不存在: " + args[1]); - } - return true; - } - - // ===== add 加白名单 ===== - if (sub.equals("add")) { - if (args.length == 2) { - String name = args[1]; - if (globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 已在全局白名单中"); - return true; - } - globalPlayerWhitelist.add(name); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " - + playerName); - } else { - Set wl = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()); - wl.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已加白: " - + playerName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect add " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== remove 移除白名单 ===== - if (sub.equals("remove")) { - if (args.length == 2) { - String name = args[1]; - if (!globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 不在全局白名单中"); - return true; - } - globalPlayerWhitelist.remove(name); - saveWhitelists(); - sender.sendMessage("§a已从全局白名单移除: " - + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.remove(playerName); - saveWhitelists(); - sender.sendMessage("§a已移除: " - + playerName); - } else { - Set wl = - areaPlayerWhitelist.get(areaName); - if (wl != null) { - wl.remove(playerName); - } - saveWhitelists(); - sender.sendMessage("§a已从 " + areaName - + " 移除: " + playerName); - } - return true; - } - sender.sendMessage("§c用法: /protect remove " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== on/off/tempon 边框 ===== - if (sub.equals("on")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, false); - p.sendMessage("§a§l[防护] §f已显示 " - + ac.name + " 边框"); - return true; - } - if (sub.equals("off")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - cancelBorder((Player) sender); - ((Player) sender).sendMessage( - "§a§l[防护] §f已关闭边框显示"); - return true; - } - if (sub.equals("tempon")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, true); - p.sendMessage("§a§l[防护] §f临时显示 " - + ac.name + " 边框(15秒)"); - return true; - } - - showHelp(sender); - return true; - } - - - private String[] parseAreaAndTarget(String arg1, String arg2) { - String r1 = resolveAreaName(arg1); - if (r1 != null) { - return new String[]{r1, arg2}; - } - String r2 = resolveAreaName(arg2); - if (r2 != null) { - return new String[]{r2, arg1}; - } - return null; - } - - private boolean playerExists(String playerName) { - // 在线玩家 - for (Player online : Bukkit.getOnlinePlayers()) { - if (online.getName().equalsIgnoreCase(playerName)) return true; - } - // 离线玩家 - @SuppressWarnings("deprecation") - OfflinePlayer op = Bukkit.getOfflinePlayer(playerName); - return op.hasPlayedBefore(); - } - - - private boolean validateArea(CommandSender sender, String areaName) { - if (!areas.containsKey(areaName)) { - sender.sendMessage("§c区域不存在: " + areaName); - return false; - } - return true; - } - - - private String matname(Material mat) { - String name = mat.name(); - String[] parts = name.split("_"); - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (sb.length() > 0) sb.append(" "); - sb.append(part.substring(0, 1).toUpperCase()) - .append(part.substring(1).toLowerCase()); - } - return sb.toString(); - } - private String yawToDir(float yaw) { - // MC中: 0=南, 90=西, 180=北, -90/270=东 - yaw = yaw % 360; - if (yaw < 0) yaw += 360; - if (yaw >= 315 || yaw < 45) return "南"; - if (yaw >= 45 && yaw < 135) return "西"; - if (yaw >= 135 && yaw < 225) return "北"; - return "东"; - } - private void saveAreaConfig(AreaConfig ac) { - File f = new File(rootDir, ac.name + ".txt"); - if (!f.exists()) return; - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - if (line.trim().startsWith("起点:")) { - lines.add("起点: " + ac.world - + "," + ac.x1 + "," + ac.z1); - } else if (line.trim().startsWith("终点:")) { - lines.add("终点: " + ac.world - + "," + ac.x2 + "," + ac.z2); - } else { - lines.add(line); - } - } - br.close(); - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - for (String l : lines) pw.println(l); - pw.close(); - } catch (IOException ignored) {} - } - - private void showHelp(CommandSender s) { - s.sendMessage("§e§l==== 区域防护 ===="); - s.sendMessage("§a/protect 工具 §7选地工具"); - s.sendMessage("§a/protect 创建 <名> §7创建区域"); - s.sendMessage("§a/protect 列表 §7区域列表"); - s.sendMessage("§a/protect 重载 §7重载配置"); - s.sendMessage("§a/protect 删除 <名> §7删除区域"); - s.sendMessage("§a/protect add [区域] <玩家> §7玩家加白"); - s.sendMessage("§a/protect remove [区域] <玩家> §7玩家删白"); - s.sendMessage("§a/protect additem [区域] <物品> §7物品加黑"); - s.sendMessage("§a/protect removeitem [区域] <物品> §7物品删黑"); - s.sendMessage("§a/protect on §7显示边框"); - s.sendMessage("§a/protect off §7关闭边框"); - s.sendMessage("§a/protect tempon §7临时边框(15秒)"); - s.sendMessage("§a/protect list §7列出全局白名单"); - s.sendMessage("§a/protect list <区域> §7列出区域白名单"); - s.sendMessage("§a/protect listitem §7列出全局物品黑名单"); - s.sendMessage("§a/protect listitem <区域> §7列出区域物品黑名单"); - - } -// ==================== 白名单管理公共方法 ==================== - - // ==================== 白名单操作 ==================== - - public boolean isPlayerGlobalWhitelisted( - String playerName) { - return globalPlayerWhitelist.contains(playerName); - } - - public boolean addPlayerToGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean isPlayerAreaWhitelisted( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - return wl != null && wl.contains(playerName); - } - - public boolean addPlayerToAreaWhitelist( - String areaName, String playerName) { - Set set = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap.newKeySet()); - boolean ok = set.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromAreaWhitelist( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - boolean ok = wl != null && wl.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - - - public int handleConfiscate(Player p, AreaConfig ac) { - if (isPlayerWhitelisted(p.getName(), ac)) return 0; - Set allItems = new HashSet<>(ac.confiscateItems); - allItems.addAll(globalItemBlacklist); - Set areaList = areaItemBlacklist.get(ac.name); - if (areaList != null) allItems.addAll(areaList); - - if (allItems.isEmpty()) return 0; - - int totalRemoved = 0; - String confiscatedNames = ""; - - for (String itemId : allItems) { - // 过滤掉区域名和无效ID - Material mat = Material.matchMaterial(itemId); - if (mat == null) { - plugin.getLogger().warning( - "[防护] 跳过无效物品: " + itemId); - continue; - } - ItemStack[] all = p.getInventory().getContents(); - int count = 0; - for (int i = 0; i < all.length; i++) { - if (all[i] != null && all[i].getType() == mat) { - count += all[i].getAmount(); - all[i] = null; - } - } - p.getInventory().setContents(all); - - ItemStack off = p.getInventory().getItemInOffHand(); - if (off != null && off.getType() == mat) { - count += off.getAmount(); - p.getInventory().setItemInOffHand(null); - } - - if (count > 0) { - totalRemoved += count; - if (!confiscatedNames.isEmpty()) - confiscatedNames += "§7、§e"; - confiscatedNames += "§e" + matname(mat) - + " x" + count; - } - } - - if (totalRemoved > 0) { - String msg = (ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty()) - ? ac.confiscateMsg - : "你携带了违禁品,已被没收"; - p.sendMessage("§c§l[区域防护] §f" + msg); - p.sendMessage("§7没收物品: " + confiscatedNames); - - if (ac.enableAnnounce - && ac.announceTemplate != null - && !ac.announceTemplate.isEmpty()) { - String ann = ac.announceTemplate - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)) - .replace("{items}", confiscatedNames); - Bukkit.broadcastMessage("§c§l[区域防护] §f" + ann); - } - for (String cmd : ac.punishCommands) { - String finalCmd = cmd - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)); - Bukkit.getScheduler().runTask(plugin, - () -> Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), finalCmd)); - } - } - return totalRemoved; - } - public void clearBlacklists() { - globalItemBlacklist.clear(); - areaItemBlacklist.clear(); - plugin.getLogger().info("[防护] 已清空所有黑名单"); - } - - @EventHandler - public void onEntityDamage( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑 ===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - // 展示框被点击(旋转/放入/取出) - @EventHandler - public void onItemFrameInteract( - PlayerInteractAtEntityEvent e) { - Entity clicked = e.getRightClicked(); - String typeName = clicked.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - Player p = e.getPlayer(); - - // 全局白放行 - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(clicked); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - // 非白名单 → 拦截 - // 记录当前旋转角度 - final Rotation savedRotation; - final Entity frame = clicked; - if (clicked instanceof org.bukkit.entity.ItemFrame) { - savedRotation = - ((org.bukkit.entity.ItemFrame) clicked) - .getRotation(); - } else { - savedRotation = null; - } - - e.setCancelled(true); - - // 下一tick强制恢复旋转 - if (savedRotation != null) { - final Rotation restore = savedRotation; - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (frame.isValid() - && frame instanceof org.bukkit.entity.ItemFrame) { - ((org.bukkit.entity.ItemFrame) frame) - .setRotation(restore); - } - }, 1L); - } - - p.sendMessage("§c§l[区域防护] §f禁止交互展示框"); - - // 驱逐远程敌对生物 - banRangedHostiles(p, ac); - } - - - - // 展示框保护:检查展示框本身+周围方块所属区域 - - // ===== 展示框攻击拦截(左键/投射物破坏)===== - @EventHandler - public void onEntityDamageByEntity( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - // 展示框被攻击 - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑(原有的,不动)===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - private AreaConfig findFrameArea(Entity frame) { - Location loc = frame.getLocation(); - int bx = loc.getBlockX(); - int by = loc.getBlockY(); - int bz = loc.getBlockZ(); - String world = loc.getWorld().getName(); - - AreaConfig ac = getArea(world, bx, by, bz); - if (ac != null && ac.denyItemFrame) return ac; - - int[][] offsets = { - {0, 0, 1}, {0, 0, -1}, - {1, 0, 0}, {-1, 0, 0}, - {0, 1, 0}, {0, -1, 0} - }; - for (int[] off : offsets) { - ac = getArea(world, - bx + off[0], by + off[1], bz + off[2]); - if (ac != null && ac.denyItemFrame) return ac; - } - return null; - } - - // ===== 投射物击中展示框(弓箭/弩)===== - @EventHandler - public void onProjectileHit( - org.bukkit.event.entity.ProjectileHitEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - Entity hit = e.getHitEntity(); - if (hit == null) return; - String typeName = hit.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(shooter.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(hit); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(shooter.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - } - - - // ===== 区域外火焰不能蔓延进区域 ===== - @EventHandler - public void onBlockSpread(BlockSpreadEvent e) { - Block block = e.getBlock(); - AreaConfig tgtArea = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (tgtArea != null && tgtArea.denyFireSpread) { - e.setCancelled(true); - } - } - - // ===== 区域内禁止点燃方块 ===== - @EventHandler - public void onIgnite(BlockIgniteEvent e) { - Player p = e.getPlayer(); - if (p == null) return; // 非玩家点燃的(闪电等) - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f此区域禁止点燃物品"); - } - } - - // ===== 区域内方块着火(TNT、岩浆等)也阻止 ===== - @EventHandler - public void onBlockBurn(BlockBurnEvent e) { - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) - e.setCancelled(true); - } - - - private void savePeaceWhitelist(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("和平") - && trimmed.contains("白名单")) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.peaceWhitelist.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存和平白名单失败: " + e.getMessage()); - } - } - - - @EventHandler - public void onPaintingPlace( - org.bukkit.event.player.PlayerBucketEmptyEvent e) { - // 暂不处理 - } - - - // ==================== 数据类 ==================== - - public static class AreaConfig { - public String name = ""; - public String world = ""; - public int x1, z1, x2, z2; - public int yMin = 0, yMax = 255; - public List confiscateItems = new ArrayList<>(); - public List denyUseItems = new ArrayList<>(); - public List clearEffects = new ArrayList<>(); // 清除单个效果名 - public boolean clearAllBadEffects = false; // 清除所有负面+中性 - public List giveEffects = new ArrayList<>(); // [效果名, 等级, 秒数] - public List punishCommands = new ArrayList<>(); - public boolean denyBlockPlace = false; - public boolean denyBlockBreak = false; - public boolean denyPVP = false; - public boolean denyFallDamage = false; - public boolean denyHunger = false; - public boolean denyAllDamage = false; - public boolean denyMount = false; - public boolean denyDrop = false; - public boolean denyEnderPearl = false; - public boolean denyBow = false; - public boolean denyPotion = false; - public boolean denyExplosion = false; - public boolean enableAnnounce = false; - public String announceTemplate = ""; - public String confiscateMsg = ""; - public String enterMsg = ""; - public String leaveMsg = ""; - public boolean denyRaid = false; - public boolean denyFireSpread = false; - public boolean denyAllEffects = false;; - public boolean peaceMode = false; - public boolean denyItemFrame = false; - public Set peaceWhitelist = new HashSet<>(); - public String enforceGameMode = null; // 强制游戏模式 - public Set modeExempt = new HashSet<>(); // 模式排除名单 - - - - - private static final Map EFFECT_MAP - = new HashMap<>(); - static { - // 负面效果 - EFFECT_MAP.put("缓慢", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("凋零", PotionEffectType.WITHER); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("霉运", PotionEffectType.UNLUCK); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("蓄风", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生", PotionEffectType.INFESTED); - // 中性效果 - EFFECT_MAP.put("不祥之兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("不祥征兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("袭击征兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("试炼征兆", PotionEffectType.TRIAL_OMEN); - // 正面效果 - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); - EFFECT_MAP.put("瞬间治疗", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("跳跃提升", PotionEffectType.JUMP_BOOST); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性提升", PotionEffectType.RESISTANCE); - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("生命提升", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("村庄英雄", PotionEffectType.HERO_OF_THE_VILLAGE); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); - EFFECT_MAP.put("潮涌能量", PotionEffectType.CONDUIT_POWER); - EFFECT_MAP.put("海豚的恩惠", PotionEffectType.DOLPHINS_GRACE); - - - // 带效果二字 - // 补充带"效果"后缀的别名 - EFFECT_MAP.put("黑暗效果", PotionEffectType.DARKNESS); - EFFECT_MAP.put("不祥之兆效果", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆效果", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆效果", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("缓慢效果", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("中毒效果", PotionEffectType.POISON); - EFFECT_MAP.put("凋零效果", PotionEffectType.WITHER); - EFFECT_MAP.put("失明效果", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿效果", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱效果", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("反胃效果", PotionEffectType.NAUSEA); - EFFECT_MAP.put("飘浮效果", PotionEffectType.LEVITATION); - EFFECT_MAP.put("发光效果", PotionEffectType.GLOWING); - EFFECT_MAP.put("霉运效果", PotionEffectType.UNLUCK); - EFFECT_MAP.put("蓄风效果", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝效果", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆效果", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生效果", PotionEffectType.INFESTED); - EFFECT_MAP.put("挖掘疲劳效果", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害效果", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("村庄英雄效果", PotionEffectType.HERO_OF_THE_VILLAGE); - } - - - public int ruleCount() { - int c = 0; - if (!confiscateItems.isEmpty()) c++; - if (denyBlockPlace) c++; - if (denyBlockBreak) c++; - if (denyPVP) c++; - if (denyFallDamage) c++; - if (denyHunger) c++; - if (denyAllDamage) c++; - if (denyDrop) c++; - if (denyMount) c++; - if (denyEnderPearl) c++; - if (denyBow) c++; - if (denyRaid) c++; - if (denyPotion) c++; - if (denyExplosion) c++; - if (denyFireSpread) c++; - if (denyAllEffects) c++; - if (!clearEffects.isEmpty()) c++; - if (clearAllBadEffects) c++; - if (!giveEffects.isEmpty()) c++; - if (enableAnnounce) c++; - if (!punishCommands.isEmpty()) c++; - if (!denyUseItems.isEmpty()) c++; - if (peaceMode) c++; - if (denyItemFrame) c++; - if (!peaceWhitelist.isEmpty()) c++; - - return c; - } - } - } \ No newline at end of file diff --git a/backup/20260605_152529/README.md b/backup/20260605_152529/README.md deleted file mode 100644 index e74ef6a..0000000 --- a/backup/20260605_152529/README.md +++ /dev/null @@ -1,569 +0,0 @@ -# Sdf1系列登录插件使用说明 - -# 特别说明:本插件仅有简中语言版本,如需除简中以外的任何版本,请下载插件源码,自行修改! - -## 1. 功能介绍 - -| 名字 | 描述/作用 | 施工状态 | -| :--- | :--- | :--- | -| 登录 | 用户登录/注册 | 已完成 | -| 世界垃圾箱 | 清理服务器凋落物 | 已完成 | -| 工单系统 | 用户提交工单 | 已完成 | -| 签到系统 | 签到积分换奖励 | 已完成 | -| 新人礼包 | 新玩家7日礼包 | 已完成 | -| 挂机检测 | 挂机检测踢出 | 已完成 | -| 自定义菜单 | 可以自己diy指令 | 已完成 | -| 自定义菜单图标 | 可以用自己喜欢的物品当图标 | 已完工 | -| 商城系统| 内置游戏商人可供销售商品 | 已完工 | -| 虚拟货币 | 供签到、游戏商人使用的货币系统 | 已完工 | - - -## 2.安装说明: - -| 前置插件 | 选装类型 | 功能作用 | -| :--- | :--- | :--- | -| [CY_beibao](https://gitee.com/nihaoshidifu/CY_beibao) | 可选,推荐 | 云背包,积分商城可购买背包容量 -| EssentialsX | 可选 | 主流经济系统 -| [sdf1](https://gitee.com/nihaoshidifu/sdf1_plugn) | 可选,推荐 | CDK兑换系统 - - -### 2.1 下载 -您可以从GitHub和Gitee下载到插件,将其丢入服务器plugin目录,重启服务器即可。 - -### 2.2. 配置文件 - -#### 2.2.1 消息.txt -此文件控制着整个插件的所有提示消息,您可以根据自己的需求修改,修改后需要执行 /sdf1_login reload 重载插件。支持[颜色代码](https://zh.minecraft.wiki/w/%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%BB%A3%E7%A0%81)
-同时兼容` `标签,对段落加粗,`\n 和
`进行段落换行 - - -```txt -# ========================================== -# Sdf1_login 消息配置文件 -# ========================================== -# 颜色代码使用 § 符号 -# §0黑色 §1深蓝 §2深绿 §3深青 -# §4深红 §5深紫 §6金色 §7灰色 -# §8深灰 §9蓝色 §a绿色 §b浅蓝 -# §c红色 §d粉色 §e黄色 §f白色 -# §l粗体 §n下划线 §o斜体 §r重置 -# -# 占位符用 {xxx} 表示,运行时自动替换 -# 支持 粗体 标签 -# 支持 \n 或
换行 -# 修改后执行 /sdf1_login reload 重载 -# ========================================== - -# ---- 登录注册相关 ---- -# login_timeout: 登录超时被踢出时的提示 -login_timeout=§c登录超时,请重新登录 -# not_registered: 未注册玩家收到的提示 -not_registered=§e您尚未注册,请使用 /reg <密码> -# not_logged_in: 已注册但未登录的提示 -not_logged_in=§e您尚未登录,请使用 /login <密码> -# already_registered: 已注册玩家再次注册时的提示 -already_registered=§c您已注册过账号 -# already_logged_in: 已登录玩家再次登录时的提示 -already_logged_in=§c您已登录 -# reg_success: 注册成功提示 -reg_success=§a注册成功!欢迎来到服务器 -# login_success: 登录成功提示 -login_success=§a登录成功! -# login_failed: 密码错误提示 -login_failed=§c密码错误,请重试 - -# ---- 密码相关 ---- -# password_wrong: 旧密码验证失败 -password_wrong=§c密码错误 -# password_changed: 密码修改成功 -password_changed=§a密码修改成功! -# password_format_error: 密码格式不符合要求 -password_format_error=§c密码需6位以上,含大小写字母和数字 -# password_same: 新密码与旧密码相同 -password_same=§c新密码不能与旧密码相同 -# reset_no_email: 找回密码时未绑定邮箱 -reset_no_email=§c您未绑定邮箱,无法找回密码 -# reset_sent: 临时密码已发送到邮箱 -reset_sent=§a临时密码已发送到您的邮箱,请查收 - -# ---- 签到积分相关 ---- -# checkin_already: 今日已签到 -checkin_already=§c您今日已签到 -# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率 -checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi} -# points_insufficient: 积分不足 -points_insufficient=§c积分不足,当前: {points} -# points_purchase_success: 积分购买成功 -points_purchase_success=§a购买成功!消耗 {count} 积分 - -# ---- 挂机检测相关 ---- -# afk_set_enabled: 挂机检测开启 -afk_set_enabled=§a挂机检测已开启 -# afk_set_disabled: 挂机检测关闭 -afk_set_disabled=§c挂机检测已关闭 -# afk_kick: 因挂机被踢出 -afk_kick=§c因挂机过久被踢出 - -# ---- 邮箱相关 ---- -# email_set: 邮箱设置成功 -email_set=§a邮箱设置成功:{email} -# email_format_error: 邮箱格式错误 -email_format_error=§c邮箱格式不正确 - -# ---- 邀请相关 ---- -# invite_code_generated: 邀请码生成 -invite_code_generated=§a邀请码:{code} -# invite_used: 使用邀请码成功 -invite_used=§a邀请成功,邀请人:{player} -# invite_self: 使用自己的邀请码 -invite_self=§c不能使用自己的邀请码 -# invite_not_found: 邀请码不存在 -invite_not_found=§c邀请码不存在 -# invite_referral_bonus: 被邀请人签到后邀请人获得奖励 -invite_referral_bonus=§a邀请奖励!获得 {points} 积分 - -# ---- 新人礼包相关 ---- -# gift_not_ready: 礼包条件未满足 -gift_not_ready=§c礼包条件未满足 -# gift_claimed: 礼包领取成功 -gift_claimed=§a礼包领取成功! - -# ---- 审批与IP限制 ---- -# need_approval: 注册需审批 -need_approval=§e注册需审批 (工单#{id}) -# ip_max_accounts: IP注册已达上限 -ip_max_accounts=§cIP注册已达上限 - -# ---- 管理员操作 ---- -# admin_delete_confirm: 管理员删除账号确认提示 -admin_delete_confirm=§c请在聊天中输入玩家名确认删除 - -# ---- 聊天过滤 ---- -# chat_muted: 禁言玩家尝试聊天 -chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天 -# chat_url_blocked: 发送违规链接被拦截 -# {url}=检测到的链接地址 -chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url} -# chat_url_violation: 当前违规次数 -# {count}=当前违规次数 -chat_url_violation=§7当前违规次数: §e{count} -# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数 -chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次) -# chat_url_broadcast: 全服通报,{player}=玩家名 -chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚 - -# ---- 通用 ---- -# 未知参数: 未知命令或权限不足时的提示 -未知参数=§c未知参数或权限不足 -# only_player: 仅玩家可用的命令 -# only_player=§c此命令仅玩家可用 - -``` - -### 2.2.2 挂机白名单.txt -当挂机踢出开启时,该配置下的玩家不受挂机提出影响 - -### 2.2.3 SMTP设置.txt - -此文件控制着SMTP邮件服务器的配置,用于找回密码时发送邮件。
可通过GUI进行配置,也可以手动修改此配置文件。修改后,重载插件即可生效 - -```txt -# ===== SMTP 邮件服务器配置 ===== -smtp地址=smtp.example.com -smtp端口=465 -smtp账号=mail@example.com -smtp密码=example.com -发件人名称=Sdf1_login -smtp加密=true -验证码接收邮箱=example@example.com - -``` - -### 2.2.4 插件设置.txt - -此配置管控这整个插件,包括玩家能注册多少个账号、默认的管理员面板密码多少、tag包含什么视为管理员等 - -```txt -# ===== Sdf1_login 插件设置 ===== -管理标签=admin -管理密码=qweasd -每IP最大账号数=3 -审批模式=manual -自动审批延迟分钟=30 -登录超时秒数=60 -挂机踢出=false -挂机超时秒数=300 -服务商积分倍率=2.0 -基础经济奖励=100 -经济奖励每评分=50.0 -报单人积分奖励=10 -# ---- 签到盲盒奖励 ---- -签到固定金额=0 -签到最小金额=10 -签到最大金额=100 -签到给积分=true -签到积分数量=10 - - -``` - -### 2.2.5 chat.txt -该文件控制着聊天过滤,当玩家发送违规链接时,会自动根据配置规则进行处理。
-可用的处罚规则: -- warn: 警告 -- mute: 禁言 -- kick: 踢出 -- ban: 封禁 -- banip:封禁IP - -```txt -# Sdf1_chat 聊天过滤配置 -通知管理员: true -全服通报: true -禁言时长: 300 - -白名单: -*.minecraft.net -baidu.com -github.com - -处罚规则: -1:warn:0 -2:warn:0 -3:mute:300 -5:mute:600 -8:kick:0 -10:ban:0 - -白名单玩家: - -``` -### 2.2.6 radio_config.txt -```txt -# ============ Sdf1 Radio Config ============ -# -# resource-pack-url -# 填外部地址(HTTP或HTTPS均可) -# 例: https://example.com/radiopack.zip -# 留空 = 使用内置HTTP服务 -# -# http-port -# 内置HTTP端口(仅resource-pack-url为空时生效) -# -# ============ Settings =============== - -resource-pack-url=https://*.ypshidifu.cn/|*.mcserver.ccwu.cc -http-port=443 -``` -- radio_config这里可以使用通配符域名,当使用通配符时,系统随机生成url,拒绝过资源包的玩家,每次都会收到提示。并且自1.2版本起,拒绝资源包=放弃游玩服务器。如果您无需广播功能,可以不放ogg文件进入`./plugin/radio/ogg`。这样服务端不会自动生成资源包,也不会强制下载了 - -### 2.2.7 菜单.txt -- 自定义菜单配置文件 - -```json -# 服务器菜单 -# 使用类json格式 -// 支持使用://Java单行注释、#yml单行注释、/* */块注释,也支持的块注释 - -{ - 标题:主城传送 - 指令:/dom tp 主城区 # 这里如果是xxx.txt则表示进入二级目录 - 权限类型:玩家 - 图标:COMPASS -} - -{ - 标题:随机传送 - 指令:/rtp - 权限类型:玩家 - 图标:ENDER_PEARL -} - -{ - 标题:擂台比武 - 指令:/dom tp PVP - 权限类型:玩家 - 图标:netherite_spear -} - -{ - 标题:领地操作 - 指令:/dom - 权限类型:玩家 - 图标:paper -} -{ - 标题:逛商店 - 指令:/dom tp shop - 权限类型:玩家 - 图标:honeycomb -} - -{ - 标题:传送开关 - 指令:/tptoggle - 权限类型:玩家 - 图标:redstone_torch -} - -{ - 标题:返回上一个位置 - 指令:/back - 权限类型:玩家 - 图标:red_bed -} -{ - 标题:前往小黑塔 - 指令:/dom tp 小黑塔 - 权限类型:玩家 - 图标:dragon_head -} -{ - 标题:云背包 - 指令:/cy - 权限类型:玩家 - 图标:potato -} -{ - 标题:自杀 - 指令:/suicide - 权限类型:玩家 - 图标:wooden_axe -} -{ - 标题:游戏商人 - 指令:/shop - 权限类型:玩家 - 图标:shield - 格子:28 -} -``` -### 2.2.8 商品配置 -- 配置此处的文件,负责展示游戏内所销售的商品。
-- 一个文件名,对应一个商品分类,使用md文件格式 -``` -# 建筑材料 - -| ID | 品名 | 材质 | 购入价 | 售出价 | 库存 | 本小时销量 | 总销量 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| STONE | 石头 | STONE | 10 | -1 | -1 | 0 | 0 | -| COBBLESTONE | 圆石 | COBBLESTONE | 5 | -1 | -1 | 0 | 26 | -| OAK_PLANKS | 橡木木板 | OAK_PLANKS | 8 | 3 | -1 | 0 | 0 | -| BRICKS | 砖块 | BRICKS | 12 | -1 | -1 | 0 | 0 | -| GLASS | 玻璃 | GLASS | 15 | 6 | -1 | 0 | 0 | -| QUARTZ_BLOCK | 石英块 | QUARTZ_BLOCK | 20 | 8 | -1 | 0 | 0 | -| AMETHYST_BLOCK | 紫水晶 | AMETHYST_BLOCK | 10 | 0 | -1 | 0 | 999 | -| OCHRE_FROGLIGHT | 赭黄蛙鸣灯 | OCHRE_FROGLIGHT | 100 | 0 | -1 | 0 | 1000 | - -``` - -## 2.3 商城部分介绍 -1. 自助退款,买多买错,不用慌,一件退单很安心
-2. 购物车,买的多怕买错,先加购物车,后续统一结算
-3. 害怕订单找不到?一键查看历史订单
- ![](img/shop4.png) -4. 玩家要耍赖皮,拿着小票来找麻烦怎么办?退款订单自动贴标,拒绝白嫖 -![](img/shop7.png)
-![](img/shop6.png) -5. 想查看玩家消费记录?小手一点,明明白白
-![](img/shop5.png) - - - - -## 2.4 打印流水部分介绍 -1. 管理员怀疑某个玩家刷钱了,怎么办?/printer 玩家名,一检查流水,很安心。很多人都刷了怎么办?/printer不加参数,一键导出全服流水,查他个底朝天
-2. 忘记删除怎么办?插件每分钟自动检测,旧文件超过24小时,自动删 - - - -## 3. 指令参数及介绍 - -- Sdf1_login 主指令
-- Sdf1_login reload 重载配置文件
-- reg 注册账号
-- login 登录账号
-- l 登录账号
-- sdf1_login del 删除玩家账号
-- sdf1_login radio 停止播放广播
-- sdf1_login radio reload 重载广播并重新打包资源包
-- menu 开/关 是否开启菜单
-- printer 打印流水数据,支持不带参数&带玩家名为参数
-- sdf1_login printer 作用同printer
-- sdf1_login shop 商城 -- protect 区域防护 - - - -## 4. 自定义菜单物品 -- 打开主界面,选择"我的"进入
-![](img/menu1.png) -- 将你的自定义物品与雪球交换
-![](img/menu2.png) -- 随后系统会给你重新发放菜单,并且此时图标就是你的自定义物品了 - -## 5. pvp战神榜 -- 通过此功能,你可以在服务器里制作大服务器同款的击杀排行榜,而且能精准定位指定区域
-![](img/pvp1.png) -- 并且有着网游同款的击杀通报
-![](img/pvp3.png) -- 播报逻辑是:
-- 3杀
-- 5杀
-- 5杀以上每杀一次都是一条播报
-- 终结者通报,触发连杀的玩家被终结也会有全服通报 - -## 6. 区域防护 -- 通过此功能,您可在服务器里需要防护的地方(比如展示框不想被玩家随便破坏、主城不准玩家袭击等)加以防护。
-- 还能够保护指定区域的玩家不受任何伤害。
- -| 指令 | 参数1| 参数2 | 参数3 | 结果 | -| :---: | :---: | :---: | :---: | :---: | -| protect | 无 | 无 | 无 | 帮助信息 | -| protect | add | 区域名 | 玩家名 | 加白指定玩家到区域 | -| protect | add | 玩家名 | 无| 加白指定玩家为全局 | -| protect | additem | 区域名 | 物品名 | 加黑指定物品到区域 | -| protect | additem | 物品名 | 无 | 加黑指定物品到全局 | -| protect | addname | 区域名 | 名字 | 加白指定生物名字到区域和平模式白名单 | -| protect | addplayer | 区域名 | 玩家名字 | 加白指定玩家到区域强制模式豁免名单 | -| protect | remove | 玩家名字 | 无 | 移除玩家为全白 | -| protect | remove | 区域名 | 玩家名 | 移除玩家为区白 | -| protect | removeitem | 物品名 | 无 | 删除指定物品为全黑 -| protect | removeitem | 区域名 | 物品名 | 删除指定物品为区黑 -| protect | removename | 区域名 | 名字 | 删除指定名字为区域和平模式白名单 | -| protect | removeplayer | 区域名 | 名字 | 删除指定玩家为强制模式豁免名单 | -| protect | 参数不分顺序 | 区名可以写这里也可以写后面 | 玩家名可以写这里也可以写前面 | 完美的宝宝化 | - -示范: -- `/protect add 末地保护区 players` 添加players玩家到末地保护区
-- `/protect removeitem 主城区 TNT` 从主城删了TNT这个黑名单物品
-- `/protect 筑城区 addwhite zhangsan` 把zhangsan加白到主城区的模式豁免名单。
-- 所有参数`任意排序`,`区域名字`随便填写,强大的`兜底`会自动识别"宝宝画板",你可以放心、大胆的乱写 - - -| 参数 | 效果 | -| :---: | :---: | -| 和平模式 | 所有敌对生物全部进虚空 | -| 禁止袭击 | 立即清理玩家身上的袭击效果,传灾厄村民进虚空 | -| 清除XX效果 | 立即清除玩家身上的效果 -| 给与xx效果 | 根据配置文件,给予玩家指定等级、时长的效果 - -## 生物属性对照表 -| 中文名称 | 类型 | 附加属性 | -| :---: | :----: | :----: | -| 溺尸 | 近战/远程 | 亡灵、水生 | -| 骷髅 | 远程 | 亡灵 | -| 流浪者 | 远程 | 亡灵、寒冷群系变种 | -| 沼骸 | 远程 | 亡灵、沼泽变种 | -| 焦骸 | 远程 | 亡灵、沙漠变种、免疫阳光 | -| 掠夺者 | 远程 | 灾厄村民、袭击成员 | -| 卫道士 | 近战 | 灾厄村民、袭击成员 | -| 幻术师 | 远程 | 灾厄村民、Java专属、隐身分身 | -| 旋风人 | 远程 | 试炼密室生成、免疫弹射物、强击退 | -| 监守者 | 近战/声波远程 | 深暗之域、附带盲视效果 | -| 远古守卫者 | 近战/激光远程 | 海底神殿、水下、附加挖掘疲劳 | -| 守卫者 | 近战/激光远程 | 海底神殿、水下 | - - -配置文件解析:
-- 支持注释格式:`Java的//单行注释`、`yml的#单行注释`、`HTML的注释`和`Java/* */块注释` -```txt -# 区域: 主城区 -起点: world,-130,-713 -终点: world,-427,-560 -高度范围: 0-255 - -没收物品: 蘑菇,毒蘑菇 #没收哪些物品(末地推荐这两个) - // 禁止放置方块 #保护区内,非区白/全白玩家禁止放置方块 -// 禁止破坏方块 # 同上 -禁止PVP # 玩家之间不能PVP -禁止摔伤 # 摔落无法造成任何伤害 -禁止饥饿 # 不会消耗饱食度 -禁止一切伤害 # 任何怪物都无法造成伤害 -# 禁止丢弃物品 -# 禁止末影珍珠 -# 禁止使用弓箭 -# 禁止骑乘 -// 和平模式 // 所有的敌对生物全部传送进虚空 -禁止袭击 -禁止交互展示框 # 核心,任何玩家不能触碰展示框。区白/全白玩家除外 -// 清除所有效果 # 清理所有负面效果 -效果 村庄英雄 255 999999 # 给予玩家效果。格式为: 效果 效果名字 等级(最高255) 时间 -效果 生命提升 255 999999 -效果 抗性提升 255 999999 -效果 抗火 255 999999 -# 禁止药水 -禁止爆炸 -禁止使用物品: TNT -# 效果: 夜视 1 999 -进入提示: 欢迎来到保护区 # 是否开启进入提示,即自定义提示 -离开提示: 已离开保护区 - 没收提示: 你的违禁品已被没收 - 通报批评: §c§l{player} §f在{area}违规携带违禁品,被没收 §e{count}§f件! # 通报违禁品。{player} 玩家名 {count}数量 {area}区域 -# 惩罚命令: ban {player} 60s 违规 - - -``` - -## 运行截图 - -主界面 -![](img/login.png) - -邀请数据 -![](img/login2.png) - -积分商店 -![](img/login3.png) - -任务中心 -![](img/login4.png) - -垃圾箱 -![](img/login5.png) - -管理员面板 -![](img/login6.png) - -管理面板-用户管理 -![](img/login7.png) - - -工单中心 -![](img/login8.png) - -工单管理 -![](img/login9.png) - -我的工单 -![](img/login10.png) - - -提交工单 -![](img/login11.png) - -服务商列表 -![](img/login12.png) - -服务商抢单大厅 -![](img/login13.png) - -服务商-接单界面 -![](img/login14.png) - -管理员-工单详情页面 -![](img/login15.png) - -商城主界面 -![](img/shop1.png) - -商城购物车 -![](img/cart1.png) - -商城退单 -![](img/shop2.png) - - -## 4. 下载地址 -- GitHub:[https://github.com/ypsdf1/sdf1_login](https://github.com/ypsdf1/sdf1_login) -- Gitee:[https://gitee.com/nihaoshidifu/sdf1_login](https://gitee.com/nihaoshidifu/sdf1_login) diff --git a/backup/20260605_152529/pom.xml b/backup/20260605_152529/pom.xml deleted file mode 100644 index 9d5c185..0000000 --- a/backup/20260605_152529/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - sdf1 - Sdf1_login - 1.0 - jar - - - 21 - UTF-8 - - - - - papermc - https://repo.papermc.io/repository/maven-public/ - - - jitpack.io - https://jitpack.io - - - - - - io.papermc.paper - paper-api - 1.21.4-R0.1-SNAPSHOT - provided - - - - com.github.MilkBowl - VaultAPI - 1.7.1 - provided - - - org.xerial - sqlite-jdbc - 3.46.0.0 - provided - - - - - - Sdf1_login - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 21 - 21 - - - - - diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/AFKManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/AFKManager.java deleted file mode 100644 index f714c1b..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/AFKManager.java +++ /dev/null @@ -1,197 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; - -public class AFKManager { - - // 玩家最后活动时间(空闲才开始计时) - private final Map lastAction = - new HashMap<>(); - // 每个玩家当前的警告阶段 - private final Map lastStage = - new HashMap<>(); - // 是否已进入挂机检测(false=还在活动,不计时) - private final Map afkTracking = - new HashMap<>(); - - private final ConfigManager config; - private final JavaPlugin plugin; - - public AFKManager(ConfigManager config, - JavaPlugin plugin) { - this.config = config; - this.plugin = plugin; - } - - public void startCheck() { - new BukkitRunnable() { - public void run() { - tick(); - } - }.runTaskTimer(plugin, 20L, 20L); - } - - private void tick() { - if (!config.afkEnabled) return; - long now = System.currentTimeMillis(); - - for (Player p : Bukkit.getOnlinePlayers()) { - String name = p.getName(); - UUID uuid = p.getUniqueId(); - - if (!isLoggedIn(name)) continue; - if (config.getAfkWhitelist().contains(name)) - continue; - - // 确保有记录 - if (!lastAction.containsKey(uuid)) { - lastAction.put(uuid, now); - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - continue; - } - - long last = lastAction.get(uuid); - long idle = (now - last) / 1000; - int timeout = config.afkTimeout; - - // 还在活动范围内:不进入挂机检测 - if (idle < 60) { - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - continue; - } - - // 超过60秒无活动:开始挂机检测 - afkTracking.put(uuid, true); - long remain = timeout - idle; - - // 已超时:踢出 - if (remain <= 0) { - p.kickPlayer("§c§l你已挂机 " - + fmtSec(idle) + ",被踢出"); - lastAction.remove(uuid); - lastStage.remove(uuid); - afkTracking.remove(uuid); - continue; - } - - int stage = lastStage.getOrDefault(uuid, 0); - - // 阶段3:最后10秒 - if (remain <= 10) { - if (stage < 3) { - lastStage.put(uuid, 3); - } - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + remain + " 秒踢出", - 0, 22, 3); - playSound(p); - continue; - } - - // 阶段2:1分钟~10秒 - if (remain <= 60) { - if (stage < 2) { - lastStage.put(uuid, 2); - p.sendMessage( - "§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" - + fmtSec(idle) - + "§f,还剩 §c" - + fmtSec(remain) - + "§f踢出"); - } - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + fmtSec(remain) + " 踢出", - 5, 25, 5); - playSound(p); - continue; - } - - // 阶段1:3分钟提醒 - if (remain <= 180 && stage < 1) { - lastStage.put(uuid, 1); - p.sendMessage( - "§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" - + fmtSec(idle) - + "§f,还剩 §c" - + fmtSec(remain) - + "§f踢出"); - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§f还剩 " + fmtSec(remain) + " 踢出", - 10, 60, 20); - for (int i = 0; i < 3; i++) { - final int delay = i * 10; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - if (p.isOnline()) playSound(p); - }, delay); - } - } - - if (remain > 180) { - lastStage.remove(uuid); - } - } - } - - /** - * 玩家有活动时调用(移动/交互/放置/挖掘等) - * 重置计时,退出挂机检测 - */ - public void recordAction(UUID uuid) { - lastAction.put(uuid, System.currentTimeMillis()); - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - } - - /** - * 玩家下线时调用 - * 彻底停止计时,清理所有数据 - */ - public void remove(UUID uuid) { - lastAction.remove(uuid); - lastStage.remove(uuid); - afkTracking.remove(uuid); - } - - /** - * 获取玩家当前是否在挂机检测中 - */ - public boolean isAfkTracking(UUID uuid) { - return afkTracking.getOrDefault(uuid, false); - } - - private boolean isLoggedIn(String name) { - return ((Main) plugin).getLoggedIn() - .contains(name); - } - - private void playSound(Player p) { - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_CHIME, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_BASS, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_PLING, - 1.0f, 0.79f); - } - - private String fmtSec(long sec) { - if (sec < 60) return sec + "秒"; - long m = sec / 60; - long s = sec % 60; - return m + "分" + (s > 0 ? s + "秒" : ""); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/AccountRequestManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/AccountRequestManager.java deleted file mode 100644 index ae5eaad..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/AccountRequestManager.java +++ /dev/null @@ -1,146 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class AccountRequestManager { - - public enum ApprovalMode { AUTO, MANUAL, HYBRID } - - public static class Request { - public final int id; - public final String requester; - public final String target; - public final String ip; - public final long time; - public String status; - public String denyReason; - - public Request(int id, String requester, String target, String ip) { - this.id = id; - this.requester = requester; - this.target = target; - this.ip = ip; - this.time = System.currentTimeMillis(); - this.status = "PENDING"; - this.denyReason = ""; - } - } - - private final Main plugin; - private final Map requests = new ConcurrentHashMap<>(); - private final Map pendingByPlayer = new ConcurrentHashMap<>(); - private int nextId = 1; - private ApprovalMode mode = ApprovalMode.MANUAL; - private int autoDelayMinutes = 60; - - public AccountRequestManager(Main plugin) { - this.plugin = plugin; - } - - public ApprovalMode getMode() { return mode; } - - public void setMode(String m) { - try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); } - catch (Exception e) { this.mode = ApprovalMode.MANUAL; } - } - - public void setAutoDelayMinutes(int min) { - this.autoDelayMinutes = min; - } - - public Request createRequest(String requester, String target, String ip) { - int id = nextId++; - Request req = new Request(id, requester, target, ip); - requests.put(id, req); - pendingByPlayer.put(requester, id); - - if (mode == ApprovalMode.AUTO) { - scheduleAutoApprove(id); - } else if (mode == ApprovalMode.HYBRID) { - String targetIp = (String) plugin.getDb().getField(target, "ip_address"); - if (ip != null && ip.equals(targetIp)) { - scheduleAutoApprove(id); - } - } - return req; - } - - private void scheduleAutoApprove(int id) { - long delayTicks = (long) autoDelayMinutes * 60L * 20L; - Request req = requests.get(id); - if (req == null) return; - String requesterName = req.requester; - String targetName = req.target; - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过"); - - Bukkit.getScheduler().runTaskLater(plugin, () -> { - Request r = requests.get(id); - if (r != null && "PENDING".equals(r.status)) { - approveRequest(id); - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过"); - Player requester = Bukkit.getPlayer(requesterName); - if (requester != null && requester.isOnline()) { - requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!"); - } - } - }, delayTicks); - } - - public boolean approveRequest(int id) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "APPROVED"; - plugin.getDb().deleteUser(req.target); - pendingByPlayer.remove(req.requester); - // 清背包+传送出生点+踢出 - Player target = Bukkit.getPlayer(req.target); - if (target != null && target.isOnline()) { - target.getInventory().clear(); - target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]); - target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]); - target.teleport(target.getWorld().getSpawnLocation()); - target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册"); - } - return true; - } - - - public boolean denyRequest(int id, String reason) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "DENIED"; - req.denyReason = reason; - pendingByPlayer.remove(req.requester); - return true; - } - - public List getPendingRequests() { - List list = new ArrayList<>(); - for (Request r : requests.values()) { - if ("PENDING".equals(r.status)) list.add(r); - } - return list; - } - - public Request getRequest(int id) { - return requests.get(id); - } - - public boolean hasPending(String player) { - return pendingByPlayer.containsKey(player); - } - - public void cancelPending(String player) { - Integer id = pendingByPlayer.remove(player); - if (id != null) { - Request r = requests.get(id); - if (r != null) r.status = "CANCELLED"; - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/AreaProtection.java b/backup/20260605_152529/src/main/java/Sdf1_login/AreaProtection.java deleted file mode 100644 index bc53d53..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/AreaProtection.java +++ /dev/null @@ -1,4907 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.event.block.BlockBurnEvent; -import org.bukkit.event.block.BlockIgniteEvent; -import org.bukkit.event.block.BlockSpreadEvent; -import org.bukkit.entity.Entity; -import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import java.sql.*; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.GameMode; -import org.bukkit.event.EventPriority; - - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; -import java.util.Iterator; -import java.util.Set; -import java.util.HashSet; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.event.entity.EntitySpawnEvent; -import org.bukkit.entity.Item; - - - - -public class AreaProtection implements Listener { - - private final Main plugin; - public List giveEffects = new ArrayList<>(); - private final Map> playerAreas - = new HashMap<>(); - private final Map areas = - new ConcurrentHashMap<>(); - private Connection dbConnection; - - // 全局白名单(玩家) - private final Set globalPlayerWhitelist = - ConcurrentHashMap.newKeySet(); - // 区域白名单 key=区域名 value=玩家名集合 - private final Map> areaPlayerWhitelist = - new ConcurrentHashMap<>(); - // 全局黑名单(物品ID) - private final Set globalItemBlacklist = - ConcurrentHashMap.newKeySet(); - // 区域黑名单 - private final Map> areaItemBlacklist = - new ConcurrentHashMap<>(); - - // 选地 - private static final Material WAND = Material.BREEZE_ROD; - - private final Map pos1 = - new ConcurrentHashMap<>(); - private final Map pos2 = - new ConcurrentHashMap<>(); - private final Set selecting = - ConcurrentHashMap.newKeySet(); - public boolean denyRaid = false; - // ===== 统一文件夹 ===== - private final File rootDir; - private final File whitelistDir; - private final File globalWhitelistFile; - private final File oldDbFile; - private final File newDbFile; - - // 边框显示 - private final Map displayTaskIds = - new ConcurrentHashMap<>(); - private final Map> globalWhitelist - = new HashMap<>(); - private final Map> areaWhitelist - = new HashMap<>(); - private final Map protectedEntities - = new ConcurrentHashMap<>(); - // 区域给予的效果记录(贴标) - private final Map> playerAppliedEffects = new HashMap<>(); - // 延时清理任务 - private static final long MOVE_DEBOUNCE_MS = 100; - // 新:全部改为线程安全 - private final Map playerCurrentArea - = new ConcurrentHashMap<>(); - private final Map> playerMarkedEffects - = new ConcurrentHashMap<>(); - private final Map pendingClearTask - = new ConcurrentHashMap<>(); - private final Map pendingClearArea - = new ConcurrentHashMap<>(); - private final Map lastMoveProcess - = new ConcurrentHashMap<>(); - // 防止传送时重复处理 - private final Set teleporting - = ConcurrentHashMap.newKeySet(); - // 待清理的效果列表(与延时任务配合) - private final Map> pendingClearEffects - = new ConcurrentHashMap<>(); - - - -// ==================== 强制游戏模式 ==================== - - /** - * 监听玩家尝试切换游戏模式 - * 如果在强制游戏模式区域且不在豁免名单中,则取消切换 - */ - // 防止递归的标记 - private final Set modeSwitching - = ConcurrentHashMap.newKeySet(); - @EventHandler(priority = EventPriority.HIGHEST) - public void onGameModeChange( - PlayerGameModeChangeEvent event) { - if (event.isCancelled()) return; - - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - if (modeSwitching.contains(uid)) return; - - String areaName = playerCurrentArea.get(uid); - if (areaName == null) return; - - AreaConfig ac = areas.get(areaName); - if (ac == null) return; - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) return; - if (isExemptFromGameMode(p, areaName)) return; - - event.setCancelled(true); - - modeSwitching.add(uid); - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } finally { - modeSwitching.remove(uid); - } - - // ★ 切换后发一次中文提示 - p.sendMessage(formatAreaMsg( - "&c该区域强制 " + ac.enforceGameMode - + " 模式,无法切换")); - } - - public Set getAlreadyForced() { - return alreadyForced; - } - - - // ==================== 定时强制游戏模式 ==================== - - private BukkitTask enforceTask; - - /** - * 启动定时强制任务 - * 每秒检查一次所有在线玩家 - */ - private BukkitTask enforceTask1; - - // 记录上次强制模式的玩家(避免重复发消息) - private final Set alreadyForced - = ConcurrentHashMap.newKeySet(); - - public void startEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - } - enforceTask = new BukkitRunnable() { - @Override - public void run() { - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String areaName = - playerCurrentArea.get(uid); - if (areaName == null) { - // 不在任何区域,清除标记 - alreadyForced.remove(uid); - continue; - } - - AreaConfig ac = areas.get(areaName); - if (ac == null) continue; - if (ac.enforceGameMode == null - || ac.enforceGameMode - .isEmpty()) continue; - if (isExemptFromGameMode( - p, areaName)) { - alreadyForced.remove(uid); - continue; - } - - GameMode target = - GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - modeSwitching.add(uid); - try { - p.setGameMode(target); - } finally { - modeSwitching.remove(uid); - } - - // ★ 只在玩家已被强制过时才发消息 - // 首次不发,后续才提醒 - if (alreadyForced.contains(uid)) { - p.sendMessage(formatAreaMsg( - "&c该区域强制 " - + ac.enforceGameMode - + " 模式,无法切换")); - } - alreadyForced.add(uid); - } else { - // 模式一致,清除标记 - alreadyForced.remove(uid); - } - } - } - }.runTaskTimer(plugin, 20L, 20L); - } - - public void stopEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - enforceTask = null; - } - } - - - - /** - * 检查玩家是否豁免于游戏模式强制 - * 豁免条件:全局白名单 / 区域白名单 / 模式排除名单 - */ - private boolean isExemptFromGameMode( - Player p, String areaName) { - String name = p.getName(); - - /* if (p.isOp()) { - plugin.getLogger().info( - "[防护-调试] 豁免原因: OP, 玩家=" + name); - return true; - }*/ - if (globalPlayerWhitelist.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 全局白名单, 玩家=" - + name);*/ - return true; - } - Set aw = - areaPlayerWhitelist.get(areaName); - if (aw != null && aw.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 区域白名单, 玩家=" - + name + " 区域=" + areaName);*/ - return true; - } - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 模式排除名单, 玩家=" - + name);*/ - return true; - } - /* plugin.getLogger().info( - "[防护-调试] 无豁免, 玩家=" + name);*/ - return false; - } - - /** - * 玩家进入区域时,如果该区域强制游戏模式, - * 则立即设置(排除豁免玩家) - */ - private void applyEnforcedGameMode(Player p, - String areaName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: ac=null, area=" - + areaName);*/ - return; - } - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: 未配置强制模式, area=" - + areaName);*/ - return; - } - - boolean exempt = isExemptFromGameMode( - p, areaName); - /* plugin.getLogger().info( - "[防护-调试] applyMode: 玩家=" + p.getName() - + " 区域=" + areaName - + " 强制=" + ac.enforceGameMode - + " 当前=" + p.getGameMode() - + " 豁免=" + exempt - + " OP=" + p.isOp() - + " 全白=" + globalPlayerWhitelist - .contains(p.getName()));*/ - - if (exempt) return; - - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - /* plugin.getLogger().info( - "[防护] 强制切换 " + p.getName() - + " → " + ac.enforceGameMode);*/ - } - } catch (Exception e) { - /* plugin.getLogger().warning( - "[防护] 强制模式失败: " - + e.getMessage());*/ - } - } - - public AreaProtection(Main plugin) { - this.plugin = plugin; - - // 统一根目录 - rootDir = new File(plugin.getDataFolder(), "区域防护"); - if (!rootDir.exists()) rootDir.mkdirs(); - - // 白名单子目录 - whitelistDir = new File(rootDir, "whitelists"); - if (!whitelistDir.exists()) whitelistDir.mkdirs(); - globalWhitelistFile = new File( - whitelistDir, "全局白名单.txt"); - - // 数据库迁移 - oldDbFile = new File( - plugin.getDataFolder(), "area_effects.db"); - newDbFile = new File(rootDir, "area_effects.db"); - migrateDatabase(); - - // 初始化 - initDatabase(); - writeDefaultConfig(); - loadAllAreas(); - loadWhitelists(); - recoverPendingEffects(); - } - public void recoverPendingEffects() { - try { - // 1. 遍历所有在线玩家(而不是遍历DB记录) - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String playerName = p.getName(); - - // 2. 获取玩家当前所在区域 - AreaConfig currentAc = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - - // 3. 获取该玩家在DB中的所有历史记录 - List recordedAreas = - getPlayerAreas(uid); - - // 4. 清理不在当前区域的旧记录 - for (String recordedArea : recordedAreas) { - if (currentAc == null - || !currentAc.name - .equals(recordedArea)) { - // 玩家不在这个区域了 → 清DB - removePlayerEffects( - uid, recordedArea); - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在" + recordedArea - + ", 清理旧记录"); - } - } - - // 5. 如果在区域中 → 恢复/给予效果 - if (currentAc != null) { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 在" + currentAc.name - + ", 恢复效果"); - applyRegionEffects(p, uid, - currentAc.name, currentAc); - } else { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在任何区域, 跳过"); - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 恢复效果失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - // 取消延时清理任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 清标记 - playerMarkedEffects.remove(uid); - playerCurrentArea.remove(uid); - } - - -// ==================== 数据库迁移 ==================== - - /** - * 如果旧位置有 db 文件,移到新位置 - * 如果新位置已有,不动 - * 如果旧位置也没有,在新位置创建 - */ - - private void migrateDatabase() { - if (newDbFile.exists()) { - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - oldDbFile.delete(); - } - return; - } - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - try { - java.nio.file.Files.move( - oldDbFile.toPath(), - newDbFile.toPath(), - java.nio.file.StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 迁移数据库失败: " - + e.getMessage()); - } - } - } - - - // 保存玩家进入区域时给予的效果 - private void savePlayerEffects(UUID uid, String playerName, - String areaName, List effects) { - try { - // 先删除该区域旧记录,避免重复 - PreparedStatement del = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - del.setString(1, uid.toString()); - del.setString(2, areaName); - del.executeUpdate(); - del.close(); - - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)"); - long now = System.currentTimeMillis(); - for (PotionEffectType type : effects) { - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, type.getName()); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, now); - ps.addBatch(); - } - ps.executeBatch(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 保存效果失败: " + e.getMessage()); - } - } - - // 查询玩家在某个区域获得的效果 - private List getPlayerEffects( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - PotionEffectType type = PotionEffectType - .getByName(rs.getString("effect_type")); - if (type != null) list.add(type); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - // 删除玩家在某个区域的效果记录 - private void removePlayerEffects(UUID uid, String areaName) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除效果失败: " + e.getMessage()); - } - } - - // 删除玩家所有效果记录 - private void removeAllPlayerEffects(UUID uid) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除全部效果失败: " + e.getMessage()); - } - } - - // 获取玩家所有记录的区域 - private List getPlayerAreas(UUID uid) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT DISTINCT area_name " - + "FROM player_effects WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("area_name")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询区域失败: " + e.getMessage()); - } - return list; - } - - // 查询所有待处理的效果(跨服重启也能恢复) - private List getAllPendingEffects() { - List list = new ArrayList<>(); - try { - Statement stmt = dbConnection.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT uuid, player_name, area_name, " - + "effect_type FROM player_effects"); - while (rs.next()) { - list.add(new String[]{ - rs.getString("uuid"), - rs.getString("player_name"), - rs.getString("area_name"), - rs.getString("effect_type")}); - } - rs.close(); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询全部效果失败: " + e.getMessage()); - } - return list; - } - - - private void writeDefaultConfig() { - File f = new File(rootDir, "末地保护区.txt"); - if (f.exists()) return; - - String endWorld = "world_the_end"; - for (org.bukkit.World w : Bukkit.getWorlds()) { - if (w.getEnvironment() - == org.bukkit.World.Environment.THE_END) { - endWorld = w.getName(); - break; - } - } - - final String ew = endWorld; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 末地保护区"); - pw.println("起点: " + ew + ",-30000000,-30000000"); - pw.println("终点: " + ew + ",30000000,30000000"); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("没收物品: BROWN_MUSHROOM,RED_MUSHROOM,MUSHROOM_STEW"); - pw.println("没收提示: 你携带了违禁品「蘑菇」,已被全部没收!"); - pw.println("通报批评: {player} 在末地违规携带蘑菇!"); - pw.println(); - pw.println("禁止放置方块"); - pw.println("禁止破坏方块"); - pw.println("禁止PVP"); - pw.println("禁止袭击"); - pw.println(); - pw.println("效果: 夜视 1 999"); - pw.println("进入提示: 末地保护区 - 请遵守规则"); - pw.println("离开提示: 已离开末地保护区"); - pw.close(); - plugin.getLogger().info("[防护] 生成末地配置, 世界: " + ew); - } catch (IOException ignored) { - } - } - - public void reload() { - loadAllAreas(); - loadWhitelists(); - // 强制输出醒目的中文日志 - plugin.getLogger().info("========================================"); - plugin.getLogger().info("[防护] 已重载 " + areas.size() + " 个区域"); - plugin.getLogger().info("[防护] 已重载白名单"); - plugin.getLogger().info("========================================"); - } - -// ==================== 消息格式化 ==================== - - /** - * 格式化区域消息 - * 1. & → §(颜色符号转换) - * 2.
和 \n → 换行 - * 3. 内容 → §l内容§r(加粗) - * 4. 自动添加 [区域防护] 前缀 - */ - private String formatAreaMsg(String raw) { - if (raw == null || raw.isEmpty()) return ""; - - String msg = raw; - - // & → § - for (int i = 0; i <= 9; i++) { - msg = msg.replace("&" + i, "§" + i); - } - for (char c : "abcdefklnor".toCharArray()) { - msg = msg.replace("&" + c, "§" + c); - } - - // 换行 - msg = msg.replace("
", "\n"); - msg = msg.replace("\\n", "\n"); - - // HTML → Minecraft格式(栈式解析,支持嵌套) - msg = convertHtmlTags(msg); - - // 前缀 - msg = "§c§l【区域防护】§r " + msg; - - return msg; - } - - /** - * 栈式HTML标签解析 - * 处理 text rest 这样的嵌套标签 - * 内层关闭时重新应用外层格式 - */ - private String convertHtmlTags(String msg) { - StringBuilder sb = new StringBuilder(); - java.util.Deque stack = - new ArrayDeque<>(); - int i = 0; - - while (i < msg.length()) { - if (msg.charAt(i) == '<' - && i + 1 < msg.length()) { - if (msg.charAt(i + 1) == '/') { - // 关闭标签
- int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 2, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0 && !stack.isEmpty() - && stack.peek() == code) { - stack.pop(); - if (!stack.isEmpty()) { - // 重置后重新应用外层格式 - sb.append("§r"); - for (char c : stack) { - sb.append("§").append(c); - } - } else { - sb.append("§r"); - } - } - i = end + 1; - } else { - // 开放标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 1, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0) { - stack.push(code); - sb.append("§").append(code); - } - i = end + 1; - } - } else { - sb.append(msg.charAt(i)); - i++; - } - } - return sb.toString(); - } - - private char htmlToMc(String tag) { - switch (tag) { - case "b": return 'l'; - case "i": return 'o'; - case "u": return 'n'; - case "s": return 'm'; - default: return 0; - } - } - -// =============== 加载 ==================== - - public void loadAllAreas() { - areas.clear(); - File[] files = rootDir.listFiles( - (File d, String n) -> n.endsWith(".txt")); - if (files == null) return; - for (File f : files) { - try { - String name = f.getName() - .replace(".txt", ""); - AreaConfig ac = parseArea(name, f); - areas.put(name, ac); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载失败: " + f.getName()); - } - } - } - - - private void initDatabase() { - try { - dbConnection = DriverManager.getConnection( - "jdbc:sqlite:" + newDbFile.getAbsolutePath()); - Statement stmt = dbConnection.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS player_effects (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "uuid TEXT NOT NULL," - + "player_name TEXT," - + "area_name TEXT NOT NULL," - + "effect_type TEXT NOT NULL," - + "effect_level INTEGER DEFAULT 0," - + "effect_duration INTEGER DEFAULT 999," - + "enter_time INTEGER NOT NULL)"); - stmt.executeUpdate( - "CREATE INDEX IF NOT EXISTS idx_uuid " - + "ON player_effects(uuid)"); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().severe( - "[防护] 数据库初始化失败: " - + e.getMessage()); - } - } - - -// ==================== 效果名称中英文映射 ==================== - - /** - * 将中文效果名转为 Bukkit 能识别的英文名 - */ - private String resolveEffectName(String name) { - if (name == null) return null; - String n = name.trim(); - // 如果已经是英文或命名空间格式,直接用 - if (n.contains(":") - || n.equals(n.toUpperCase())) { - return n; - } - // 中文 → 英文映射 - switch (n) { - case "伤害吸收": return "ABSORPTION"; - case "生命提升": return "HEALTH_BOOST"; - case "抗性提升": return "DAMAGE_RESISTANCE"; - case "抗火": return "FIRE_RESISTANCE"; - case "夜视": return "NIGHT_VISION"; - case "力量": return "INCREASE_DAMAGE"; - case "速度": return "SPEED"; - case "急迫": return "FAST_DIGGING"; - case "跳跃提升": return "JUMP"; - case "再生": return "REGENERATION"; - case "生命恢复": return "REGENERATION"; - case "治疗": return "HEAL"; - case "瞬间治疗": return "HEAL"; - case "伤害药水": return "HARM"; - case "瞬间伤害": return "HARM"; - case "虚弱": return "WEAKNESS"; - case "中毒": return "POISON"; - case "缓慢": return "SLOW"; - case "挖掘疲劳": return "SLOW_DIGGING"; - case "失明": return "BLINDNESS"; - case "隐身": return "INVISIBILITY"; - case "发光": return "GLOWING"; - // 村庄英雄 - case "村庄英雄": return "HERO_OF_THE_VILLAGE"; - case "幸运": return "LUCK"; - case "不幸": return "UNLUCK"; - case "水下呼吸": return "WATER_BREATHING"; - case "水下速掘": return "DOLPHINS_GRACE"; - case "缓降": return "LEVITATION"; - case "漂浮": return "LEVITATION"; - case "饱和": return "SATURATION"; - // 无效效果 - case "伤害": return "HARM"; - case "治疗药水": return "HEAL"; - default: - // 尝试直接用英文匹配 - return n; - } - } - - /** - * 全量容错:把全角字符统一转半角, - * 去掉零宽字符/不可见字符, - * 让用户怎么写都能正确解析。 - */ - private String normalizeLine(String input) { - if (input == null || input.isEmpty()) return ""; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - - // 跳过零宽字符和不可见字符 - if (c == '\u200B' // 零宽空格 - || c == '\u200C' // 零宽非连接 - || c == '\u200D' // 零宽连接 - || c == '\uFEFF' // BOM - || c == '\u00A0' // 不间断空格 - || c == '\u2028' // 行分隔符 - || c == '\u2029' // 段分隔符 - || c == '\u200E' // LTR标记 - || c == '\u200F' // RTL标记 - || c == '\u2060' // 字连接符 - || (c >= '\u2000' && c <= '\u200A')) { // 各种空格 - continue; - } - - // 全角→半角映射 - if (c >= '\uFF01' && c <= '\uFF5E') { - c = (char) (c - 0xFEE0); - } - - // 全角空格→半角空格 - if (c == '\u3000') { - c = ' '; - } - - sb.append(c); - } - - return sb.toString(); - } - - - private AreaConfig parseArea(String name, File f) - throws IOException { - AreaConfig ac = new AreaConfig(); - ac.name = name; - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean inBlockComment = false; - boolean inHtmlComment = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty()) continue; -// ===== 全量容错预处理:全角→半角,去零宽字符 ===== - line = normalizeLine(line); - if (line.isEmpty()) continue; -// ===== 暴力调试:输出所有包含关键词的行 ===== - if (line.indexOf("游戏") >= 0 - || line.indexOf("模式") >= 0 - || line.indexOf("生存") >= 0 - || line.indexOf("创造") >= 0 - || line.indexOf("排除") >= 0 - || line.indexOf("豁免") >= 0 - || line.indexOf("强制") >= 0 - || line.indexOf("GameMode") >= 0 - || line.indexOf("SURVIVAL") >= 0 - || line.indexOf("CREATIVE") >= 0) { - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < line.length(); i++) { - hex.append(String.format("U+%04X ", (int) line.charAt(i))); - } - /* plugin.getLogger().info("[暴力调试] 行=[" + line - + "] 长度=" + line.length() - + " 字符码=" + hex.toString());*/ - } -// ===== 兜底扫描:确保和平白名单一定能解析 ===== - if (line.indexOf("白名单") >= 0 - || line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - int ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf('='); - if (ci < 0) ci = line.indexOf(' '); - - // 和平白名单 - if (line.indexOf("和平") >= 0 - && line.indexOf("白名单") >= 0 - && ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.peaceWhitelist.add(nm); - } - plugin.getLogger().info("[解析兜底] 和平白名单=" - + ac.peaceWhitelist); - } - - // 排除名单 - if (line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - if (ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.modeExempt.add(nm); - } - plugin.getLogger().info("[解析兜底] 排除名单=" - + ac.modeExempt); - } - } - } -// ===== 兜底结束 ===== - - if (inBlockComment) { - int endIdx = line.indexOf("*/"); - if (endIdx >= 0) { - inBlockComment = false; - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("/*")) { - int endIdx = line.indexOf("*/", 2); - if (endIdx >= 0) { - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - inBlockComment = true; - continue; - } - } - if (inHtmlComment) { - int endIdx = line.indexOf("-->"); - if (endIdx >= 0) { - inHtmlComment = false; - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("", 4); - if (endIdx >= 0) { - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - inHtmlComment = true; - continue; - } - } - if (line.startsWith("#") || line.startsWith("//")) { - continue; - } - int hashIdx = line.indexOf("#"); - if (hashIdx > 0) { - line = line.substring(0, hashIdx).trim(); - } - int slashIdx = line.indexOf("//"); - if (slashIdx > 0) { - line = line.substring(0, slashIdx).trim(); - } - if (line.isEmpty()) continue; - - if (line.startsWith("起点:")) { - String[] pp = line.substring("起点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x1 = Integer.parseInt(pp[1].trim()); - ac.z1 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("终点:")) { - String[] pp = line.substring("终点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x2 = Integer.parseInt(pp[1].trim()); - ac.z2 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("高度范围:")) { - String[] pp = line.substring("高度范围:".length()) - .trim().split("-"); - if (pp.length == 2) { - ac.yMin = Integer.parseInt(pp[0].trim()); - ac.yMax = Integer.parseInt(pp[1].trim()); - } - } else if (line.startsWith("没收物品:")) { - for (String s : line.substring("没收物品:".length()) - .trim().split("[,,]")) - ac.confiscateItems.add( - s.trim().toUpperCase()); - } else if (line.startsWith("禁止使用物品:")) { - for (String s : line.substring("禁止使用物品:".length()) - .trim().split("[,,]")) - ac.denyUseItems.add( - s.trim().toUpperCase()); - } else if (line.equals("禁止放置方块")) { - ac.denyBlockPlace = true; - } else if (line.equals("禁止破坏方块")) { - ac.denyBlockBreak = true; - } else if (line.equals("禁止PVP")) { - ac.denyPVP = true; - } else if (line.equals("禁止摔伤")) { - ac.denyFallDamage = true; - } else if (line.equals("禁止饥饿")) { - ac.denyHunger = true; - } else if (line.equals("禁止一切伤害")) { - ac.denyAllDamage = true; - } else if (line.equals("禁止丢弃物品")) { - ac.denyDrop = true; - } else if (line.equals("禁止末影珍珠")) { - ac.denyEnderPearl = true; - } else if (line.equals("禁止使用弓箭")) { - ac.denyBow = true; - } else if (line.equals("禁止骑乘")) { - ac.denyMount = true; - } else if (line.equals("禁止袭击")) { - ac.denyRaid = true; - } else if (line.equals("禁止药水")) { - ac.denyPotion = true; - } else if (line.equals("禁止爆炸")) { - ac.denyExplosion = true; - } else if (line.equals("禁止燃烧") - || line.equals("禁止火焰蔓延")) { - ac.denyFireSpread = true; - } else if (line.equals("清除所有效果") - || line.equals("清理所有效果")) { - ac.clearAllBadEffects = true; - } else if (line.startsWith("清除") - || line.startsWith("清理")) { - String effName = line.substring(2).trim(); - if (effName.endsWith("效果")) - effName = effName.substring(0, - effName.length() - 2); - if (!effName.isEmpty()) - ac.clearEffects.add(effName); - } else if (line.contains("和平模式") - && !line.contains("白名单") - && !line.contains("定时") - && !line.contains("开关")) { - ac.peaceMode = true; - plugin.getLogger().info("[解析] 和平模式已识别"); - - } else if (line.startsWith("效果")) { - String rest = line.substring(2).trim(); - // 去掉前导冒号、空格 - while (rest.length() > 0 - && (rest.charAt(0) == ':' - || rest.charAt(0) == ':' - || rest.charAt(0) == ' ' - || rest.charAt(0) == '\t')) { - rest = rest.substring(1); - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - // 第一个是效果名(可能是中文) - String effName = parts[0]; - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) {} - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) {} - } - - // ★ 用映射方法转为英文名 - String resolved = resolveEffectName(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → " + resolved - + " 等级=" + effLv - + " 时长=" + effDur); - - ac.giveEffects.add(new String[]{ - resolved, - String.valueOf(effLv), - String.valueOf(effDur)}); - // ★ 加日志确认解析结果 - PotionEffectType checkType = - resolveEffectType(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → type=" - + (checkType != null - ? checkType.getName() - : "NULL(失败)") - + " 等级=" + effLv - + " 时长=" + effDur); - - - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) { - } - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) { - } - } - ac.giveEffects.add(new String[]{ - parts[0], - String.valueOf(effLv), - String.valueOf(effDur)}); - } - } else if (line.startsWith("通报批评:")) { - ac.announceTemplate = line.substring( - "通报批评:".length()).trim(); - ac.enableAnnounce = true; - } else if (line.startsWith("没收提示:")) { - ac.confiscateMsg = line.substring( - "没收提示:".length()).trim(); - } else if (line.startsWith("进入提示:")) { - String raw = line.substring("进入提示:".length()).trim(); - ac.enterMsg = raw; - - // ★ 调试:打印原文和Unicode - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★进入提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("离开提示:")) { - String raw = line.substring("离开提示:".length()).trim(); - ac.leaveMsg = raw; - - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★离开提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("惩罚命令:")) { - ac.punishCommands.add(line.substring( - "惩罚命令:".length()).trim()); - } else if (line.startsWith("强制游戏模式:") - || line.startsWith("游戏模式:")) { - String prefix = line.startsWith("强制游戏模式:") - ? "强制游戏模式:" : "游戏模式:"; - String modeStr = line.substring(prefix.length()) - .trim().toUpperCase(); - // 容错:支持多种写法 - if (modeStr.equals("CREATIVE") || modeStr.equals("创造") - || modeStr.equals("1")) { - ac.enforceGameMode = "CREATIVE"; - } else if (modeStr.equals("SURVIVAL") || modeStr.equals("生存") - || modeStr.equals("0")) { - ac.enforceGameMode = "SURVIVAL"; - } else if (modeStr.equals("ADVENTURE") || modeStr.equals("冒险") - || modeStr.equals("2")) { - ac.enforceGameMode = "ADVENTURE"; - } else if (modeStr.equals("SPECTATOR") || modeStr.equals("旁观") - || modeStr.equals("spectator") - || modeStr.equals("3")) { - ac.enforceGameMode = "SPECTATOR"; - } - } else if (line.contains("排除名单") - || line.contains("模式排除") - || line.contains("模式豁免")) { - int colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf('='); - if (colonIdx >= 0) { - String rest = line.substring(colonIdx + 1).trim(); - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playerName = s.trim(); - if (!playerName.isEmpty()) { - ac.modeExempt.add(playerName); - } - } - } - - String rest = line.substring(/*idx*/ + 4).trim(); - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playername = s.trim(); - if (!name.isEmpty()) ac.modeExempt.add(name); - } - } - - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String wlName = s.trim(); - if (!wlName.isEmpty()) { - ac.peaceWhitelist.add(wlName); - } - } - } - - - - } else if (line.equals("禁止交互展示框") - || line.equals("禁止展示框")) { - ac.denyItemFrame = true; - - } - } - br.close(); - return ac; - } -// ===== 和平白名单操作 ===== - - // 添加生物名字到区域和平白名单 - public boolean addPeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.add(creatureName); - savePeaceWhitelist(ac); - return true; - } - - // 从区域和平白名单移除生物名字 - public boolean removePeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.remove(creatureName); - savePeaceWhitelist(ac); - return true; - } - -// ===== 模式排除名单操作 ===== - - - @EventHandler - public void onCreatureSpawn(CreatureSpawnEvent e) { - Entity entity = e.getEntity(); - if (!isHostile(entity.getType().name())) return; - - Location loc = entity.getLocation(); - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - - if (ac == null || !ac.peaceMode) return; - - protectedEntities.put( - entity.getUniqueId(), - System.currentTimeMillis() + 5000); - - /* plugin.getLogger().info("[和平] " - + entity.getType().name() - + " 获得5秒保护期" - + " 区域=" + ac.name - + " 白名单=" + ac.peaceWhitelist - + " 白名单数量=" + ac.peaceWhitelist.size());*/ - } - - - public void cleanupProtectedEntities() { - long now = System.currentTimeMillis(); - int cleaned = 0; - Iterator> it = - protectedEntities.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (now > entry.getValue()) { - it.remove(); - cleaned++; - } - } - if (cleaned > 0) { - /* plugin.getLogger().info("[和平模式] 清理过期保护: " - + cleaned + "个 剩余:" - + protectedEntities.size() + "个");*/ - } - } - - - public void banHostilesWithWhitelist(Player p, AreaConfig ac) { - long now = System.currentTimeMillis(); - // 看看附近到底有什么实体 - List nearby = p.getNearbyEntities(48, 48, 48); - /* plugin.getLogger().info("[和平] " + p.getName() - + " 附近实体总数=" + nearby.size());*/ - for (Entity ent : nearby) { - String typeName = ent.getType().name(); - /* plugin.getLogger().info("[和平] 实体: " - + typeName + " isHostile=" + isHostile(typeName));*/ - } - int scanned = 0; - int waiting = 0; - int saved = 0; - int banished = 0; - - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (!isHostile(ent.getType().name())) continue; - scanned++; - - UUID entUid = ent.getUniqueId(); - Long protectUntil = protectedEntities.get(entUid); - String customName = ent.getCustomName(); - - // 有保护期记录 - if (protectUntil != null) { - long remain = protectUntil - now; - - if (remain > 0) { - // 保护期内,精确剩余秒数 - int secondsLeft = (int) Math.ceil(remain / 1000.0); - waiting++; - - // 保护期内也检查名字(玩家可能刚贴上) - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - protectedEntities.remove(entUid); - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 白名单匹配,永久保留");*/ - continue; - } - - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 保护期剩余约" + secondsLeft + "秒");*/ - continue; - } - - // 保护期到了,最终检查 - protectedEntities.remove(entUid); - /* plugin.getLogger().info("[和平] " - + ent.getType().name() + " 5秒到期");*/ - - // 到期时再查一次白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 到期但白名单匹配,保留");*/ - continue; - } - - // 没名字或不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名=[" + customName + "] 传送虚空");*/ - continue; - } - - // 没有保护期记录(非刷怪蛋生成的或记录被清了) - // 也检查一下白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 无保护期但白名单匹配,保留");*/ - continue; - } - - // 没有保护期且不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 无保护期,命名=[" + customName + "] 传送虚空");*/ - } - - /* plugin.getLogger().info("[和平] " - + " 扫描=" + scanned - + " 等待=" + waiting - + " 保留=" + saved - + " 传送=" + banished - + " 白名单=" + ac.peaceWhitelist);*/ - } - - private static final Set HOSTILE_TYPES = new HashSet<>(Arrays.asList( - "ZOMBIE", "ZOMBIE_VILLAGER", "HUSK", "DROWNED", - "SKELETON", "STRAY", "BOGGED", "WITHER_SKELETON", - "CREEPER", "SPIDER", "CAVE_SPIDER", "ENDERMITE", - "SILVERFISH", "BLAZE", "GHAST", "BIG_GHAST", - "MAGMA_CUBE", "SLIME", "WITCH", - "GUARDIAN", "ELDER_GUARDIAN", - "PILLAGER", "VINDICATOR", "EVOKER", "RAVAGER", - "ILLUSIONER", "VEX", - "PHANTOM", "SHULKER", "WARDEN", "BREEZE", - "PIGLIN", "PIGLIN_BRUTE", "ZOGLIN", - "ZOMBIFIED_PIGLIN", "ENDER_DRAGON" - )); - - private boolean isHostile(String typeName) { - return HOSTILE_TYPES.contains(typeName); - } - - // ==================== 白名单持久化 ==================== - - /** - * 保存白名单到文件 - * 路径:区域防护/whitelists/全局白名单.txt - * 路径:区域防护/whitelists/区域名.txt - */ - public void saveWhitelists() { - try { - // 1. 保存全局白名单 - PrintWriter pwGlobal = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(globalWhitelistFile), - StandardCharsets.UTF_8)); - for (String name : globalPlayerWhitelist) { - pwGlobal.println(name); - } - pwGlobal.close(); - - // 2. 删除旧的区域白名单文件(防止残留) - File[] oldFiles = whitelistDir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (oldFiles != null) { - for (File f : oldFiles) f.delete(); - } - - // 3. 保存各区域白名单 - for (Map.Entry> entry - : areaPlayerWhitelist.entrySet()) { - String name = entry.getKey(); - File areaFile = new File( - whitelistDir, name + ".txt"); - PrintWriter pwArea = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(areaFile), - StandardCharsets.UTF_8)); - for (String playerName : entry.getValue()) { - pwArea.println(playerName); - } - pwArea.close(); - } - - plugin.getLogger().info("[防护] 白名单已保存"); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 保存白名单失败: " - + e.getMessage()); - } - } - - - - // ==================== 区域检测 ==================== - - public AreaConfig getArea(String worldName, - int x, int y, int z) { - for (AreaConfig ac : areas.values()) { - if (ac.world.equalsIgnoreCase(worldName) - && inRect(x, z, ac) - && y >= ac.yMin - && y <= ac.yMax) { - return ac; - } - } - return null; - } - - private boolean inRect(int x, int z, AreaConfig ac) { - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - return x >= minX && x <= maxX - && z >= minZ && z <= maxZ; - } - - // ==================== 白名单检查 ==================== - - private boolean isPlayerWhitelisted(String player, - AreaConfig ac) { - if (globalPlayerWhitelist.contains( - player.toLowerCase())) - return true; - Set areaList = - areaPlayerWhitelist.get(ac.name); - return areaList != null - && areaList.contains(player.toLowerCase()); - } - - private boolean isItemBlacklisted(String itemId, - AreaConfig ac) { - if (globalItemBlacklist.contains( - itemId.toUpperCase())) - return true; - Set areaList = - areaItemBlacklist.get(ac.name); - return areaList != null - && areaList.contains(itemId.toUpperCase()); - } - - // ==================== 没收 ==================== - - private int confiscateItem(Player p, String itemTypeId) { - Material mat = Material.matchMaterial(itemTypeId); - if (mat == null) return 0; - ItemStack[] contents = p.getInventory().getContents(); - int removed = 0; - for (int i = 0; i < contents.length; i++) { - if (contents[i] != null - && contents[i].getType() == mat) { - removed += contents[i].getAmount(); - contents[i] = null; - } - } - p.getInventory().setContents(contents); - return removed; - } - - // ==================== 边框显示 ==================== - private void showBorder(Player p, AreaConfig ac, boolean temp) { - cancelBorder(p); - int taskId = Bukkit.getScheduler() - .runTaskTimer(plugin, new Runnable() { - @Override - public void run() { - if (!p.isOnline()) return; - Location loc = p.getLocation(); - int px = loc.getBlockX(); - int py = loc.getBlockY(); - int pz = loc.getBlockZ(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - for (int dy = 5; dy <= 30; dy += 5) { - for (int x = minX; x <= maxX; x += 4) { - for (int z = minZ; z <= maxZ; z += 4) { - if (x == minX || x == maxX - || z == minZ || z == maxZ) { - p.spawnParticle(Particle.END_ROD, - x + 0.5, py + dy, z + 0.5, - 1, 0, 0, 0, 0); - } - } - } - } - } - }, 0L, 20L).getTaskId(); - displayTaskIds.put(p.getUniqueId(), taskId); // taskId 是 int - if (temp) { - Bukkit.getScheduler().runTaskLater(plugin, - () -> cancelBorder(p), 300L); - } - } - - - private void cancelBorder(Player p) { - Integer taskId = displayTaskIds.remove(p.getUniqueId()); - if (taskId != null) { - Bukkit.getScheduler().cancelTask(taskId); - } - } - - public void onPlayerQuit(String playerName) { - playerCurrentArea.remove(playerName); - } - - public void clearAllPlayerAreas() { - playerCurrentArea.clear(); - } - private void saveModeExempt(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("排除名单") - || trimmed.contains("模式排除") - || trimmed.contains("模式豁免")) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.modeExempt.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存模式排除名单失败: " + e.getMessage()); - } - } - - // ==================== 事件监听 ==================== - @EventHandler - public void onPlayerMove(PlayerMoveEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - Location to = event.getTo(); - if (to == null) return; - - Location from = event.getFrom(); - if (from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ()) { - return; - } - - // 防抖 - Long lastTime = lastMoveProcess.get(uid); - long now = System.currentTimeMillis(); - if (lastTime != null - && now - lastTime < MOVE_DEBOUNCE_MS) { - return; - } - lastMoveProcess.put(uid, now); - - // 检测区域 - String oldArea = playerCurrentArea.get(uid); - String newArea = null; - String forcedMode = null; - - for (Map.Entry entry - : areas.entrySet()) { - AreaConfig ac = entry.getValue(); - if (!ac.world.equals(to.getWorld().getName())) - continue; - int px = to.getBlockX(); - int pz = to.getBlockZ(); - int py = to.getBlockY(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - if (px >= minX && px <= maxX - && pz >= minZ && pz <= maxZ - && py >= ac.yMin && py <= ac.yMax) { - newArea = entry.getKey(); - forcedMode = ac.enforceGameMode; - break; - } - } - - /* plugin.getLogger().info( - "[防护-移动] " + p.getName() - + " 旧=[" + oldArea - + "] 新=[" + newArea + "]" - + " areas.size=" + areas.size() - + " world=" + to.getWorld().getName());*/ - - // ===== 2. 区域没变 ===== - if (Objects.equals(oldArea, newArea)) { - if (newArea != null) { - AreaConfig ac = areas.get(newArea); - if (ac != null) { - // ★ 每次移动都清除指定效果 - clearBadEffects(p, ac); - - // ★ 每次移动都强制游戏模式 - if (ac.enforceGameMode != null - && !ac.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - } - return; - } - - // 离开旧区域 - if (oldArea != null) { - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★开始离开处理: - + oldArea);*/ - - AreaConfig oldAc = areas.get(oldArea); - - // 离开提示 - if (oldAc != null && oldAc.leaveMsg != null - && !oldAc.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(oldAc.leaveMsg)); - } - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★收标: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - plugin.getLogger().info( - "[防护-移动-离开] 标记项: " + t.getName()); - } - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - plugin.getLogger().info( - "[防护-移动-离开] 取消旧延时"); - } - - // 创建延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = oldArea; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★创建延时任务: " - + toClear.size() + "个");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-移动-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); - - plugin.getLogger().info( - "[防护-移动-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size()); - - if (online == null || !online.isOnline()) { - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-移动-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-移动-清理] + cleared" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - } - } - - - // 进入新区域 - if (newArea != null) { - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - if (oldP != null) { - diffEffects(p, uid, oldP, newArea); - } - } - - AreaConfig newAc = areas.get(newArea); - if (newAc != null) { - if (newAc.enterMsg != null - && !newAc.enterMsg.isEmpty()) { - p.sendMessage( - formatAreaMsg(newAc.enterMsg)); - } - applyRegionEffects(p, uid, newArea, newAc); - // 清除效果 - clearBadEffects(p, newAc); - - } -// ===== 清除指定效果 ===== - if (newAc.clearEffects != null - && !newAc.clearEffects.isEmpty()) { - for (String effName : newAc.clearEffects) { - PotionEffectType type = - resolveEffectType(effName); - if (type != null && p.getPotionEffect(type) != null) { - p.removePotionEffect(type); - plugin.getLogger().info( - "[防护] ★清除效果: " + effName - + " 玩家=" + p.getName()); - } - } - } - -// ===== 清除所有效果 ===== - if (newAc.clearAllBadEffects) { - for (PotionEffect eff : p.getActivePotionEffects()) { - p.removePotionEffect(eff.getType()); - } - plugin.getLogger().info( - "[防护] ★清除所有效果, 玩家=" + p.getName()); - } - - if (forcedMode != null - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(forcedMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - if (newArea != null) { - playerCurrentArea.put(uid, newArea); - } else { - playerCurrentArea.remove(uid); - } - } -// ===== 清理袭击生物 ===== - - // 需要清理的袭击生物类型 - private static final Set RAID_MOBS = new HashSet<>(Arrays.asList( - "VEX", - "EVOKER", - "PILLAGER", - "VINDICATOR", - "WITCH", - "RAVAGER", - "PILLAGER" - )); - - @EventHandler - public void onEntitySpawn(EntitySpawnEvent event) { - Entity entity = event.getEntity(); - Location loc = entity.getLocation(); - - // 检查是否在防护区域内 - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - if (ac == null) return; - - // 禁止袭击:取消袭击事件 - if (ac.denyRaid) { - // 清理袭击生物 - String typeName = entity.getType().name(); - if (RAID_MOBS.contains(typeName)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] ★清理袭击生物: " + typeName - + " 区域=" + ac.name); - } - } - - // 没收物品检测 - if (ac.confiscateItems != null - && !ac.confiscateItems.isEmpty()) { - if (entity instanceof Item) { - Item item = (Item) entity; - String itemType = item.getItemStack() - .getType().name(); - if (ac.confiscateItems.contains(itemType)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] 没收违禁掉落物: " - + itemType); - } - } - } - } - - - /** - * 进入区域时发放效果 - * 流程:查DB → 有记录跳过写入 → 无记录写入DB - * → 给效果 → 贴标 - */ - private void applyRegionEffects(Player p, UUID uid, - String areaName, AreaConfig ac) { - /* plugin.getLogger().info( - "[防护-贴标] ★★★方法被调用! 玩家=" - + p.getName() + " 区域=" + areaName - + " ac=" + (ac != null) - + " 效果配置数=" - + (ac != null ? ac.giveEffects.size() : "null"));*/ - - if (ac == null || ac.giveEffects.isEmpty()) return; - if (ac.giveEffects.isEmpty()) return; - // 查DB:该玩家在此区域是否已有效果记录 - List dbEffects = - getPlayerEffectNames(uid, areaName); - - if (dbEffects.isEmpty()) { - // 无记录 → 写入DB - for (String[] eff : ac.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t == null) continue; - saveSingleEffect(uid, p.getName(), - areaName, t.getName()); - } - plugin.getLogger().info( - "[防护] DB写入, 玩家=" + p.getName() - + " 区域=" + areaName); - } else { - plugin.getLogger().info( - "[防护] DB已有记录, 跳过写入, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - - // 给效果 + 贴标 - List applied = new ArrayList<>(); - for (String[] eff : ac.giveEffects) { - try { - PotionEffectType type = - resolveEffectType(eff[0]); - if (type == null) continue; - int lv = Integer.parseInt(eff[1]) - 1; - int dur = Integer.parseInt(eff[2]) * 20; - - PotionEffect existing = - p.getPotionEffect(type); - if (existing != null - && existing.getAmplifier() >= lv) { - continue; - } - p.addPotionEffect( - new PotionEffect(type, dur, lv)); - applied.add(type); - } catch (Exception ignored) { - } - } - - // 贴标 - // 新代码(合并,不覆盖): - List existingMarks = - playerMarkedEffects.get(uid); - if (existingMarks != null && !existingMarks.isEmpty()) { - // 已有标记,合并新增的 - for (PotionEffectType t : applied) { - if (!existingMarks.contains(t)) { - existingMarks.add(t); - } - } - /* plugin.getLogger().info( - "[防护-贴标] 合并标记: " - + existingMarks.size() - + " 个, 玩家=" + p.getName());*/ - } else { - // 无标记,新建 - playerMarkedEffects.put(uid, applied); - /* plugin.getLogger().info( - "[防护-贴标] 新建标记: " - + applied.size() - + " 个, 玩家=" + p.getName());*/ - } - } - - /** - * 离开区域时,延时15秒清理效果 - * 流程:收标 → 15秒后清效果 → 确认清空 → 清DB - */ - private void scheduleEffectClear(Player p, UUID uid, - String areaName) { - // 收标 - List marked = - playerAppliedEffects.remove(uid); - - plugin.getLogger().info( - "[防护] 收标 " + (marked != null ? marked.size() : 0) - + " 个, 玩家=" + p.getName() - + " 区域=" + areaName); - - // 启动15秒延时 - pendingClearArea.put(uid, areaName); - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - - // 清除效果 - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - } - - // 确认清空后清DB - removePlayerEffects(uid, areaName); - plugin.getLogger().info( - "[防护] 15秒已到, 清除 " - + (marked != null ? marked.size() : 0) - + " 个效果 + DB, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - }.runTaskLater(plugin, 300L); // 15秒 = 300 tick - - pendingClearTask.put(uid, task); - } - public Set getAllAreaNames() { - return areas.keySet(); - } - - /** - * 15秒内进了新区域: - * 清除旧区域有、新区域没有的效果 - */ - private void diffEffects(Player p, UUID uid, - String oldAreaName, String newAreaName) { - AreaConfig oldAc = areas.get(oldAreaName); - AreaConfig newAc = areas.get(newAreaName); - if (oldAc == null || newAc == null) return; - - // ★ 从 pendingClearEffects 获取旧标记 - List oldMarked = - pendingClearEffects.remove(uid); - - if (oldMarked == null || oldMarked.isEmpty()) { - plugin.getLogger().info( - "[防护-跨区域] 无待清理效果"); - return; - } - - // 新区域的效果集合 - Set newTypes = new HashSet<>(); - if (newAc != null) { - for (String[] eff : newAc.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t != null) newTypes.add(t); - } - } - - int cleared = 0; - for (PotionEffectType type : oldMarked) { - if (!newTypes.contains(type)) { - p.removePotionEffect(type); - cleared++; - plugin.getLogger().info( - "[防护-跨区域] 清除: " - + type.getName()); - } else { - plugin.getLogger().info( - "[防护-跨区域] 保留: " - + type.getName() - + " (新区域也有)"); - } - } - - removePlayerEffects(uid, oldAreaName); - plugin.getLogger().info( - "[防护-跨区域] 完成: 清除" - + cleared + "/" + oldMarked.size() - + " 个, " + oldAreaName - + " → " + newAreaName); - } - -// ===== DB 辅助方法(简化版)===== - - /** - * 查询玩家在某个区域的效果记录(返回英文名列表) - */ - private List getPlayerEffectNames( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid=? AND area_name=?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("effect_type")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - /** - * 写入单条效果记录 - */ - private void saveSingleEffect(UUID uid, - String playerName, String areaName, - String effectType) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?,?,?,?,?,?,?)"); - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, effectType); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 写入效果失败: " + e.getMessage()); - } - } - - // ==================== 违禁品没收 ==================== - - /** - * 检查玩家是否豁免于没收 - * 全局白名单 / 区域白名单 / modeExempt / OP 都跳过 - */ - private boolean isExemptFromConfiscation( - Player p, String areaName) { - String name = p.getName(); - - if (globalPlayerWhitelist.contains(name)) - return true; - - Set areaWl = - areaPlayerWhitelist.get(areaName); - if (areaWl != null && areaWl.contains(name)) - return true; - - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) - return true; - - return false; - } - - /** - * 没收玩家背包中的违禁品 - */ - private void confiscateItems(Player p, - AreaConfig ac) { - boolean confiscated = false; - - for (int i = 0; - i < p.getInventory().getSize(); i++) { - ItemStack item = p.getInventory().getItem(i); - if (item == null) continue; - - String typeName = item.getType().name(); - if (ac.confiscateItems - .contains(typeName)) { - p.getInventory().setItem(i, null); - confiscated = true; - } - } - - if (confiscated) { - String msg = ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty() - ? ac.confiscateMsg - : "§c你的违禁品已被没收"; - p.sendMessage(msg); - - // 通报批评 - if (ac.enableAnnounce - && ac.announceTemplate != null) { - String text = ac.announceTemplate - .replace("{player}", - p.getName()); - Bukkit.broadcastMessage(text); - } - } - } - - - - private String getChineseGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: return "生存模式"; - case CREATIVE: return "创造模式"; - case ADVENTURE: return "冒险模式"; - case SPECTATOR: return "旁观模式"; - default: return mode.name(); - } - } - - private void handleEnter(Player p, AreaConfig ac) { - UUID uid = p.getUniqueId(); - - // 清除负面效果 - clearBadEffects(p, ac); - - // 给予效果 - List given = new ArrayList<>(); - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - given.add(type); - } - } - - removePlayerEffects(uid, ac.name); - if (!given.isEmpty()) { - // savePlayerEffects(uid, p.getName(), ac.name, given); - } - - // 白名单检查(最先执行) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 游戏模式切换(只对非白名单玩家) - if (ac.enforceGameMode != null) { - boolean exempt = isPlayerExemptFromModeChange(p, ac); - if (!exempt) { - GameMode target = GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - p.sendMessage("§e§l[区域防护] §f游戏模式已切换为: " - + getChineseGameMode(target)); - } - } - } - - // 和平模式/袭击检查 - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - handleConfiscate(p, ac); - } - - private boolean isPlayerExemptFromModeChange( - Player p, AreaConfig ac) { - // if (p.isOp()) return true; - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return true; - } - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return true; - } - if (ac.modeExempt.contains(p.getName())) { - return true; - } - return false; - } - - private void banRangedHostiles(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (isRangedHostile(ent.getType().name())) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - /** - * 按配置清除效果 - * denyRaid → 清除袭击相关 - * denyAllEffects → 清除所有负面+中性 - * denyAllEffects > denyRaid(全清优先) - */ - private void clearBadEffects(Player p, AreaConfig ac) { - // 和平模式 > 清除所有效果 > 禁止单个 - if (ac.peaceMode || ac.clearAllBadEffects) { - PotionEffectType[] allBad = { - PotionEffectType.SLOWNESS, - PotionEffectType.MINING_FATIGUE, - PotionEffectType.INSTANT_DAMAGE, - PotionEffectType.NAUSEA, - PotionEffectType.BLINDNESS, - PotionEffectType.HUNGER, - PotionEffectType.WEAKNESS, - PotionEffectType.POISON, - PotionEffectType.WITHER, - PotionEffectType.LEVITATION, - PotionEffectType.UNLUCK, - PotionEffectType.DARKNESS, - PotionEffectType.WIND_CHARGED, - PotionEffectType.WEAVING, - PotionEffectType.OOZING, - PotionEffectType.INFESTED, - PotionEffectType.BAD_OMEN, - PotionEffectType.TRIAL_OMEN, - PotionEffectType.RAID_OMEN - }; - for (PotionEffectType type : allBad) { - p.removePotionEffect(type); - } - return; - } - - // 禁止袭击 → 自动关联清除 - if (ac.denyRaid) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - p.removePotionEffect(PotionEffectType.RAID_OMEN); - } - - // 配置文件单个清除 - for (String effName : ac.clearEffects) { - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.removePotionEffect(type); - } - } - } - - - private PotionEffectType resolveEffectType(String name) { - if (name == null || name.isEmpty()) return null; - - // 去掉"效果"后缀 - String clean = name; - if (clean.endsWith("效果")) { - clean = clean.substring(0, clean.length() - 2); - } - - // 硬编码中文名→英文ID映射(不依赖任何外部Map) - switch (clean) { - // 负面 - case "缓慢": - return resolveEffectType("slowness"); - case "挖掘疲劳": - return resolveEffectType("mining_fatigue"); - case "瞬间伤害": - return resolveEffectType("instant_damage"); - case "反胃": - return resolveEffectType("nausea"); - case "失明": - return resolveEffectType("blindness"); - case "饥饿": - return resolveEffectType("hunger"); - case "虚弱": - return resolveEffectType("weakness"); - case "中毒": - return resolveEffectType("poison"); - case "凋零": - return resolveEffectType("wither"); - case "飘浮": - return resolveEffectType("levitation"); - case "霉运": - return resolveEffectType("unluck"); - case "黑暗": - return resolveEffectType("darkness"); - case "蓄风": - return resolveEffectType("wind_charged"); - case "盘丝": - return resolveEffectType("weaving"); - case "渗浆": - return resolveEffectType("oozing"); - case "寄生": - return resolveEffectType("infested"); - // 中性 - case "不祥之兆": - case "不祥征兆": - return resolveEffectType("bad_omen"); - case "袭击之兆": - case "袭击征兆": - return resolveEffectType("raid_omen"); - case "试炼之兆": - case "试炼征兆": - return resolveEffectType("trial_omen"); - // 正面 - case "迅捷": - return resolveEffectType("speed"); - case "急迫": - return resolveEffectType("haste"); - case "力量": - return resolveEffectType("strength"); - case "瞬间治疗": - return resolveEffectType("instant_health"); - case "跳跃提升": - return resolveEffectType("jump_boost"); - case "生命恢复": - return resolveEffectType("regeneration"); - case "抗性提升": - return resolveEffectType("resistance"); - case "抗火": - return resolveEffectType("fire_resistance"); - case "水下呼吸": - return resolveEffectType("water_breathing"); - case "隐身": - return resolveEffectType("invisibility"); - case "夜视": - return resolveEffectType("night_vision"); - case "发光": - return resolveEffectType("glowing"); - case "生命提升": - return resolveEffectType("health_boost"); - case "伤害吸收": - return resolveEffectType("absorption"); - case "饱和": - return resolveEffectType("saturation"); - case "幸运": - return resolveEffectType("luck"); - case "村庄英雄": - return resolveEffectType("hero_of_the_village"); - case "缓降": - return resolveEffectType("slow_falling"); - case "潮涌能量": - return resolveEffectType("conduit_power"); - case "海豚的恩惠": - return resolveEffectType("dolphins_grace"); - case "致命中毒": - return resolveEffectType("poison"); - default: - break; - } - - -// 兜底:直接用原版英文ID查(★ 注意是 PotionEffectType.getByName,不是 resolveEffectType) - PotionEffectType fallback = PotionEffectType.getByName(clean); - if (fallback != null) return fallback; - fallback = PotionEffectType.getByName(clean.toLowerCase()); - if (fallback != null) return fallback; - - plugin.getLogger().warning("[防护] 无法识别效果: " + name); - return null; - - } - - - private static final Map EFFECT_MAP - = new HashMap<>(); - - - private void clearRaidEffects(Player p) { - boolean cleared = false; - try { - if (p.hasPotionEffect(PotionEffectType.BAD_OMEN)) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - try { - if (p.hasPotionEffect(PotionEffectType.RAID_OMEN)) { - p.removePotionEffect(PotionEffectType.RAID_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - if (cleared) { - p.sendMessage("§c§l[区域防护] §f已清除袭击相关效果"); - } - } - - public Map> - getPendingClearEffects() { - return pendingClearEffects; - } - - public void onPlayerOffline(UUID uid, String playerName, - String areaName) { - // 清除该玩家在该区域的所有效果 - List given = getPlayerEffects(uid, areaName); - for (PotionEffectType type : given) { - // 只清除我们给予的效果 - Player p = Bukkit.getPlayer(uid); - if (p != null) { - p.removePotionEffect(type); - } - } - // 清除DB记录 - removePlayerEffects(uid, areaName); - plugin.getLogger().info("[防护] " + playerName - + " 下线,已清除" + given.size() - + "个效果并清理DB"); - } - - @EventHandler - public void onTeleport(PlayerTeleportEvent e) { - Location from = e.getFrom(); - Location to = e.getTo(); - if (to == null) return; - - if (from.getWorld() == to.getWorld() - && from.distance(to) < 5) return; - - Player p = e.getPlayer(); - UUID uid = p.getUniqueId(); - - // 标记传送中 - teleporting.add(uid); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - teleporting.remove(uid); - }, 5L); - - AreaConfig fromArea = getArea( - from.getWorld().getName(), - from.getBlockX(), from.getBlockY(), - from.getBlockZ()); - AreaConfig toArea = getArea( - to.getWorld().getName(), - to.getBlockX(), to.getBlockY(), - to.getBlockZ()); - - String fromName = fromArea != null - ? fromArea.name : null; - String toName = toArea != null - ? toArea.name : null; - - /* plugin.getLogger().info( - "[防护-传送] ★★★进入onTeleport: " - + p.getName() - + " [" + fromName + "] → [" + toName + "]" - + " teleporting=" + teleporting.contains(uid));*/ - - if (Objects.equals(fromName, toName)) { - /* plugin.getLogger().info( - "[防护-传送] 区域相同, 跳过");*/ - return; - } - - // ===== 离开旧区域 ===== - if (fromName != null) { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★开始离开处理: " - + fromName);*/ - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★收标结果: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - /* plugin.getLogger().info( - "[防护-传送-离开] 标记项: " - + t.getName());*/ - } - } else { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★标记为null! " - + "尝试其他key查找...");*/ - // 遍历查找可能的标记 - for (Map.Entry> entry - : playerMarkedEffects.entrySet()) { - if (entry.getKey().equals(uid)) { - /* plugin.getLogger().info( - "[防护-传送-离开] 找到标记! 效果数=" - + entry.getValue().size());*/ - } - } - /* plugin.getLogger().info( - "[防护-传送-离开] playerMarkedEffects大小=" - + playerMarkedEffects.size());*/ - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 创建15秒延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = fromName; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★创建延时任务: " - + toClear.size() + "个效果");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-传送-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); -/* - plugin.getLogger().info( - "[防护-传送-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size() - + " 区域=" + clearArea);*/ - - if (online == null - || !online.isOnline()) { - plugin.getLogger().info( - "[防护-传送-清理] 玩家不在线, 清DB"); - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-传送-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-传送-清理] ★★★完成: 清除" - + cleared + "/" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - - } - - // 离开提示 - if (fromArea != null && fromArea.leaveMsg != null - && !fromArea.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(fromArea.leaveMsg)); - } - } - - // ===== 进入新区域 ===== - if (toName != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] ★★★开始进入处理: " - + toName);*/ - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - plugin.getLogger().info( - "[防护-传送-进入] 取消旧延时, 旧区域=" - + oldP); - if (oldP != null) { - diffEffects(p, uid, oldP, toName); - } - } - - // 进入提示 - if (toArea != null && toArea.enterMsg != null - && !toArea.enterMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(toArea.enterMsg)); - } - - // 发放效果 - if (toArea != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] 调用applyRegionEffects");*/ - applyRegionEffects(p, uid, toName, toArea); -// 清除效果 - clearBadEffects(p, toArea); - - if (toArea.confiscateItems != null - && !toArea.confiscateItems.isEmpty() - && !isExemptFromConfiscation( - p, toName)) { - confiscateItems(p, toArea); - } - } - - // 强制模式 - if (toArea != null && toArea.enforceGameMode != null - && !toArea.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, toName)) { - GameMode target = GameMode.valueOf( - toArea.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - // 更新记录 - if (toName != null) { - playerCurrentArea.put(uid, toName); - } else { - playerCurrentArea.remove(uid); - } - } - - - public Map getPendingClearTasks() { - return pendingClearTask; - } - - public Map> - getPlayerMarkedEffects() { - return playerMarkedEffects; - } - - public void handleInside(Player p, AreaConfig ac) { - clearBadEffects(p, ac); - - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - // 全白或区白 → 跳过所有限制性规则(包括没收) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - handleConfiscate(p, ac); - } - - public void cleanupExpiredProtections() { - protectedEntities.entrySet().removeIf( - entry -> System.currentTimeMillis() > entry.getValue()); - } - - // 禁止袭击:只驱逐袭击怪物 - private void banRaidMobs(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(32, 32, 32)) { - String t = ent.getType().name(); - if (t.equals("PILLAGER") - || t.equals("VINDICATOR") - || t.equals("EVOKER") - || t.equals("RAVAGER") - || t.equals("VEX") - || t.equals("WITCH") - || t.equals("ILLUSIONER") - || t.equals("ZOMBIE_VILLAGER")) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(voidLoc.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - public Set getAreaNames() { - return areas.keySet(); - } - - public Collection getPendingTasks() { - return pendingClearTask.values(); - } - - - // 判断是否为远程攻击敌对生物 - private boolean isRangedHostile(String typeName) { - switch (typeName) { - // ===== 骷髅系 ===== - case "SKELETON": // 骷髅(弓箭) - case "STRAY": // 流浪者(迟缓之箭) - case "BOGGED": // 虫蚀骷髅(毒箭) - case "WITHER_SKELETON": // 凋灵骷髅(近战但算灾厄) - - // ===== 灾厄村民 ===== - case "PILLAGER": // 掠夺者(弩) - case "ILLUSIONER": // 幻术师(弓箭+失明) - case "EVOKER": // 幻魔者(唤魔) - case "VINDICATOR": // 卫道士(斧头近战) - case "VEX": // 恼鬼(剑近战,但会穿墙飞行) - - // ===== 法术/投射 ===== - case "BLAZE": // 烈焰人(火球) - case "WITCH": // 女巫(药水) - - // ===== 飞行/爆炸 ===== - case "GHAST": // 恶魂(火球) - case "PHANTOM": // 幻翼(俯冲) - case "BIG_GHAST": // 大恶魂(1.21新增) - - // ===== 水下 ===== - case "GUARDIAN": // 守卫者(激光) - case "ELDER_GUARDIAN": // 远古守卫者(激光) - - // ===== 远程特殊 ===== - case "SHULKER": // 潜影贝(潜影弹) - case "BREEZE": // 旋风人(风弹) - - // ===== 蜘蛛系(远程毒液攻击) ===== - case "SPIDER": // 蜘蛛(白天远程吐丝) - case "CAVE_SPIDER": // 洞穴蜘蛛(毒) - - // ===== 1.21新增 ===== - case "WIND_CHARGED": // 蓄风怪(风弹) - case "WEAVING": // 盘丝怪(吐丝) - case "OOZING": // 渗浆怪(史莱姆) - case "INFESTED": // 寄生怪(蠹虫) - - // ===== 盔甲/变种(部分版本) ===== - case "ARMORED_SKELETON": // 盔甲骷髅 - case "ARCHER_SKELETON": // 弓箭骷髅(部分版本) - case "SNOW_GOLEM": // 雪傀儡(不是敌对但远程) - - // ===== 亡灵骑乘 ===== - case "HUSK": // 尸壳(近战但属于灾厄系) - case "DROWNED": // 溺尸(三叉戟远程) - case "ZOMBIE_VILLAGER": // 僵尸村民 - case "ZOMBIE_PIGMAN": // 僵尸猪灵 - case "ZOMBIFIED_PIGLIN": // 僵尸猪灵(1.16+) - case "PIGLIN_BRUTE": // 猪灵蛮兵 - - // ===== 猪灵(弓箭) ===== - case "PIGLIN": // 猪灵(金弩远程) - - // ===== 末影系 ===== - case "ENDER_DRAGON": // 末影龙(远程吐息) - case "WARDEN": // 监守者(远程音波) - - // ===== 其他 ===== - case "CREPER": // 苦力怕(爆炸不算远程但危险) - case "CREEPER": // 苦力怕 - - return true; - default: - return false; - } - } - - - - private void applyEffects(Player p, AreaConfig ac) { - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - - PotionEffectType type = resolveEffectType(effName); - plugin.getLogger().info("[防护调试] 给予: " - + effName + " -> " + type); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - plugin.getLogger().info("[防护调试] 已给予: " - + effName + " 等级" + level - + " 持续" + duration + "秒"); - } - } - } - - - @EventHandler - public void onBlockPlace(BlockPlaceEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockPlace - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止放置方块"); - } - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockBreak - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏方块"); - } - } - - - @EventHandler - public void onDamage(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - // 保护性规则:对所有人无条件生效 - if (ac.denyAllDamage) { e.setCancelled(true); return; } - if (ac.denyFallDamage - && e.getCause() == EntityDamageEvent.DamageCause.FALL) - e.setCancelled(true); - } - - - @EventHandler - public void onPVP(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyPVP - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - - @EventHandler - public void onFood(FoodLevelChangeEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyHunger) - e.setCancelled(true); - } - - - - @EventHandler - public void onDrop(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyDrop - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止丢弃物品"); - } - } - - // 替换整个 onInteract 方法 - @EventHandler - public void onInteract(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (hand == null) return; - - // ===== 选区工具 ===== - if (hand.getType() == Material.BREEZE_ROD) { - ItemMeta meta = hand.getItemMeta(); - if (meta != null && meta.getDisplayName() != null - && meta.getDisplayName().contains("区域选择")) { - Action action = e.getAction(); - boolean isLeft = (action == Action.LEFT_CLICK_AIR - || action == Action.LEFT_CLICK_BLOCK); - boolean isRight = (action == Action.RIGHT_CLICK_AIR - || action == Action.RIGHT_CLICK_BLOCK); - if (isLeft || isRight) { - e.setCancelled(true); - Block block = e.getClickedBlock(); - if (block == null) { - try { block = p.getTargetBlockExact(5); } - catch (Exception ignored) {} - } - if (block == null) return; - UUID uid = p.getUniqueId(); - if (isLeft) { - pos1.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置1: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } else { - pos2.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置2: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } - } - return; - } - } - - // ===== 放置实体类物品时不检查(展示框、矿车、船、地图等)===== - Material mat = hand.getType(); - if (mat == Material.ITEM_FRAME - || mat == Material.GLOW_ITEM_FRAME - || mat == Material.MINECART - || mat == Material.TNT_MINECART - || mat == Material.CHEST_MINECART - || mat == Material.HOPPER_MINECART - || mat == Material.OAK_BOAT - || mat == Material.SPRUCE_BOAT - || mat == Material.BIRCH_BOAT - || mat == Material.JUNGLE_BOAT - || mat == Material.ACACIA_BOAT - || mat == Material.DARK_OAK_BOAT - || mat == Material.MANGROVE_BOAT - || mat == Material.CHERRY_BOAT - || mat == Material.BAMBOO_RAFT - || mat == Material.MAP - || mat == Material.ARMOR_STAND) { - return; - } - - // ===== 区域规则检查 ===== - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - - // 末影珍珠禁止 - if (ac.denyEnderPearl - && hand.getType() == Material.ENDER_PEARL) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用末影珍珠"); - return; - } - - // 弓箭禁止 - if (ac.denyBow - && (hand.getType() == Material.BOW - || hand.getType() == Material.CROSSBOW)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用弓箭"); - return; - } - - // 白名单跳过 - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 禁止使用物品 - String typeId = hand.getType().name(); - if (ac.denyUseItems.contains(typeId)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用此物品"); - return; - } - - // 没收检查 - handleConfiscate(p, ac); - } - - - - @EventHandler - public void onExplosion(EntityExplodeEvent e) { - Iterator it = e.blockList().iterator(); - while (it.hasNext()) { - Block b = it.next(); - AreaConfig ac = getArea( - b.getWorld().getName(), - b.getX(), b.getY(), b.getZ()); - if (ac != null && ac.denyExplosion) - it.remove(); - } - } - public void loadWhitelists() { - globalPlayerWhitelist.clear(); - areaPlayerWhitelist.clear(); - - if (!whitelistDir.exists()) { - whitelistDir.mkdirs(); - return; - } - - try { - // 加载全局白名单 - if (globalWhitelistFile.exists()) { - List lines = - java.nio.file.Files.readAllLines( - globalWhitelistFile.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - globalPlayerWhitelist.add(name); - } - } - } - - // 加载各区域白名单 - File[] files = whitelistDir.listFiles( - (File d, String n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (files != null) { - for (File f : files) { - String areaName = f.getName() - .replace(".txt", ""); - Set set = - ConcurrentHashMap.newKeySet(); - List lines = - java.nio.file.Files.readAllLines( - f.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - set.add(name); - } - } - if (!set.isEmpty()) { - areaPlayerWhitelist.put( - areaName, set); - } - } - } - - plugin.getLogger().info( - "[防护] 白名单加载完成,全局: " - + globalPlayerWhitelist.size() - + ",区域数: " - + areaPlayerWhitelist.size()); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载白名单失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onPotionSplash(PotionSplashEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - AreaConfig ac = getArea( - shooter.getWorld().getName(), - shooter.getLocation().getBlockX(), - shooter.getLocation().getBlockY(), - shooter.getLocation().getBlockZ()); - if (ac != null && ac.denyPotion - && !isPlayerWhitelisted(shooter.getName(), ac)) { - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止使用药水"); - } - } - - /** - * 解析区域名:先精准匹配,再模糊匹配 - * @param input 用户输入的区域名 - * @return 匹配到的区域名,无匹配返回null - */ - public String resolveAreaName(String input) { - if (input == null || input.isEmpty()) return null; - if (input.equalsIgnoreCase("global")) return "global"; - - // 第一步:精准匹配 - for (String name : areas.keySet()) { - if (name.equalsIgnoreCase(input)) { - return name; - } - } - - // 第二步:包含匹配(用户只打了部分名字) - for (String name : areas.keySet()) { - if (name.contains(input) || input.contains(name)) { - return name; - } - } - - // 第三步:编辑距离模糊匹配(著称→主城) - String bestMatch = null; - int bestDistance = Integer.MAX_VALUE; - for (String name : areas.keySet()) { - int dist = levenshteinDistance( - input.toLowerCase(), name.toLowerCase()); - if (dist < bestDistance) { - bestDistance = dist; - bestMatch = name; - } - } - - // 容错阈值:编辑距离 <= 名字长度的一半 - if (bestMatch != null) { - int threshold = Math.max(1, bestMatch.length() / 2); - if (bestDistance <= threshold) { - return bestMatch; - } - } - - return null; - } - - /** - * 判断字符串是否是区域名(精准或模糊) - */ - private boolean isAreaName(String name) { - if (name.equalsIgnoreCase("global")) return false; - return resolveAreaName(name) != null; - } - - /** - * 获取模糊匹配的实际区域名(用于提示用户) - */ - private String getResolvedName(String input) { - return resolveAreaName(input); - } - - /** - * 编辑距离算法(莱文斯坦距离) - */ - private int levenshteinDistance(String s1, String s2) { - int[][] dp = new int[s1.length() + 1][s2.length() + 1]; - for (int i = 0; i <= s1.length(); i++) dp[i][0] = i; - for (int j = 0; j <= s2.length(); j++) dp[0][j] = j; - for (int i = 1; i <= s1.length(); i++) { - for (int j = 1; j <= s2.length(); j++) { - if (s1.charAt(i - 1) == s2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], - Math.min(dp[i - 1][j], dp[i][j - 1])); - } - } - } - return dp[s1.length()][s2.length()]; - } - - /** - * 智能重排参数:找出子命令位置,重排为标准顺序 - * 标准顺序:/protect 子命令 参数1 参数2 ... - */ - private String[] smartReorderArgs(String[] original) { - if (original.length <= 1) return original; - - Set knownSubs = new HashSet<>(Arrays.asList( - "add", "remove", "additem", "removeitem", - "addname", "removename", "listname", - "addwhite", "removewhite", "listwhite", - "list", "listitem", "创建", "删除", "重载", - "工具", "expand", "contraction", - "on", "off", "tempon", "modeexempt" - )); - - String first = original[0].toLowerCase(); - if (knownSubs.contains(first)) { - return original; - } - - int subIndex = -1; - for (int i = 0; i < original.length; i++) { - if (knownSubs.contains(original[i].toLowerCase())) { - subIndex = i; - break; - } - } - - if (subIndex < 0) { - return original; - } - - String[] reordered = new String[original.length]; - reordered[0] = original[subIndex]; - int idx = 1; - for (int i = 0; i < original.length; i++) { - if (i == subIndex) continue; - reordered[idx] = original[i]; - idx++; - } - - return reordered; - } - - // ==================== 管理命令 ==================== - public boolean handleCommand(CommandSender sender, - String[] args) { - args = smartReorderArgs(args); - if (args.length == 0) { - showHelp(sender); - return true; - } - String sub = args[0].toLowerCase(); -// ===== 诊断命令 ===== - if (sub.equals("sdf1debug")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - sender.sendMessage("§e===== 调试信息 ====="); - sender.sendMessage("§7当前区域: " - + playerCurrentArea.get(uid)); - sender.sendMessage("§7标记效果数: " - + (playerMarkedEffects.containsKey(uid) - ? playerMarkedEffects.get(uid).size() - : "无标记")); - sender.sendMessage("§7待清理任务: " - + pendingClearTask.containsKey(uid)); - sender.sendMessage("§7区域数: " + areas.size()); - - if (playerMarkedEffects.containsKey(uid)) { - List marked = - playerMarkedEffects.get(uid); - for (PotionEffectType type : marked) { - sender.sendMessage("§7 标记: " - + type.getName() - + " 身上有=" - + (p.getPotionEffect(type) != null)); - } - } - return true; - } - -// ===== 强制清理命令(测试用)===== - if (sub.equals("testclear")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - List marked = - playerMarkedEffects.remove(uid); - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - sender.sendMessage("§a已强制清理 " - + marked.size() + " 个效果"); - } else { - sender.sendMessage("§c无标记效果可清理"); - } - - // 取消延时任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - sender.sendMessage("§a已取消延时任务"); - } - - removePlayerEffects(uid, - playerCurrentArea.getOrDefault(uid, "")); - sender.sendMessage("§aDB已清理"); - return true; - } - - // ===== 工具 ===== - if (sub.equals("工具") || sub.equals("wand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - ItemStack tool = new ItemStack(WAND); - ItemMeta meta = tool.getItemMeta(); - meta.setDisplayName("§a§l区域选择工具"); - meta.setLore(Arrays.asList( - "§7左键: 位置1", "§7右键: 位置2")); - tool.setItemMeta(meta); - p.getInventory().addItem(tool); - p.sendMessage("§a§l[防护] §f已获取选择工具"); - return true; - } - - // ===== 创建 ===== - if (sub.equals("创建") || sub.equals("create")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 创建 <名>"); - return true; - } - Player p = (Player) sender; - UUID u = p.getUniqueId(); - if (!pos1.containsKey(u) - || !pos2.containsKey(u)) { - p.sendMessage("§c请先用工具选点"); - return true; - } - String areaName = args[1]; - File f = new File(rootDir, areaName + ".txt"); - if (f.exists()) { - p.sendMessage("§c区域已存在"); - return true; - } - Location l1 = pos1.get(u); - Location l2 = pos2.get(u); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 区域: " + areaName); - pw.println("起点: " + l1.getWorld().getName() - + "," + l1.getBlockX() - + "," + l1.getBlockZ()); - pw.println("终点: " + l2.getWorld().getName() - + "," + l2.getBlockX() - + "," + l2.getBlockZ()); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("# 禁止放置方块"); - pw.println("# 禁止破坏方块"); - pw.println("# 禁止PVP"); - pw.println("# 效果: 夜视 1 999"); - pw.println("# 进入提示: 欢迎来到保护区"); - pw.println("# 离开提示: 已离开保护区"); - pw.close(); - p.sendMessage("§a§l[防护] §f区域 " - + areaName + " 已创建"); - loadAllAreas(); - } catch (IOException ex) { - p.sendMessage("§c创建失败"); - } - return true; - } - - // ===== 和平白名单 ===== - if (sub.equals("addname")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别: " - + args[1] + " 或 " + args[2]); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 和平白名单"); - return true; - } - sender.sendMessage("§c用法: /protect addname " - + "<区域> <名字>"); - return true; - } - if (sub.equals("removename")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " + parsed[0]); - return true; - } - sender.sendMessage("§c用法: /protect removename " - + "<区域> <名字>"); - return true; - } - if (sub.equals("listname")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listname <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 和平白名单:"); - if (ac.peaceWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.peaceWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 模式排除 ===== - if (sub.equals("addwhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect addwhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("removewhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect removewhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("listwhite")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listwhite <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 模式排除名单:"); - if (ac.modeExempt.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.modeExempt) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 列表 ===== - if (sub.equals("列表") || sub.equals("list")) { - if (args.length == 2) { - if (args[1].equalsIgnoreCase("global")) { - sender.sendMessage("§a全局白名单:"); - if (globalPlayerWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n - : globalPlayerWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - String an = args[1]; - Set wl = - areaPlayerWhitelist.get(an); - sender.sendMessage("§a" + an + " 白名单:"); - if (wl == null || wl.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : wl) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - if (areas.isEmpty()) { - sender.sendMessage("§7暂无区域"); - } else { - sender.sendMessage("§e§l区域列表:"); - for (Map.Entry en - : areas.entrySet()) { - sender.sendMessage("§a " + en.getKey() - + " §7规则:" - + en.getValue().ruleCount() - + "条"); - } - } - return true; - } - - // ===== 重载 ===== - if (sub.equals("重载") || sub.equals("reload")) { - reload(); - sender.sendMessage("§a已重载 " - + areas.size() + " 个区域"); - return true; - } - - // ===== 物品黑名单 ===== - if (sub.equals("additem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + args[1]); - return true; - } - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + itemName); - return true; - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - } else { - areaItemBlacklist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()) - .add(itemName); - sender.sendMessage("§a已加黑: " - + itemName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect additem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("removeitem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (!globalItemBlacklist - .contains(itemName)) { - sender.sendMessage( - "§c全局黑名单中没有: " - + itemName); - return true; - } - globalItemBlacklist.remove(itemName); - sender.sendMessage("§a已移除: " + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.remove(itemName); - } else { - Set list = - areaItemBlacklist.get(areaName); - if (list != null) { - list.remove(itemName); - } - } - sender.sendMessage("§a已移除: " - + itemName); - return true; - } - sender.sendMessage("§c用法: /protect removeitem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("listitem")) { - if (args.length < 2) { - sender.sendMessage( - "§a全局物品黑名单:"); - if (globalItemBlacklist.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item - : globalItemBlacklist) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - Set list = - areaItemBlacklist.get(args[1]); - sender.sendMessage("§a" + args[1] - + " 物品黑名单:"); - if (list == null || list.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item : list) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - - // ===== expand 扩建 ===== - if (sub.equals("expand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ -= amount; - else if (dir.equals("南")) maxZ += amount; - else if (dir.equals("东")) maxX += amount; - else if (dir.equals("西")) minX -= amount; - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f扩建 §e" - + amount + "§f格"); - return true; - } - - // ===== contract 收缩 ===== - if (sub.equals("contraction") - || sub.equals("contract")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ += amount; - else if (dir.equals("南")) maxZ -= amount; - else if (dir.equals("东")) maxX -= amount; - else if (dir.equals("西")) minX += amount; - if (minX > maxX || minZ > maxZ) { - p.sendMessage("§c收缩过度"); - return true; - } - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f收缩 §e" - + amount + "§f格"); - return true; - } - - // ===== 删除 ===== - if (sub.equals("删除") || sub.equals("delete")) { - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 删除 <名>"); - return true; - } - File f = new File(rootDir, args[1] + ".txt"); - if (f.exists()) { - f.delete(); - areas.remove(args[1]); - sender.sendMessage("§a已删除: " + args[1]); - } else { - sender.sendMessage("§c不存在: " + args[1]); - } - return true; - } - - // ===== add 加白名单 ===== - if (sub.equals("add")) { - if (args.length == 2) { - String name = args[1]; - if (globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 已在全局白名单中"); - return true; - } - globalPlayerWhitelist.add(name); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " - + playerName); - } else { - Set wl = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()); - wl.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已加白: " - + playerName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect add " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== remove 移除白名单 ===== - if (sub.equals("remove")) { - if (args.length == 2) { - String name = args[1]; - if (!globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 不在全局白名单中"); - return true; - } - globalPlayerWhitelist.remove(name); - saveWhitelists(); - sender.sendMessage("§a已从全局白名单移除: " - + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.remove(playerName); - saveWhitelists(); - sender.sendMessage("§a已移除: " - + playerName); - } else { - Set wl = - areaPlayerWhitelist.get(areaName); - if (wl != null) { - wl.remove(playerName); - } - saveWhitelists(); - sender.sendMessage("§a已从 " + areaName - + " 移除: " + playerName); - } - return true; - } - sender.sendMessage("§c用法: /protect remove " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== on/off/tempon 边框 ===== - if (sub.equals("on")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, false); - p.sendMessage("§a§l[防护] §f已显示 " - + ac.name + " 边框"); - return true; - } - if (sub.equals("off")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - cancelBorder((Player) sender); - ((Player) sender).sendMessage( - "§a§l[防护] §f已关闭边框显示"); - return true; - } - if (sub.equals("tempon")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, true); - p.sendMessage("§a§l[防护] §f临时显示 " - + ac.name + " 边框(15秒)"); - return true; - } - - showHelp(sender); - return true; - } - - - private String[] parseAreaAndTarget(String arg1, String arg2) { - String r1 = resolveAreaName(arg1); - if (r1 != null) { - return new String[]{r1, arg2}; - } - String r2 = resolveAreaName(arg2); - if (r2 != null) { - return new String[]{r2, arg1}; - } - return null; - } - - private boolean playerExists(String playerName) { - // 在线玩家 - for (Player online : Bukkit.getOnlinePlayers()) { - if (online.getName().equalsIgnoreCase(playerName)) return true; - } - // 离线玩家 - @SuppressWarnings("deprecation") - OfflinePlayer op = Bukkit.getOfflinePlayer(playerName); - return op.hasPlayedBefore(); - } - - - private boolean validateArea(CommandSender sender, String areaName) { - if (!areas.containsKey(areaName)) { - sender.sendMessage("§c区域不存在: " + areaName); - return false; - } - return true; - } - - - private String matname(Material mat) { - String name = mat.name(); - String[] parts = name.split("_"); - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (sb.length() > 0) sb.append(" "); - sb.append(part.substring(0, 1).toUpperCase()) - .append(part.substring(1).toLowerCase()); - } - return sb.toString(); - } - private String yawToDir(float yaw) { - // MC中: 0=南, 90=西, 180=北, -90/270=东 - yaw = yaw % 360; - if (yaw < 0) yaw += 360; - if (yaw >= 315 || yaw < 45) return "南"; - if (yaw >= 45 && yaw < 135) return "西"; - if (yaw >= 135 && yaw < 225) return "北"; - return "东"; - } - private void saveAreaConfig(AreaConfig ac) { - File f = new File(rootDir, ac.name + ".txt"); - if (!f.exists()) return; - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - if (line.trim().startsWith("起点:")) { - lines.add("起点: " + ac.world - + "," + ac.x1 + "," + ac.z1); - } else if (line.trim().startsWith("终点:")) { - lines.add("终点: " + ac.world - + "," + ac.x2 + "," + ac.z2); - } else { - lines.add(line); - } - } - br.close(); - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - for (String l : lines) pw.println(l); - pw.close(); - } catch (IOException ignored) {} - } - - private void showHelp(CommandSender s) { - s.sendMessage("§e§l==== 区域防护 ===="); - s.sendMessage("§a/protect 工具 §7选地工具"); - s.sendMessage("§a/protect 创建 <名> §7创建区域"); - s.sendMessage("§a/protect 列表 §7区域列表"); - s.sendMessage("§a/protect 重载 §7重载配置"); - s.sendMessage("§a/protect 删除 <名> §7删除区域"); - s.sendMessage("§a/protect add [区域] <玩家> §7玩家加白"); - s.sendMessage("§a/protect remove [区域] <玩家> §7玩家删白"); - s.sendMessage("§a/protect additem [区域] <物品> §7物品加黑"); - s.sendMessage("§a/protect removeitem [区域] <物品> §7物品删黑"); - s.sendMessage("§a/protect on §7显示边框"); - s.sendMessage("§a/protect off §7关闭边框"); - s.sendMessage("§a/protect tempon §7临时边框(15秒)"); - s.sendMessage("§a/protect list §7列出全局白名单"); - s.sendMessage("§a/protect list <区域> §7列出区域白名单"); - s.sendMessage("§a/protect listitem §7列出全局物品黑名单"); - s.sendMessage("§a/protect listitem <区域> §7列出区域物品黑名单"); - - } -// ==================== 白名单管理公共方法 ==================== - - // ==================== 白名单操作 ==================== - - public boolean isPlayerGlobalWhitelisted( - String playerName) { - return globalPlayerWhitelist.contains(playerName); - } - - public boolean addPlayerToGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean isPlayerAreaWhitelisted( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - return wl != null && wl.contains(playerName); - } - - public boolean addPlayerToAreaWhitelist( - String areaName, String playerName) { - Set set = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap.newKeySet()); - boolean ok = set.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromAreaWhitelist( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - boolean ok = wl != null && wl.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - - - public int handleConfiscate(Player p, AreaConfig ac) { - if (isPlayerWhitelisted(p.getName(), ac)) return 0; - Set allItems = new HashSet<>(ac.confiscateItems); - allItems.addAll(globalItemBlacklist); - Set areaList = areaItemBlacklist.get(ac.name); - if (areaList != null) allItems.addAll(areaList); - - if (allItems.isEmpty()) return 0; - - int totalRemoved = 0; - String confiscatedNames = ""; - - for (String itemId : allItems) { - // 过滤掉区域名和无效ID - Material mat = Material.matchMaterial(itemId); - if (mat == null) { - plugin.getLogger().warning( - "[防护] 跳过无效物品: " + itemId); - continue; - } - ItemStack[] all = p.getInventory().getContents(); - int count = 0; - for (int i = 0; i < all.length; i++) { - if (all[i] != null && all[i].getType() == mat) { - count += all[i].getAmount(); - all[i] = null; - } - } - p.getInventory().setContents(all); - - ItemStack off = p.getInventory().getItemInOffHand(); - if (off != null && off.getType() == mat) { - count += off.getAmount(); - p.getInventory().setItemInOffHand(null); - } - - if (count > 0) { - totalRemoved += count; - if (!confiscatedNames.isEmpty()) - confiscatedNames += "§7、§e"; - confiscatedNames += "§e" + matname(mat) - + " x" + count; - } - } - - if (totalRemoved > 0) { - String msg = (ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty()) - ? ac.confiscateMsg - : "你携带了违禁品,已被没收"; - p.sendMessage("§c§l[区域防护] §f" + msg); - p.sendMessage("§7没收物品: " + confiscatedNames); - - if (ac.enableAnnounce - && ac.announceTemplate != null - && !ac.announceTemplate.isEmpty()) { - String ann = ac.announceTemplate - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)) - .replace("{items}", confiscatedNames); - Bukkit.broadcastMessage("§c§l[区域防护] §f" + ann); - } - for (String cmd : ac.punishCommands) { - String finalCmd = cmd - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)); - Bukkit.getScheduler().runTask(plugin, - () -> Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), finalCmd)); - } - } - return totalRemoved; - } - public void clearBlacklists() { - globalItemBlacklist.clear(); - areaItemBlacklist.clear(); - plugin.getLogger().info("[防护] 已清空所有黑名单"); - } - - @EventHandler - public void onEntityDamage( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑 ===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - // 展示框被点击(旋转/放入/取出) - @EventHandler - public void onItemFrameInteract( - PlayerInteractAtEntityEvent e) { - Entity clicked = e.getRightClicked(); - String typeName = clicked.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - Player p = e.getPlayer(); - - // 全局白放行 - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(clicked); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - // 非白名单 → 拦截 - // 记录当前旋转角度 - final Rotation savedRotation; - final Entity frame = clicked; - if (clicked instanceof org.bukkit.entity.ItemFrame) { - savedRotation = - ((org.bukkit.entity.ItemFrame) clicked) - .getRotation(); - } else { - savedRotation = null; - } - - e.setCancelled(true); - - // 下一tick强制恢复旋转 - if (savedRotation != null) { - final Rotation restore = savedRotation; - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (frame.isValid() - && frame instanceof org.bukkit.entity.ItemFrame) { - ((org.bukkit.entity.ItemFrame) frame) - .setRotation(restore); - } - }, 1L); - } - - p.sendMessage("§c§l[区域防护] §f禁止交互展示框"); - - // 驱逐远程敌对生物 - banRangedHostiles(p, ac); - } - - - - // 展示框保护:检查展示框本身+周围方块所属区域 - - // ===== 展示框攻击拦截(左键/投射物破坏)===== - @EventHandler - public void onEntityDamageByEntity( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - // 展示框被攻击 - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑(原有的,不动)===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - private AreaConfig findFrameArea(Entity frame) { - Location loc = frame.getLocation(); - int bx = loc.getBlockX(); - int by = loc.getBlockY(); - int bz = loc.getBlockZ(); - String world = loc.getWorld().getName(); - - AreaConfig ac = getArea(world, bx, by, bz); - if (ac != null && ac.denyItemFrame) return ac; - - int[][] offsets = { - {0, 0, 1}, {0, 0, -1}, - {1, 0, 0}, {-1, 0, 0}, - {0, 1, 0}, {0, -1, 0} - }; - for (int[] off : offsets) { - ac = getArea(world, - bx + off[0], by + off[1], bz + off[2]); - if (ac != null && ac.denyItemFrame) return ac; - } - return null; - } - - // ===== 投射物击中展示框(弓箭/弩)===== - @EventHandler - public void onProjectileHit( - org.bukkit.event.entity.ProjectileHitEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - Entity hit = e.getHitEntity(); - if (hit == null) return; - String typeName = hit.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(shooter.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(hit); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(shooter.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - } - - - // ===== 区域外火焰不能蔓延进区域 ===== - @EventHandler - public void onBlockSpread(BlockSpreadEvent e) { - Block block = e.getBlock(); - AreaConfig tgtArea = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (tgtArea != null && tgtArea.denyFireSpread) { - e.setCancelled(true); - } - } - - // ===== 区域内禁止点燃方块 ===== - @EventHandler - public void onIgnite(BlockIgniteEvent e) { - Player p = e.getPlayer(); - if (p == null) return; // 非玩家点燃的(闪电等) - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f此区域禁止点燃物品"); - } - } - - // ===== 区域内方块着火(TNT、岩浆等)也阻止 ===== - @EventHandler - public void onBlockBurn(BlockBurnEvent e) { - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) - e.setCancelled(true); - } - - - private void savePeaceWhitelist(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("和平") - && trimmed.contains("白名单")) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.peaceWhitelist.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存和平白名单失败: " + e.getMessage()); - } - } - - - @EventHandler - public void onPaintingPlace( - org.bukkit.event.player.PlayerBucketEmptyEvent e) { - // 暂不处理 - } - - - // ==================== 数据类 ==================== - - public static class AreaConfig { - public String name = ""; - public String world = ""; - public int x1, z1, x2, z2; - public int yMin = 0, yMax = 255; - public List confiscateItems = new ArrayList<>(); - public List denyUseItems = new ArrayList<>(); - public List clearEffects = new ArrayList<>(); // 清除单个效果名 - public boolean clearAllBadEffects = false; // 清除所有负面+中性 - public List giveEffects = new ArrayList<>(); // [效果名, 等级, 秒数] - public List punishCommands = new ArrayList<>(); - public boolean denyBlockPlace = false; - public boolean denyBlockBreak = false; - public boolean denyPVP = false; - public boolean denyFallDamage = false; - public boolean denyHunger = false; - public boolean denyAllDamage = false; - public boolean denyMount = false; - public boolean denyDrop = false; - public boolean denyEnderPearl = false; - public boolean denyBow = false; - public boolean denyPotion = false; - public boolean denyExplosion = false; - public boolean enableAnnounce = false; - public String announceTemplate = ""; - public String confiscateMsg = ""; - public String enterMsg = ""; - public String leaveMsg = ""; - public boolean denyRaid = false; - public boolean denyFireSpread = false; - public boolean denyAllEffects = false;; - public boolean peaceMode = false; - public boolean denyItemFrame = false; - public Set peaceWhitelist = new HashSet<>(); - public String enforceGameMode = null; // 强制游戏模式 - public Set modeExempt = new HashSet<>(); // 模式排除名单 - - - - - private static final Map EFFECT_MAP - = new HashMap<>(); - static { - // 负面效果 - EFFECT_MAP.put("缓慢", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("凋零", PotionEffectType.WITHER); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("霉运", PotionEffectType.UNLUCK); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("蓄风", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生", PotionEffectType.INFESTED); - // 中性效果 - EFFECT_MAP.put("不祥之兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("不祥征兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("袭击征兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("试炼征兆", PotionEffectType.TRIAL_OMEN); - // 正面效果 - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); - EFFECT_MAP.put("瞬间治疗", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("跳跃提升", PotionEffectType.JUMP_BOOST); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性提升", PotionEffectType.RESISTANCE); - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("生命提升", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("村庄英雄", PotionEffectType.HERO_OF_THE_VILLAGE); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); - EFFECT_MAP.put("潮涌能量", PotionEffectType.CONDUIT_POWER); - EFFECT_MAP.put("海豚的恩惠", PotionEffectType.DOLPHINS_GRACE); - - - // 带效果二字 - // 补充带"效果"后缀的别名 - EFFECT_MAP.put("黑暗效果", PotionEffectType.DARKNESS); - EFFECT_MAP.put("不祥之兆效果", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆效果", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆效果", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("缓慢效果", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("中毒效果", PotionEffectType.POISON); - EFFECT_MAP.put("凋零效果", PotionEffectType.WITHER); - EFFECT_MAP.put("失明效果", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿效果", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱效果", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("反胃效果", PotionEffectType.NAUSEA); - EFFECT_MAP.put("飘浮效果", PotionEffectType.LEVITATION); - EFFECT_MAP.put("发光效果", PotionEffectType.GLOWING); - EFFECT_MAP.put("霉运效果", PotionEffectType.UNLUCK); - EFFECT_MAP.put("蓄风效果", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝效果", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆效果", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生效果", PotionEffectType.INFESTED); - EFFECT_MAP.put("挖掘疲劳效果", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害效果", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("村庄英雄效果", PotionEffectType.HERO_OF_THE_VILLAGE); - } - - - public int ruleCount() { - int c = 0; - if (!confiscateItems.isEmpty()) c++; - if (denyBlockPlace) c++; - if (denyBlockBreak) c++; - if (denyPVP) c++; - if (denyFallDamage) c++; - if (denyHunger) c++; - if (denyAllDamage) c++; - if (denyDrop) c++; - if (denyMount) c++; - if (denyEnderPearl) c++; - if (denyBow) c++; - if (denyRaid) c++; - if (denyPotion) c++; - if (denyExplosion) c++; - if (denyFireSpread) c++; - if (denyAllEffects) c++; - if (!clearEffects.isEmpty()) c++; - if (clearAllBadEffects) c++; - if (!giveEffects.isEmpty()) c++; - if (enableAnnounce) c++; - if (!punishCommands.isEmpty()) c++; - if (!denyUseItems.isEmpty()) c++; - if (peaceMode) c++; - if (denyItemFrame) c++; - if (!peaceWhitelist.isEmpty()) c++; - - return c; - } - } - } \ No newline at end of file diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/BondManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/BondManager.java deleted file mode 100644 index ea78152..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/BondManager.java +++ /dev/null @@ -1,697 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import java.io.File; -import java.sql.*; -import java.util.*; - -public class BondManager { - - private final Main plugin; - private Connection db; - - // ===== 交易类型常量 ===== - public static final String TX_ADMIN_GIVE = "admin_give"; - public static final String TX_ADMIN_DEDUCT = "admin_deduct"; - public static final String TX_TRANSFER_OUT = "transfer_out"; - public static final String TX_TRANSFER_IN = "transfer_in"; - public static final String TX_REDEEM = "redeem"; - public static final String TX_SHOP_BUY = "shop_buy"; - public static final String TX_DAILY_SIGN = "daily_sign"; - public static final String TX_FREEZE = "freeze"; - public static final String TX_UNFREEZE = "unfreeze"; - - - public static final int MAX_WATER_DAYS = 14; - public static final int ADMIN_MAX_WATER_DAYS = 30; - - public BondManager(Main plugin) { - this.plugin = plugin; - initDB(); - } - - // ======================== 数据库初始化 ======================== - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), "bond.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 债券余额(含冻结状态) - st.execute("CREATE TABLE IF NOT EXISTS bonds (" - + "player_name TEXT PRIMARY KEY," - + "amount INTEGER DEFAULT 0," - + "status TEXT DEFAULT 'normal')"); - - // 兼容旧表 - try { - st.execute("ALTER TABLE bonds " - + "ADD COLUMN status " - + "TEXT DEFAULT 'normal'"); - } catch (SQLException ignored) {} - - // 旧流水(保留兼容) - st.execute("CREATE TABLE IF NOT EXISTS bond_log (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "amount INTEGER NOT NULL," - + "reason TEXT DEFAULT ''," - + "time INTEGER DEFAULT 0)"); - - // 新交易流水表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "bond_transaction (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "type TEXT NOT NULL," - + "amount INTEGER NOT NULL," - + "target_player TEXT DEFAULT ''," - + "operator TEXT DEFAULT ''," - + "reason TEXT DEFAULT ''," - + "balance_after INTEGER DEFAULT 0," - + "time INTEGER DEFAULT 0)"); - - try { - st.execute("CREATE INDEX " - + "IF NOT EXISTS idx_tx_pt " - + "ON bond_transaction(" - + "player_name, time)"); - } catch (SQLException ignored) {} - try { - st.execute("ALTER TABLE bond_transaction " - + "ADD COLUMN balance_before " - + "INTEGER DEFAULT 0"); - } catch (SQLException ignored) {} - - - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[Bond] DB init failed: " - + e.getMessage(), e); - } - } - - // ======================== 账户状态 ======================== - - public boolean isFrozen(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT status FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String s = rs.getString("status"); - rs.close(); ps.close(); - return "frozen".equals(s); - } - rs.close(); ps.close(); - } catch (SQLException ignored) {} - return false; - } - - public String getAccountStatus(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT status FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String s = rs.getString("status"); - rs.close(); ps.close(); - return s != null ? s : "normal"; - } - rs.close(); ps.close(); - } catch (SQLException ignored) {} - return "normal"; - } - - public void freezeAccount(String player) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET status='frozen' " - + "WHERE player_name=?"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - } - - public void unfreezeAccount(String player) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET status='normal' " - + "WHERE player_name=?"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - } - - private void ensureAccount(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR IGNORE INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,0,'normal')"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException ignored) {} - } - - // ======================== 余额查询 ======================== - - public int getBonds(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT amount FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - int v = 0; - if (rs.next()) v = rs.getInt("amount"); - rs.close(); ps.close(); - return v; - } catch (SQLException e) { return 0; } - } - - /** 该玩家是否在 bond 表中有真实记录 */ - public boolean hasBondAccount(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM bonds " - + "WHERE player_name=? " - + "AND amount > 0"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - boolean exists = rs.next(); - rs.close(); ps.close(); - return exists; - } catch (SQLException e) { - return false; - } - } - - // ======================== 增加债券 ======================== - - public int addBonds(String player, int amount) { - return addBondsInternal(player, amount, - TX_ADMIN_GIVE, "", "", - "管理员发放"); - } - - public int addBonds(String player, int amount, - String type, - String targetPlayer, - String operator, - String reason) { - if (isFrozen(player)) return -1; - return addBondsInternal(player, amount, - type, targetPlayer, operator, reason); - } - - private int addBondsInternal(String player, - int amount, - String type, - String targetPlayer, - String operator, - String reason) { - int before = getBonds(player); - int after = before + amount; - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status)" - + " VALUES (?,?, 'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, after); - ps.setInt(3, after); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - logTransaction(player, type, amount, - targetPlayer, operator, reason, - before, after); - logLegacy(player, amount); - return after; - } - - // ======================== 扣除债券 ======================== - - - - private boolean deductBondsInternal(String player, - int amount, - String type, - String targetPlayer, - String operator, - String reason) { - int before = getBonds(player); - if (before < amount) return false; - int after = before - amount; - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET amount=? " - + "WHERE player_name=?"); - ps.setInt(1, after); - ps.setString(2, player); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - logTransaction(player, type, amount, - targetPlayer, operator, reason, - before, after); - logLegacy(player, -amount); - return true; - } - - // ======================== 流水写入 ======================== - - private void logTransaction(String player, - String type, - int amount, - String targetPlayer, - String operator, - String reason, - int balanceBefore, - int balanceAfter) { - try { - // 合并模式:shop_buy 30秒内同商品合并 - if ("shop_buy".equals(type) - && reason != null - && !reason.isEmpty()) { - long mergeWindow = - System.currentTimeMillis() - 30000L; - PreparedStatement chk = - db.prepareStatement( - "SELECT id, amount " - + "FROM bond_transaction " - + "WHERE player_name=? " - + "AND type=? " - + "AND reason=? " - + "AND time > ? " - + "ORDER BY time DESC " - + "LIMIT 1"); - chk.setString(1, player); - chk.setString(2, type); - chk.setString(3, reason); - chk.setLong(4, mergeWindow); - ResultSet rs = chk.executeQuery(); - if (rs.next()) { - int existId = rs.getInt("id"); - int existAmt = rs.getInt("amount"); - rs.close(); - chk.close(); - PreparedStatement upd = - db.prepareStatement( - "UPDATE bond_transaction " - + "SET amount=?, " - + "balance_after=?, " - + "time=? " - + "WHERE id=?"); - upd.setInt(1, existAmt + amount); - upd.setInt(2, balanceAfter); - upd.setLong(3, - System.currentTimeMillis()); - upd.setInt(4, existId); - upd.executeUpdate(); - upd.close(); - return; - } - rs.close(); - chk.close(); - } - - // 原始插入 - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bond_transaction " - + "(player_name, type, amount," - + " target_player, operator," - + " reason, balance_before," - + " balance_after, time)" - + " VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, player); - ps.setString(2, type); - ps.setInt(3, amount); - ps.setString(4, - targetPlayer != null ? targetPlayer : ""); - ps.setString(5, - operator != null ? operator : ""); - ps.setString(6, - reason != null ? reason : ""); - ps.setInt(7, balanceBefore); - ps.setInt(8, balanceAfter); - ps.setLong(9, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - // ======================== 扣除债券 ======================== - - /** 完整版:含冻结检查 + 流水记录 */ - public boolean deductBonds(String player, int amount, - String type, - String targetPlayer, - String operator, - String reason) { - if (isFrozen(player)) return false; - return deductBondsInternal(player, amount, - type, targetPlayer, operator, reason); - } - - public boolean checkoutCart(String player, - String[] itemNames, - int[] subtotals) { - int total = 0; - for (int s : subtotals) total += s; - if (isFrozen(player)) return false; - - int before = getBonds(player); - if (before < total) return false; - int after = before - total; - - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,?,'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, after); - ps.setInt(3, after); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - - int running = before; - for (int i = 0; i < itemNames.length; i++) { - int next = running - subtotals[i]; - logTransaction(player, "shop_buy", subtotals[i], - "", "商店系统", - "购物车购买-" + itemNames[i], - running, next); - running = next; - } - - logLegacy(player, -total); - return true; - } - - public boolean setBonds(String player, int amount) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,?, 'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, amount); - ps.setInt(3, amount); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return true; - } - - // ======================== 转账 ======================== - - public TransferResult transfer(String from, - String to, - int amount) { - if (from.equalsIgnoreCase(to)) - return new TransferResult(false, - "不能给自己转账"); - if (isFrozen(from)) - return new TransferResult(false, - "你的账户已被冻结"); - if (isFrozen(to)) - return new TransferResult(false, - to + " 的账户已被冻结"); - if (isSameIP(from, to)) - return new TransferResult(false, - "不能与同IP玩家转账"); - - if (!deductBondsInternal(from, amount, - TX_TRANSFER_OUT, to, from, - "转账给" + to)) - return new TransferResult(false, "债券不足"); - - addBondsInternal(to, amount, - TX_TRANSFER_IN, from, from, - "收到来自" + from + "的转账"); - - return new TransferResult(true, "转账成功"); - } - - // ======================== 同IP检测 ======================== - - public boolean isSameIP(String p1, String p2) { - String ip1 = getPlayerIPAny(p1); - String ip2 = getPlayerIPAny(p2); - if (ip1 == null || ip2 == null) return false; - return ip1.equals(ip2); - } - - /** 获取同IP下的所有其他在线玩家 */ - public List getSameIPPlayers(String player) { - String ip = getPlayerIPAny(player); - if (ip == null) return Collections.emptyList(); - List list = new ArrayList<>(); - for (Player p : - plugin.getServer().getOnlinePlayers()) { - String pIp = plugin.getPlayerIP(p); - if (pIp != null && pIp.equals(ip) - && !p.getName() - .equalsIgnoreCase(player)) { - list.add(p.getName()); - } - } - return list; - } - - private String getPlayerIPAny(String player) { - // 在线:直接取 - Player p = plugin.getServer() - .getPlayerExact(player); - if (p != null) { - String ip = plugin.getPlayerIP(p); - if (ip != null) return ip; - } - // 离线:查主库 - try { - Object v = plugin.getDb() - .getField(player, "last_login_ip"); - if (v != null) return v.toString(); - } catch (Exception ignored) {} - try { - Object v = plugin.getDb() - .getField(player, "register_ip"); - if (v != null) return v.toString(); - } catch (Exception ignored) {} - return null; - } - - // ======================== 流水查询 ======================== - - public List> getTransactions( - String player, int days) { - days = Math.min(days, MAX_WATER_DAYS); - long cutoff = System.currentTimeMillis() - - (long) days * 24 * 60 * 60 * 1000; - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "AND time>=? " - + "ORDER BY time DESC"); - ps.setString(1, player); - ps.setLong(2, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("operator", - rs.getString("operator")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return list; - } -// 放在 getTransactions 方法后面: - - /** - * 获取该玩家全部流水(不限时间,供打印使用) - */ - public List> - getAllTransactions(String player) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "ORDER BY time ASC"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("player_name", - rs.getString("player_name")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("operator", - rs.getString("operator")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } -// 放在 getAllTransactions 方法后面: - - /** - * 获取所有有流水记录的玩家名(去重) - */ - public List getAllPlayerNames() { - List list = new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT DISTINCT player_name " - + "FROM bond_transaction " - + "ORDER BY player_name ASC"); - while (rs.next()) { - list.add(rs.getString("player_name")); - } - rs.close(); st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTransferTransactions( - String player, int days) { - days = Math.min(days, MAX_WATER_DAYS); - long cutoff = System.currentTimeMillis() - - (long) days * 24 * 60 * 60 * 1000; - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "AND time>=? " - + "AND (type=? OR type=?) " - + "ORDER BY time DESC"); - ps.setString(1, player); - ps.setLong(2, cutoff); - ps.setString(3, TX_TRANSFER_OUT); - ps.setString(4, TX_TRANSFER_IN); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return list; - } - - - private void logLegacy(String player, int amount) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bond_log " - + "(player_name,amount,time) " - + "VALUES (?,?,?)"); - ps.setString(1, player); - ps.setInt(2, amount); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); ps.close(); - } catch (SQLException ignored) {} - } - - public Map getLog( - String player, int limit) { - Map info = new LinkedHashMap<>(); - info.put("balance", getBonds(player)); - return info; - } - - // ======================== 转账结果 ======================== - - public static class TransferResult { - public final boolean success; - public final String message; - public TransferResult(boolean success, - String message) { - this.success = success; - this.message = message; - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/BondPrinter.java b/backup/20260605_152529/src/main/java/Sdf1_login/BondPrinter.java deleted file mode 100644 index 674ec3c..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/BondPrinter.java +++ /dev/null @@ -1,378 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -public class BondPrinter { - - private final Main plugin; - private final File outputDir; - - private static final SimpleDateFormat FILE_SDF = - new SimpleDateFormat( - "yyyy-MM-dd_HH-mm-ss"); - private static final SimpleDateFormat CELL_SDF = - new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - private static final LinkedHashMap - TYPE_NAMES = new LinkedHashMap<>(); - - static { - TYPE_NAMES.put("admin_give", "管理员发放"); - TYPE_NAMES.put("admin_deduct", "管理员扣除"); - TYPE_NAMES.put("transfer_out", "转出"); - TYPE_NAMES.put("transfer_in", "转入"); - TYPE_NAMES.put("redeem", "CDK兑换"); - TYPE_NAMES.put("shop_buy", "商城购买"); - TYPE_NAMES.put("shop_sell", "商城出售"); // ← 补上 - TYPE_NAMES.put("daily_sign", "签到奖励"); - TYPE_NAMES.put("freeze", "冻结"); - TYPE_NAMES.put("unfreeze", "解冻"); - - } - - public BondPrinter(Main plugin) { - this.plugin = plugin; - this.outputDir = new File( - plugin.getDataFolder(), "printer"); - if (!this.outputDir.exists()) { - this.outputDir.mkdirs(); - } - } - - // ============ 打印单个玩家 ============ - - public void printPlayer(String player, - CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doPrintPlayer( - player, sender)); - } - - private void doPrintPlayer(String player, - CommandSender sender) { - try { - List> txs = - plugin.getBonds() - .getAllTransactions(player); - - String fileName = player + "_" - + FILE_SDF.format(new Date()) - + ".xls"; - File file = new File(outputDir, fileName); - - deleteOld(player); - - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8)); - try { - writeXmlHeader(pw, - escXml(player)); - writeXmlRow(pw, - new String[]{ - "时间", "类型", - "金额", "对象", - "操作者", "理由", - "操作前余额", - "操作后余额"}); - - for (Map tx : txs) { - writeXmlRow(pw, new String[]{ - txTime(tx), - txTypeName(tx), - String.valueOf( - txInt(tx, - "amount")), - txStr(tx, - "target_player"), - txStr(tx, "operator"), - txStr(tx, "reason"), - String.valueOf( - txInt(tx, - "balance_before")), - String.valueOf( - txInt(tx, - "balance_after")) - }); - } - - writeXmlFooter(pw); - pw.flush(); - } finally { - pw.close(); - } - - if (sender != null) { - sender.sendMessage("§a[打印] §f" - + player + " §a流水已导出"); - sender.sendMessage("§7文件: §f" - + fileName); - sender.sendMessage("§7共 §f" - + txs.size() - + " §7条,§724小时后删除"); - } - } catch (Exception e) { - if (sender != null) { - sender.sendMessage("§c导出失败: " - + e.getMessage()); - } - plugin.getLogger().severe( - "[Printer] " + e.getMessage()); - } - } - - // ============ 打印全服 ============ - - public void printAll(CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doPrintAll(sender)); - } - - private void doPrintAll(CommandSender sender) { - try { - List players = - plugin.getBonds() - .getAllPlayerNames(); - if (players.isEmpty()) { - if (sender != null) { - sender.sendMessage("§c没有流水数据"); - } - return; - } - - String fileName = "全服流水_" - + FILE_SDF.format(new Date()) - + ".xls"; - File file = new File(outputDir, fileName); - - // ===== 收集所有流水 ===== - // key = 时间戳+玩家+金额,用于去重 - // value = 一行数据 - List allRows = new ArrayList<>(); - Set seenKeys = new HashSet<>(); - - for (String player : players) { - List> txs = - plugin.getBonds() - .getAllTransactions(player); - for (Map tx : txs) { - int txId = ((Number) tx.get("id")) - .intValue(); - String dedupKey = - String.valueOf(txId); - if (!seenKeys.add(dedupKey)) { - continue; - } - - String timeStr = txTime(tx); - long timeMs = 0; - Object t = tx.get("time"); - if (t != null) { - timeMs = - ((Number) t).longValue(); - } - - allRows.add(new String[]{ - player, - timeStr, - txTypeName(tx), - String.valueOf( - txInt(tx, "amount")), - txStr(tx, "target_player"), - txStr(tx, "operator"), - txStr(tx, "reason"), - String.valueOf( - txInt(tx, - "balance_before")), - String.valueOf( - txInt(tx, - "balance_after")), - // 额外存时间戳用于排序 - String.valueOf(timeMs) - }); - } - } - - // ===== 按时间戳排序 ===== - allRows.sort((a, b) -> { - long ta = Long.parseLong( - a[a.length - 1]); - long tb = Long.parseLong( - b[b.length - 1]); - return Long.compare(ta, tb); - }); - - // ===== 写入文件 ===== - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8)); - try { - writeXmlHeader(pw, "全服流水"); - writeXmlRow(pw, new String[]{ - "玩家", "时间", "类型", - "金额", "对象", "操作者", - "理由", "操作前余额", - "操作后余额" - }); - - for (String[] row : allRows) { - // 去掉最后的时间戳列 - String[] cells = - new String[row.length - 1]; - System.arraycopy(row, 0, - cells, 0, cells.length); - writeXmlRow(pw, cells); - } - - writeXmlFooter(pw); - pw.flush(); - - if (sender != null) { - sender.sendMessage( - "§a[打印] §f全服流水 §a已导出"); - sender.sendMessage("§7文件: §f" - + fileName); - sender.sendMessage("§7共 §f" - + players.size() - + " 名玩家,§f" - + allRows.size() + " §7条"); - sender.sendMessage( - "§7按时间从早到晚排序"); - sender.sendMessage( - "§724小时后自动删除"); - } - } finally { - pw.close(); - } - } catch (Exception e) { - if (sender != null) { - sender.sendMessage("§c导出失败: " - + e.getMessage()); - } - plugin.getLogger().severe( - "[Printer] " + e.getMessage()); - } - } - - // ============ 清理 ============ - - public void cleanupOldFiles() { - long now = System.currentTimeMillis(); - long maxAge = 24L * 60 * 60 * 1000; - File[] files = outputDir.listFiles( - (d, n) -> n.endsWith(".xls")); - if (files == null) { - return; - } - int count = 0; - for (File f : files) { - if (now - f.lastModified() > maxAge) { - if (f.delete()) { - count++; - } - } - } - if (count > 0) { - plugin.getLogger().info( - "[Printer] 清理 " - + count + " 个过期文件"); - } - } - - private void deleteOld(String player) { - File[] old = outputDir.listFiles( - (d, n) -> n.startsWith(player + "_") - && n.endsWith(".xls")); - if (old != null) { - for (File f : old) { - f.delete(); - } - } - } - - // ============ XML工具 ============ - - private void writeXmlHeader(PrintWriter pw, - String sheetName) { - pw.println(""); - pw.println(""); - pw.println(""); - pw.println(" "); - pw.println(" "); - } - - private void writeXmlRow(PrintWriter pw, - String[] cells) { - pw.println(" "); - for (String cell : cells) { - pw.println(" " - + "" - + escXml(cell != null ? cell : "") - + ""); - } - pw.println(" "); - } - - private void writeXmlFooter(PrintWriter pw) { - pw.println("
"); - pw.println("
"); - pw.println("
"); - } - - private String escXml(String val) { - if (val == null) { - return ""; - } - return val.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """); - } - - private String txTime(Map tx) { - Object t = tx.get("time"); - if (t == null) return ""; - long ms = ((Number) t).longValue(); - return CELL_SDF.format(new Date(ms)); - } - - private String txTypeName( - Map tx) { - String type = (String) tx.get("type"); - return TYPE_NAMES.getOrDefault(type, type); - } - - private int txInt(Map tx, - String key) { - Object v = tx.get(key); - if (v == null) return 0; - return ((Number) v).intValue(); - } - - private String txStr(Map tx, - String key) { - Object v = tx.get(key); - return v != null ? v.toString() : ""; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/CDKManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/CDKManager.java deleted file mode 100644 index e07569f..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/CDKManager.java +++ /dev/null @@ -1,293 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.*; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.nio.charset.StandardCharsets; - -public class CDKManager { - - private final Main plugin; - private Connection db; - private final Map pendingInputs - = new ConcurrentHashMap<>(); - - public CDKManager(Main plugin) { - this.plugin = plugin; - initDB(); - } - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), "bond.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS cdk (" - + "code TEXT PRIMARY KEY," - + "amount INTEGER DEFAULT 0," - + "type TEXT DEFAULT 'bond'," - + "used INTEGER DEFAULT 0," - + "used_by TEXT DEFAULT ''," - + "created_time INTEGER DEFAULT 0," - + "used_time INTEGER DEFAULT 0)"); - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[CDK] DB init failed: " - + e.getMessage(), e); - } - } - - // ===== CDK兑换(供SDF1调用) ===== - - /** - * 兑换CDK,返回结果字符串 - * @return "success:金额:余额前:余额后" - * 或 "fail:原因" - */ - public String redeem(String code, String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT amount,type,used FROM cdk " - + "WHERE code=?"); - ps.setString(1, code); - ResultSet rs = ps.executeQuery(); - if (!rs.next()) { - rs.close(); ps.close(); - return "fail:not_found"; - } - int amount = rs.getInt("amount"); - String type = rs.getString("type"); - int used = rs.getInt("used"); - rs.close(); ps.close(); - - if (used == 1) - return "fail:already_used"; - - if (plugin.getBonds() - .isFrozen(player)) - return "fail:frozen"; - - PreparedStatement up = db.prepareStatement( - "UPDATE cdk SET used=1," - + "used_by=?,used_time=? " - + "WHERE code=?"); - up.setString(1, player); - up.setLong(2, - System.currentTimeMillis()); - up.setString(3, code); - up.executeUpdate(); up.close(); - - if ("bond".equals(type)) { - int bef = plugin.getBonds() - .getBonds(player); - plugin.getBonds().addBonds(player, - amount, - BondManager.TX_REDEEM, - "", "CDK系统", - "CDK兑换: " + code); - int aft = plugin.getBonds() - .getBonds(player); - return "success:" + amount - + ":" + bef + ":" + aft; - } - return "fail:unknown_type"; - } catch (SQLException e) { - return "fail:" + e.getMessage(); - } - } - - // ===== 聊天输入系统 ===== - - public void requestInput(Player p, - String type, - String context) { - CDKInput input = new CDKInput(); - input.type = type; - input.context = context; - input.time = System.currentTimeMillis(); - pendingInputs.put(p.getName(), input); - } - - public boolean isListening(String playerName) { - return pendingInputs - .containsKey(playerName); - } - - public void onChat(Player p, String msg) { - CDKInput input = pendingInputs - .remove(p.getName()); - if (input == null) return; - - if (System.currentTimeMillis() - - input.time > 60000) { - p.sendMessage("§c输入超时"); - return; - } - - if ("cdk".equals(input.type)) { - String result = redeem( - msg.trim(), p.getName()); - if (result != null - && result.startsWith("success:")) { - String[] parts = result.split(":"); - int amt = Integer.parseInt(parts[1]); - int bef = Integer.parseInt(parts[2]); - int aft = Integer.parseInt(parts[3]); - p.sendMessage("§e§l[CDK] §f兑换成功!" - + "获得 §c" + amt + "§f债券"); - p.sendMessage("§7余额: §e" + bef - + " §7-> §a" + aft); - } else { - p.sendMessage("§c§l[CDK] §f" - + (result != null - ? result.replace("fail:", "") - : "无效的兑换码")); - } - return; - } - - if ("econ".equals(input.type)) { - handleEconInput(p, msg.trim(), - input.context); - return; - } - - if ("bond".equals(input.type)) { - handleBondInput(p, msg.trim(), - input.context); - } - } - - private void handleEconInput(Player admin, - String msg, - String target) { - net.milkbowl.vault.economy.Economy econ - = null; - try { - var reg = plugin.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault - .economy - .Economy.class); - if (reg != null) - econ = reg.getProvider(); - } catch (Exception ignored) {} - if (econ == null) { - admin.sendMessage("§c经济系统不可用"); - return; - } - if (!msg.startsWith("+") - && !msg.startsWith("-")) { - admin.sendMessage("§c格式: §a+100" - + " §7或 §c-50"); - return; - } - try { - String numStr = - msg.substring(1).trim(); - double amount = - Double.parseDouble(numStr); - if (amount <= 0) { - admin.sendMessage("§c金额必须>0"); - return; - } - if (msg.startsWith("-")) { - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp == null) { - admin.sendMessage("§c" + target - + " 不在线"); - return; - } - if (!econ.has(tp, amount)) { - admin.sendMessage("§c余额不足"); - return; - } - econ.withdrawPlayer(tp, amount); - admin.sendMessage("§a扣款成功"); - } else { - econ.depositPlayer(target, amount); - admin.sendMessage("§a到账成功"); - } - } catch (NumberFormatException e) { - admin.sendMessage("§c格式错误"); - } - } - - private void handleBondInput(Player admin, - String msg, - String target) { - if (!msg.startsWith("+") - && !msg.startsWith("-")) { - admin.sendMessage("§c格式: §a+100" - + " §7或 §c-50"); - return; - } - try { - String sign = msg.substring(0, 1); - String rest = - msg.substring(1).trim(); - String[] parts = - rest.split("\\s+", 2); - int amount = Integer.parseInt(parts[0]); - String reason = parts.length >= 2 - ? parts[1].trim() - : "管理员手动调整"; - if (amount <= 0) { - admin.sendMessage("§c金额必须>0"); - return; - } - String opName = admin.getName(); - String txType = "-".equals(sign) - ? BondManager.TX_ADMIN_DEDUCT - : BondManager.TX_ADMIN_GIVE; - - if ("-".equals(sign)) { - if (plugin.getBonds() - .isFrozen(target)) { - admin.sendMessage("§c账户冻结"); - return; - } - if (plugin.getBonds().deductBonds( - target, amount, txType, - "", opName, reason)) { - admin.sendMessage("§a扣除成功"); - } else { - admin.sendMessage("§c债券不足"); - } - } else { - if (plugin.getBonds() - .isFrozen(target)) { - admin.sendMessage("§c账户冻结"); - return; - } - plugin.getBonds().addBonds(target, - amount, txType, - "", opName, reason); - admin.sendMessage("§a发放成功"); - } - } catch (NumberFormatException e) { - admin.sendMessage("§c格式错误"); - } - } - - public void cancelInput(String player) { - pendingInputs.remove(player); - } - - static class CDKInput { - String type; - String context; - long time; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/ChatFilterManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/ChatFilterManager.java deleted file mode 100644 index 59cb5de..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/ChatFilterManager.java +++ /dev/null @@ -1,612 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.*; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ChatFilterManager { - - private final JavaPlugin plugin; - private final List whitelistUrls = - new ArrayList<>(); - private final Set whitelistPlayers = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map - punishmentRules = - new LinkedHashMap<>(); - private final Map - punishmentDurations = - new LinkedHashMap<>(); - private final Map - violationCount = - new ConcurrentHashMap<>(); - private final Map - mutedPlayers = - new ConcurrentHashMap<>(); - private final Map messages = - new LinkedHashMap<>(); - private boolean notifyAdmin = true; - private boolean notifyAll = false; - private int muteDuration = 300; - private boolean enabled = true; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean v) { - enabled = v; - } - - - private static final Pattern URL_PATTERN = - Pattern.compile( - "(?i)(?:https?://\\S+)" - + "|(?:(?:[a-zA-Z0-9]" - + "(?:[a-zA-Z0-9\\-]*" - + "[a-zA-Z0-9])?\\.)+" - + "[a-zA-Z]{2,})" - + "(?:/\\S*)?"); - private static final Pattern DOT_VARIANTS = - Pattern.compile( - "[\uff0e\u3002\u2025\u2026\u00b7]"); - private static final Pattern COLOR_CODES = - Pattern.compile( - "\u00a7[0-9a-fk-orA-FK-OR]"); - - private static final Set FILE_EXT; - static { - Set s = new HashSet<>( - Arrays.asList( - "txt", "yml", "yaml", - "json", "xml", "java", - "class", "jar", "png", - "jpg", "jpeg", "gif", - "bmp", "svg", "ico", - "webp", "mp3", "mp4", - "avi", "mkv", "wav", - "flac", "ogg", "zip", - "rar", "7z", "tar", - "gz", "cfg", "conf", - "ini", "log", "db", - "sql", "sh", "bat", - "html", "htm", "css", - "js", "md", "pdf", - "dll", "so", "exe", - "bin", "c", "cpp", - "py", "rb", "go", - "rs", "kt", "lua", - "php", "asp", "jsp")); - FILE_EXT = Collections.unmodifiableSet(s); - } - - public ChatFilterManager(JavaPlugin plugin) { - this.plugin = plugin; - } - - // ========== 配置加载 ========== - - public void loadConfig() { - whitelistUrls.clear(); - whitelistPlayers.clear(); - punishmentRules.clear(); - punishmentDurations.clear(); - messages.clear(); - - File f = new File( - plugin.getDataFolder(), "chat.txt"); - if (!f.exists()) writeDefaultFile(f); - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("白名单") - || t.equals("白名单:")) { - section = "whitelist"; - continue; - } - if (t.equals("处罚规则") - || t.equals("处罚规则:")) { - section = "punishment"; - continue; - } - if (t.equals("白名单玩家") - || t.equals("白名单玩家:")) { - section = "players"; - continue; - } - if (t.contains(":")) { - String[] kv = t.split(":", 2); - String k = kv[0].trim(); - String v = kv[1].trim(); - if (equals("启用过滤") - || equals("enabled")) { - enabled = parseBool(v); - continue; - } - - if (k.equals("通知管理员")) { - notifyAdmin = parseBool(v); - continue; - } - if (k.equals("全服通报")) { - notifyAll = parseBool(v); - continue; - } - if (k.equals("禁言时长")) { - try { - muteDuration = - Integer.parseInt(v); - } catch (Exception ignored) { - } - continue; - } - } - switch (section) { - case "whitelist": - whitelistUrls.add( - t.toLowerCase()); - break; - case "punishment": - String[] parts = t.split(":"); - if (parts.length >= 2) { - try { - int cnt = - Integer.parseInt( - parts[0] - .trim()); - String type = - parts[1].trim() - .toLowerCase(); - int dur = - parts.length >= 3 - ? Integer.parseInt( - parts[2] - .trim()) - : muteDuration; - punishmentRules.put( - cnt, type); - punishmentDurations.put( - cnt, dur); - } catch (Exception ignored) { - } - } - break; - case "players": - whitelistPlayers.add(t); - break; - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[Sdf1_chat] 配置加载失败: " - + e.getMessage()); - } - - // 从消息.txt读取chat_开头的消息 - loadMessages(); - } - - private void loadMessages() { - File msgFile = new File( - plugin.getDataFolder(), - "消息.txt"); - if (!msgFile.exists()) return; - try (BufferedReader mr = - new BufferedReader( - new InputStreamReader( - new FileInputStream( - msgFile), - StandardCharsets.UTF_8))) { - String mline; - while ((mline = mr.readLine()) != null) { - mline = mline.trim(); - if (mline.isEmpty() - || mline.startsWith("#")) - continue; - // 支持 = 和 : 两种分隔符 - int eqIdx = mline.indexOf('='); - int coIdx = mline.indexOf(':'); - int splitIdx = -1; - if (eqIdx >= 0 && coIdx >= 0) { - splitIdx = Math.min(eqIdx, coIdx); - } else if (eqIdx >= 0) { - splitIdx = eqIdx; - } else if (coIdx >= 0) { - splitIdx = coIdx; - } - if (splitIdx < 0) continue; - String k = mline.substring(0, - splitIdx).trim(); - String v = mline.substring( - splitIdx + 1).trim(); - if (k.startsWith("chat_")) { - messages.put(k, v); - } - } - } catch (Exception ignored) { - } - } - - - private void writeDefaultFile(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("启用过滤: " + enabled); - pw.println("通知管理员: true"); - pw.println("全服通报: false"); - pw.println("禁言时长: 300"); - pw.println(); - pw.println("白名单:"); - pw.println("*.minecraft.net"); - pw.println("baidu.com"); - pw.println("github.com"); - pw.println(); - pw.println("处罚规则:"); - pw.println("1:warn:0"); - pw.println("2:warn:0"); - pw.println("3:mute:300"); - pw.println("5:mute:600"); - pw.println("8:kick:0"); - pw.println("10:ban:0"); - pw.println(); - pw.println("白名单玩家:"); - } catch (IOException ignored) { - } - } - - public void saveConfig() { - File f = new File( - plugin.getDataFolder(), "chat.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("通知管理员: " + notifyAdmin); - pw.println("全服通报: " + notifyAll); - pw.println("禁言时长: " + muteDuration); - pw.println(); - pw.println("白名单:"); - for (String u : whitelistUrls) - pw.println(u); - pw.println(); - pw.println("处罚规则:"); - for (Map.Entry entry - : punishmentRules.entrySet()) { - int dur = punishmentDurations - .getOrDefault(entry.getKey(), - muteDuration); - pw.println(entry.getKey() + ":" - + entry.getValue() - + ":" + dur); - } - pw.println(); - pw.println("白名单玩家:"); - for (String p : whitelistPlayers) - pw.println(p); - } catch (IOException e) { - plugin.getLogger().severe( - "[Sdf1_chat] 保存失败: " - + e.getMessage()); - } - } - - // ========== 消息 ========== - - public String msg(String key, - String... args) { - String template = messages.getOrDefault( - key, key); - for (int i = 0; i < args.length - 1; - i += 2) { - template = template.replace( - "{" + args[i] + "}", - args[i + 1]); - } - return template; - } - - // ========== URL检测 ========== - - private String normalizeMessage(String msg) { - msg = COLOR_CODES.matcher(msg) - .replaceAll(""); - msg = DOT_VARIANTS.matcher(msg) - .replaceAll("."); - return msg; - } - - public List extractUrls( - String message) { - String normalized = - normalizeMessage(message); - List urls = new ArrayList<>(); - Matcher m = URL_PATTERN - .matcher(normalized); - while (m.find()) { - String url = m.group().toLowerCase(); - url = url.replaceAll( - "[\\s.,;!?,。;!?、)》」』】" - + "\\]\\[\\((]+$", - ""); - if (!url.isEmpty()) urls.add(url); - } - return urls; - } - - public boolean isWhitelisted(String url) { - String clean = url - .replaceAll("^https?://", "") - .toLowerCase(); - int colon = clean.indexOf(':'); - int slash = clean.indexOf('/'); - if (colon > 0 && (slash < 0 - || colon < slash)) { - clean = clean.substring(0, colon); - } - slash = clean.indexOf('/'); - if (slash > 0) { - clean = clean.substring(0, slash); - } - for (String entry : whitelistUrls) { - String e = entry.toLowerCase().trim(); - if (e.startsWith("*.")) { - String suffix = e.substring(1); - if (clean.endsWith(suffix) - || clean.equals( - suffix.substring(1))) - return true; - } else { - if (clean.equals(e) - || clean.endsWith( - "." + e)) - return true; - } - } - return false; - } - - public boolean isLikelyDomain(String url) { - int lastDot = url.lastIndexOf('.'); - if (lastDot < 0 - || lastDot >= url.length() - 1) - return false; - String ext = url.substring(lastDot + 1) - .toLowerCase(); - return !FILE_EXT.contains(ext); - } - - // ========== 玩家操作 ========== - - public boolean isMuted(String name) { - Long expiry = mutedPlayers.get(name); - if (expiry == null) return false; - if (System.currentTimeMillis() - >= expiry) { - mutedPlayers.remove(name); - return false; - } - return true; - } - - public String fmtDuration(int seconds) { - if (seconds <= 0) return "0秒"; - int day = seconds / 86400; - int hr = (seconds % 86400) / 3600; - int min = (seconds % 3600) / 60; - int sec = seconds % 60; - StringBuilder sb = new StringBuilder(); - if (day > 0) sb.append(day).append("天"); - if (hr > 0) - sb.append(hr).append("小时"); - if (min > 0) - sb.append(min).append("分"); - if (sec > 0 || sb.length() == 0) - sb.append(sec).append("秒"); - return sb.toString(); - } - - public void applyPunishment(Player player, - int violation) { - String type = "warn"; - int duration = muteDuration; - int maxRule = 0; - for (Map.Entry entry - : punishmentRules.entrySet()) { - if (violation >= entry.getKey() - && entry.getKey() > maxRule) { - maxRule = entry.getKey(); - type = entry.getValue(); - duration = punishmentDurations - .getOrDefault( - entry.getKey(), - muteDuration); - } - } - if (type.equals("mute")) { - if (isMuted(player.getName())) - return; - mutedPlayers.put(player.getName(), - System.currentTimeMillis() - + (long) duration - * 1000L); - } - final String fType = type; - final int fDur = duration; - final int fV = violation; - final String fName = player.getName(); - Bukkit.getScheduler().runTask(plugin, - () -> { - Player p = Bukkit.getPlayer(fName); - if (p == null) return; - switch (fType) { - case "warn": - // 警告不处罚,只提示 - break; - case "mute": - p.sendMessage( - msg("chat_muted")); - break; - case "kick": - p.kickPlayer( - msg("chat_muted")); - break; - case "ban": - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban " + fName - + " §c多次发送违规链接"); - break; - case "banip": - InetSocketAddress addr = - p.getAddress(); - if (addr != null) { - String ip = addr - .getAddress() - .getHostAddress(); - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban-ip " + ip - + " §c发送违规链接"); - } - break; - } - }); - } - - public void unmutePlayer(String name) { - mutedPlayers.remove(name); - } - - public void resetPlayer(String name) { - mutedPlayers.remove(name); - violationCount.remove(name); - } - - public boolean isPlayerWhitelisted( - String name) { - return whitelistPlayers.contains(name); - } - - public boolean isNotifyAdmin() { - return notifyAdmin; - } - - public boolean isNotifyAll() { - return notifyAll; - } - - public int getViolationCount(String name) { - return violationCount - .getOrDefault(name, 0); - } - - public void incrementViolation( - String name) { - violationCount.put(name, - getViolationCount(name) + 1); - } - - public List getWhitelistUrls() { - return whitelistUrls; - } - - public Set getWhitelistPlayers() { - return whitelistPlayers; - } - - public Map - getPunishmentRules() { - return punishmentRules; - } - - public Map - getPunishmentDurations() { - return punishmentDurations; - } - - public Map - getMutedPlayers() { - return mutedPlayers; - } - - public void setNotifyAdmin(boolean v) { - notifyAdmin = v; - } - - public void setNotifyAll(boolean v) { - notifyAll = v; - } - - public void setMuteDuration(int v) { - muteDuration = v; - } - - public void addUrl(String url) { - whitelistUrls.add( - url.toLowerCase().trim()); - saveConfig(); - } - - public boolean removeUrl(String url) { - boolean r = whitelistUrls.remove( - url.toLowerCase().trim()); - saveConfig(); - return r; - } - - public void addWhitelistPlayer( - String name) { - whitelistPlayers.add(name); - saveConfig(); - } - - public boolean removeWhitelistPlayer( - String name) { - boolean r = - whitelistPlayers.remove(name); - saveConfig(); - return r; - } - - public void cleanExpired() { - long now = System.currentTimeMillis(); - mutedPlayers.entrySet().removeIf( - e -> e.getValue() <= now); - } - - private boolean parseBool(String v) { - String s = v.toLowerCase().trim(); - return s.equals("true") - || s.equals("on") - || s.equals("yes") - || s.equals("1") - || s.contains("开启") - || s.contains("启用") - || s.contains("是"); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/ChatInputManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/ChatInputManager.java deleted file mode 100644 index 49f53fd..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/ChatInputManager.java +++ /dev/null @@ -1,842 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; - -public class ChatInputManager { - - public enum InputType { - NONE, - CHANGE_PWD_STEP1, - CHANGE_PWD_STEP2, - SET_EMAIL, - SET_EMAIL_VERIFY, - ADMIN_AUTH, - ADMIN_SET_POINTS, - ADMIN_SET_PWD, - ADMIN_DELETE_CONFIRM, - ADMIN_SET_AFK_TIME, - SMTP_CONFIG, - SMTP_VERIFY, - BACK_CHECKIN, - INVITE_INPUT_CODE, - TICKET_TITLE, - TICKET_DESC, - TICKET_SCORE, - TICKET_REJECT, - TICKET_ASSIGN, - TICKET_INVITE_PROVIDER, - TICKET_REMOVE_PROVIDER, - TICKET_COMPLETE, - TICKET_REPORT_ISSUE, - TICKET_REPLY, - TICKET_ADMIN_TYPE, - TICKET_ADMIN_TITLE, - TICKET_ADMIN_DESC, - TICKET_ADMIN_PROVIDER, - BACK_CHECK_ECONOMY_CONFIRM, - USER_MGMT_SEARCH, - - } - - public static class PlayerState { - public InputType type = InputType.NONE; - public String targetPlayer = ""; - public String ticketType = ""; - public String ticketTitle = ""; - public String ticketDesc = ""; - public int ticketId = 0; - public int smtpStep = 0; - public int scoreValue = 0; - - } - - private final Map states = - new HashMap<>(); - private Main plugin; - - public PlayerState getState(Player p) { - return states.computeIfAbsent( - p.getName(), - k -> new PlayerState()); - } - - public void reset(Player p) { - states.remove(p.getName()); - } - public boolean isInFlow(Player p) { - PlayerState state = states.get(p.getName()); - return state != null - && state.type != InputType.NONE; - } - - public void handleInput(Player p, String msg, - Main mainPlugin) { - if (this.plugin == null) - this.plugin = mainPlugin; - PlayerState state = getState(p); - ConfigManager config = - mainPlugin.getConfig2(); - DatabaseManager db = mainPlugin.getDb(); - - switch (state.type) { - - case CHANGE_PWD_STEP1: { - String name = p.getName(); - String salt = (String) db.getField( - name, "password_salt"); - String hash = - PasswordUtils.hash(msg, salt); - boolean mainOk = - db.checkPassword(name, hash); - if (mainOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "main"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - boolean tempOk = - db.checkPasswordOrTemp( - name, hash); - if (tempOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "temp"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - p.sendMessage(config.msg( - "password_wrong")); - return; - } - - case CHANGE_PWD_STEP2: { - String name = p.getName(); - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String oldHash = - (String) db.getField(name, - "password_hash"); - String oldSalt = - (String) db.getField(name, - "password_salt"); - String newSalt = - PasswordUtils.generateSalt(); - String newHash = - PasswordUtils.hash( - msg, newSalt); - if (newHash.equals(oldHash)) { - p.sendMessage(config.msg( - "password_same")); - reset(p); - return; - } - mainPlugin.recordPasswordChange( - name, oldHash, oldSalt); - db.setField(name, - "password_hash", newHash); - db.setField(name, - "password_salt", newSalt); - mainPlugin - .getNeedsPasswordChange() - .remove(name); - p.sendMessage(config.msg( - "password_changed")); - reset(p); - return; - } - - case SET_EMAIL: { - if (!msg.contains("@") - || !msg.contains(".")) { - p.sendMessage(config.msg( - "email_format_error")); - reset(p); - return; - } - String oldEmail = (String) - db.getField(p.getName(), - "email"); - String emailAddr = msg.trim(); - // 已绑定邮箱:验证登录密码 - if (oldEmail != null - && !oldEmail.isEmpty()) { - state.targetPlayer = emailAddr; - state.type = - InputType.SET_EMAIL_VERIFY; - state.ticketTitle = - "need_pwd"; - p.sendMessage( - "§e您已绑定邮箱: " - + oldEmail); - p.sendMessage( - "§e请验证登录密码以确认修改:"); - return; - } - // 未绑定邮箱:直接发验证码 - String code = String.valueOf( - (int) (Math.random() - * 900000 - + 100000)); - state.targetPlayer = emailAddr; - state.ticketTitle = code; - state.type = - InputType.SET_EMAIL_VERIFY; - final String to = emailAddr; - final String c = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - boolean sent = mainPlugin - .getEmail() - .sendVerifyCode( - to, c); - Bukkit.getScheduler() - .runTask(mainPlugin, - () -> { - if (sent) { - fp.sendMessage( - "§a验证码已发送到 " - + to); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败"); - reset(fp); - } - }); - }); - return; - } - - - case SET_EMAIL_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§c邮箱设置已取消"); - reset(p); - return; - } - String expected = - state.ticketTitle; - if (msg.trim().equals(expected)) { - db.setField(p.getName(), - "email", - state.targetPlayer); - p.sendMessage( - "§a邮箱已设置: " - + state.targetPlayer); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case ADMIN_AUTH: { - if (msg.equals( - config.adminPassword)) { - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openAdmin(p)); - } else { - p.sendMessage( - "§c管理密码错误"); - } - reset(p); - return; - } - - case ADMIN_SET_POINTS: { - try { - int pts = Integer.parseInt( - msg.trim()); - db.setField( - state.targetPlayer, - "points", pts); - p.sendMessage("§a已将 " - + state.targetPlayer - + " 积分设为 " + pts); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - String tgt = - state.targetPlayer; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openUserDetail( - p, tgt)); - reset(p); - return; - } - - case ADMIN_SET_PWD: { - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String tgt = state.targetPlayer; - String salt = - PasswordUtils - .generateSalt(); - String hash = - PasswordUtils.hash( - msg, salt); - db.setField(tgt, - "temp_password", hash); - db.setField(tgt, "temp_pw_expire", - System.currentTimeMillis() - + 300000L); - db.setField(tgt, - "temp_pw_used", 0); - p.sendMessage("§a已为 " + tgt - + " 设置临时密码"); - Player tp = - Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) - tp.kickPlayer( - "§c管理员已设置临时密码," - + "请使用临时密码登录"); - // 邮件通知 - String emailAddr = (String) - db.getField(tgt, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String time = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()); - String body = "玩家 " + tgt - + ":\n您的密码已于 " - + time - + " 被管理员重置为临时密码。\n" - + "如果是本人操作请忽略。\n" - + "如非本人操作请立即联系管理员。"; - final String to = emailAddr; - final String fBody = body; - final String fName = tgt; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> - mainPlugin.getEmail() - .sendTempPassword( - to, - fName, - fBody)); - } - reset(p); - return; - } - - case ADMIN_DELETE_CONFIRM: { - if (msg.equals( - state.targetPlayer)) { - db.deleteUser( - state.targetPlayer); - p.sendMessage("§a已删除 " - + state.targetPlayer); - Player tgt = - Bukkit.getPlayer( - state.targetPlayer); - if (tgt != null - && tgt.isOnline()) - tgt.kickPlayer( - "§c账号已被删除"); - } else { - p.sendMessage( - "§c输入不匹配,已取消"); - } - reset(p); - return; - } - - case ADMIN_SET_AFK_TIME: { - try { - int minutes = - Integer.parseInt( - msg.trim()); - config.afkTimeout = - minutes * 60; - config.saveSettings(); - p.sendMessage( - "§a挂机时长已设为 " - + minutes - + " 分钟"); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - reset(p); - return; - } - - case SMTP_CONFIG: { - String m = msg.trim(); - if ("000".equals(m) - || "取消取消取消" - .equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if ("取消".equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - int step = state.smtpStep; - if (step <= 1) { - if (!"0".equals(m)) - config.setSmtp( - "smtp地址", m); - state.smtpStep = 2; - p.sendMessage("§e当前端口: " - + config.getSmtp( - "smtp端口")); - p.sendMessage( - "§e输入端口(0跳过):"); - } else if (step == 2) { - if (!"0".equals(m)) - config.setSmtp( - "smtp端口", m); - state.smtpStep = 3; - p.sendMessage( - "§e输入发件邮箱:"); - } else if (step == 3) { - if (!"0".equals(m)) - config.setSmtp( - "smtp账号", m); - state.smtpStep = 4; - p.sendMessage( - "§e输入SMTP密码(0跳过):"); - } else if (step == 4) { - if (!"0".equals(m)) - config.setSmtp( - "smtp密码", m); - state.smtpStep = 5; - p.sendMessage( - "§e输入接收邮箱(留空发到发件邮箱):"); - } else if (step == 5) { - String recv = m; - if (recv.isEmpty() - || "0".equals(recv)) - recv = config.getSmtp( - "smtp账号"); - config.setSmtp( - "验证码接收邮箱", recv); - if (!config - .canSendSmtpVerify()) { - p.sendMessage( - "§c发送间隔不足1分钟"); - reset(p); - return; - } - String code = config - .generateSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String acct = config.getSmtp( - "smtp账号"); - if (acct.isEmpty() - || addr.isEmpty()) { - p.sendMessage( - "§c地址或账号为空"); - reset(p); - return; - } - config.recordSmtpSendTime(); - final String fRecv = recv; - final String fCode = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> fp.sendMessage( - "§e正在发送验证码...")); - boolean sent = - mainPlugin.getEmail() - .sendVerifyCode( - fRecv, - fCode); - final boolean fSent = sent; - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> { - if (fSent) { - state.type = - InputType - .SMTP_VERIFY; - state.smtpStep = 0; - fp.sendMessage( - "§a验证码已发送到 " - + fRecv); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败,查看控制台"); - reset(fp); - } - }); - }); - } - return; - } - - case SMTP_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if (config.checkSmtpVerifyCode( - msg.trim())) { - config.saveSmtp(); - config.clearSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String port = config.getSmtp( - "smtp端口"); - String acct = config.getSmtp( - "smtp账号"); - String recv = config.getSmtp( - "验证码接收邮箱"); - boolean ssl = config.smtpSsl; - p.sendMessage("§a§l=== SMTP配置完成 ==="); - p.sendMessage("§7地址: §f" + addr); - p.sendMessage("§7端口: §f" + port); - p.sendMessage("§7账号: §f" + acct); - p.sendMessage("§7接收: §f" + recv); - p.sendMessage("§7加密: §f" + ssl); - p.sendMessage("§a§l===================="); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case BACK_CHECKIN: { - String result = - mainPlugin.getCheckIn() - .backCheckIn(p, msg); - if (result == null) { - // 等待选择(NEED_CONFIRM) - return; - } - if (result.startsWith( - "NEED_CONFIRM:")) { - String[] parts = - result.split(":"); - // 存储到Main的Map - mainPlugin.pendingBackCheck.put( - p.getName(), - parts[1] + ":" + parts[2]); - return; - } - if (result.startsWith("§c")) { - p.sendMessage(result); - } else { - p.sendMessage(result); - } - reset(p); - return; - } - - - case INVITE_INPUT_CODE: { - String code = msg.trim(); - String myName = p.getName(); - String myCode = - (String) db.getField( - myName, - "invite_code"); - if (code.isEmpty()) { - p.sendMessage( - "§c邀请码不能为空"); - reset(p); - return; - } - if (code.equals(myCode)) { - p.sendMessage( - "§c不能用自己邀请码"); - reset(p); - return; - } - boolean found = false; - for (Map u : - db.getAllUsers()) { - String uc = (String) - u.getOrDefault( - "invite_code", ""); - if (code.equals(uc)) { - String inv = (String) - u.get("player_name"); - db.setField(myName, - "invited_by", inv); - db.addPoints(inv, 50); - p.sendMessage( - "§a邀请成功!邀请人: " - + inv); - Player ip = - Bukkit.getPlayer(inv); - if (ip != null - && ip.isOnline()) - ip.sendMessage("§a" - + myName - + " 使用了您的邀请码"); - found = true; - break; - } - } - if (!found) - p.sendMessage( - "§c邀请码不存在"); - reset(p); - return; - } - - case TICKET_TITLE: { - state.ticketTitle = msg; - state.type = - InputType.TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return; - } - - case TICKET_DESC: { - String tt = state.ticketType; - String tl = state.ticketTitle; - reset(p); - mainPlugin.getTicket() - .submitTicket( - p, tt, tl, msg); - return; - } - - case TICKET_SCORE: { - int tid = state.ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - reset(p); - mainPlugin.getTicket() - .confirmAndScore( - p, tid, sc); - } catch ( - NumberFormatException e) { - reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return; - } - - case TICKET_REJECT: { - int tid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .rejectTicket( - p, tid, msg); - return; - } - - case TICKET_ASSIGN: { - int tid = state.ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - reset(p); - mainPlugin.getTicket() - .assignToProvider( - p, tid, prov, - price); - final int fTid = tid; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getTicket() - .openDetail( - p, fTid)); - return; - } - - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return; - } - db.addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return; - } - - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.isServiceProvider(tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return; - } - db.removeServiceProvider(tgt); - p.sendMessage("§a已移除 " + tgt); - return; - } - - case TICKET_COMPLETE: { - try { - int tid = Integer.parseInt( - msg.trim()); - mainPlugin.getTicket() - .completeAsProvider( - p, tid); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效工单号"); - } - reset(p); - return; - } - - case TICKET_REPORT_ISSUE: { - String report = msg.trim(); - reset(p); - p.sendMessage("§e异常已上报"); - String tag = config.adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§c§l[工单异常] §f" - + p.getName() - + ": " + report); - } - } - return; - } - - case TICKET_REPLY: { - int replyTid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .replyTicket( - p, replyTid, msg); - return; - } - - case TICKET_ADMIN_TYPE: { - state.ticketType = msg.trim(); - state.type = - InputType - .TICKET_ADMIN_TITLE; - p.sendMessage( - "§e请输入工单标题:"); - return; - } - - case TICKET_ADMIN_TITLE: { - state.ticketTitle = msg; - state.type = - InputType - .TICKET_ADMIN_DESC; - p.sendMessage( - "§e请输入工单描述:"); - return; - } - - case TICKET_ADMIN_DESC: { - state.targetPlayer = msg; - state.type = - InputType - .TICKET_ADMIN_PROVIDER; - p.sendMessage( - "§e输入服务商名和价格:"); - p.sendMessage( - "§7例: playerA 20"); - return; - } - - case TICKET_ADMIN_PROVIDER: { - String[] parts2 = msg.trim() - .split("\\s+"); - String prov2 = parts2[0]; - int price2 = 0; - if (parts2.length >= 2) { - try { - price2 = Integer.parseInt( - parts2[1]); - } catch ( - NumberFormatException e) { - } - } - String t2 = state.ticketType; - String ti2 = state.ticketTitle; - String d2 = state.targetPlayer; - reset(p); - mainPlugin.getTicket() - .adminCreateAndAssign( - p, t2, ti2, - d2, prov2, price2); - return; - } - - default: - reset(p); - return; - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/CheckInManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/CheckInManager.java deleted file mode 100644 index b594b0f..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/CheckInManager.java +++ /dev/null @@ -1,302 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -public class CheckInManager { - - private final Main plugin; - - public CheckInManager(Main plugin) { - this.plugin = plugin; - } - - // ===== 今日是否已签到 ===== - public boolean isCheckedInToday( - String name) { - String last = (String) plugin.getDb() - .getField(name, - "last_checkin_date"); - String today = new SimpleDateFormat( - "yyyy-MM-dd") - .format(new Date()); - return today.equals(last); - } - - // ===== 签到 ===== - // ===== 签到 ===== - public String checkIn(Player p) { - String name = p.getName(); - if (isCheckedInToday(name)) { - return plugin.getConfig2() - .msg("checkin_already"); - } - - Object streakObj = plugin.getDb() - .getField(name, "checkin_streak"); - int streak = streakObj != null - ? ((Number) streakObj).intValue() - : 0; - - String lastDate = (String) plugin.getDb() - .getField(name, "last_checkin_date"); - String today = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date()); - - if (lastDate != null && !lastDate.isEmpty()) { - try { - java.util.Date lastD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(lastDate); - java.util.Date todayD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(today); - long diff = todayD.getTime() - - lastD.getTime(); - if (diff > 86400000L * 2) { - streak = 0; - } else if (diff > 86400000L) { - streak++; - } - } catch (Exception ignored) { - } - } - - if (streak == 0) streak = 1; - else streak++; - - double multi = 1.0; - if (streak >= 21) multi = 1.5; - else if (streak >= 14) multi = 1.3; - else if (streak >= 7) multi = 1.1; - - int basePoints = - plugin.getConfig2().checkinPoints; - int points = (int) (basePoints * multi); - - plugin.getDb().addPoints(name, points); - plugin.getDb().setField(name, - "checkin_streak", streak); - plugin.getDb().setField(name, - "last_checkin_date", today); - - Object totalObj = plugin.getDb() - .getField(name, "total_checkin_days"); - int total = totalObj != null - ? ((Number) totalObj).intValue() - : 0; - plugin.getDb().setField(name, - "total_checkin_days", total + 1); - - // ★ 债券盲盒奖励(6参数版,写入流水)★ - int bondMin = - plugin.getConfig2().checkinBondMin; - int bondMax = - plugin.getConfig2().checkinBondMax; - if (bondMin <= 0) bondMin = 1; - if (bondMax < bondMin) bondMax = bondMin; - int bondReward = bondMin - + new java.util.Random() - .nextInt(bondMax - bondMin + 1); - plugin.getBonds().addBonds(name, bondReward, - "daily_sign", "", "签到系统", - "每日签到连续" + streak + "天"); - - return plugin.getConfig2() - .msg("checkin_success") - .replace("{points}", - String.valueOf(points)) - .replace("{streak}", - String.valueOf(streak)) - .replace("{multi}", - String.valueOf(multi)) - + " §e+" + bondReward + "债券"; - } - - // ===== 获取连续天数 ===== - public int getStreak(String name) { - Object val = plugin.getDb() - .getField(name, - "checkin_streak"); - if (val instanceof Number) { - return ((Number) val) - .intValue(); - } - return 0; - } - // 标准化日期 - private String normalizeDate(String input) { - String s = input.trim().replace("/", "-"); - if (s.matches("\\d{8}")) { - return s.substring(0, 4) + "-" - + s.substring(4, 6) + "-" - + s.substring(6, 8); - } - return s; - } - // ===== 补签入口 ===== - public String backCheckIn( - Player p, String dateStr) { - String name = p.getName(); - String normalized = - normalizeDate(dateStr); - - // 严格日期验证 - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd"); - sdf.setLenient(false); - java.util.Date target; - try { - target = sdf.parse(normalized); - } catch (Exception ex) { - return "§c无效日期: " + dateStr; - } - String reformat = sdf.format(target); - if (!reformat.equals(normalized)) { - return "§c无效日期: " + dateStr; - } - - String today = sdf.format(new Date()); - if (normalized.compareTo(today) >= 0) { - return "§c今天不需要补签"; - } - - String threeDaysAgo = getPastDate(3); - if (normalized.compareTo( - threeDaysAgo) < 0) { - return "§c只能补签3天内" - + "(最多到 " + threeDaysAgo + ")"; - } - - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - boolean alreadyBacked = false; - if (!existing.isEmpty()) { - for (String d : existing.split(",")) { - if (normalizeDate(d.trim()) - .equals(normalized)) { - alreadyBacked = true; - break; - } - } - } - if (alreadyBacked) { - return "§c该日期已补签: " + normalized; - } - - Object normalCheck = - plugin.getDb().getField( - name, "last_checkin_date"); - String lastCheckin = normalCheck != null - ? normalCheck.toString() : ""; - if (lastCheckin.equals(normalized)) { - return "§c该日期已签到: " + normalized; - } - - // ★ 补签消耗:3债券 + 10积分 - // 积分不够时:用 9债券(3倍)代替 ★ - int bondCost = 3; - int pointCost = 10; - int bondAltCost = 9; // 3倍债券替代 - - int myBonds = plugin.getBonds() - .getBonds(name); - Object ptsObj = plugin.getDb() - .getField(name, "points"); - int myPoints = ptsObj != null - ? ((Number) ptsObj).intValue() - : 0; - - - // 方案A:3债券 + 10积分 - boolean canA = myBonds >= bondCost - && myPoints >= pointCost; - // 方案B:9债券(积分不够时) - boolean canB = myBonds >= bondAltCost - && myPoints < pointCost; - - if (!canA && !canB) { - if (myPoints < pointCost - && myBonds < bondAltCost) { - return "§c补签费用不足" - + "(需3债券+10积分," - + "或9债券代替)" - + " 当前: " - + myBonds + "债券 " - + myPoints + "积分"; - } - if (myBonds < bondCost) { - return "§c债券不足(需" - + bondCost + "枚)"; - } - return "§c积分不足(需" - + pointCost + "分)"; - } - - // 执行扣款 - if (canA) { - // 方案A:3债券 + 10积分 - plugin.getBonds().deductBonds( - name, bondCost, - "admin_deduct", "", - "补签系统", "补签扣款"); - plugin.getDb().deductPoints( - name, pointCost); - } else { - // 方案B:9债券代替 - plugin.getBonds().deductBonds( - name, bondAltCost, - "admin_deduct", "", - "补签系统", "补签扣款(债券替代)"); - } - - doBackCheckIn(p, normalized); - - String costMsg = canA - ? bondCost + "债券+" + pointCost + "积分" - : bondAltCost + "债券(替代)"; - return "§a补签成功!日期: " - + normalized - + " §7消耗: " + costMsg; - } - - - - // ===== 执行补签 ===== - // ===== 执行补签 ===== - public void doBackCheckIn( - Player p, String dateStr) { - String name = p.getName(); - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - String updated = existing.isEmpty() - ? dateStr - : existing + "," + dateStr; - plugin.getDb().setField(name, - "backcheck_dates", updated); - int streak = getStreak(name); - plugin.getDb().setField(name, - "checkin_streak", streak + 1); - // ★ 补签不发放奖励,只记录 - } - - // ===== 获取N天前日期 ===== - private String getPastDate( - int daysAgo) { - Calendar cal = - Calendar.getInstance(); - cal.add(Calendar.DAY_OF_MONTH, - -daysAgo); - return new SimpleDateFormat( - "yyyy-MM-dd") - .format(cal.getTime()); - } -} \ No newline at end of file diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/CommissionManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/CommissionManager.java deleted file mode 100644 index cb24687..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/CommissionManager.java +++ /dev/null @@ -1,227 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.logging.Logger; - -public class CommissionManager { - - private final Main main; - private final Logger logger; - private final List rules = - new ArrayList<>(); - - public CommissionManager(Main main) { - this.main = main; - this.logger = main.getLogger(); - loadRules(); - } - - public void loadRules() { - rules.clear(); - File f = new File( - main.getDataFolder(), - "提成规则.txt"); - if (!f.exists()) { - writeDefault(f); - } - try (BufferedReader br = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets - .UTF_8))) { - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() - || line.startsWith("#")) - continue; - // 格式: 邀请人数:提成百分比 - // 如 "5:15" = 邀请5人提成15% - String[] parts = - line.split(":"); - if (parts.length >= 2) { - try { - int count = Integer.parseInt( - parts[0].trim()); - double rate = Double - .parseDouble( - parts[1] - .trim()) / 100.0; - rules.add(new double[]{ - count, rate}); - } catch (Exception ignored) { - } - } - } - // 按人数排序(从大到小) - rules.sort((a, b) -> - Double.compare(b[0], a[0])); - logger.info( - "[Commission] 加载 " - + rules.size() + " 条提成规则"); - } catch (Exception e) { - logger.warning( - "[Commission] 加载失败: " - + e.getMessage()); - } - } - - private void writeDefault(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# 邀请提成规则"); - pw.println("# 格式: 邀请人数:提成百分比"); - pw.println("# 邀请10人提成20%"); - pw.println("10:20"); - pw.println("# 邀请5人提成15%"); - pw.println("5:15"); - pw.println("# 邀请1人提成10%"); - pw.println("1:10"); - } catch (IOException ignored) { - } - } - - /** - * 获取邀请人的提成比例 - * 根据已邀请人数匹配最高规则 - */ - public double getCommissionRate( - String inviter) { - int inviteCount = getInviteCount( - inviter); - for (double[] rule : rules) { - if (inviteCount >= (int) rule[0]) { - return rule[1]; - } - } - return 0; - } - - /** - * 获取邀请人数 - */ - public int getInviteCount( - String inviter) { - try { - Object val = - main.getDb().getField( - inviter, - "monthly_invite_count"); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - Object total = - main.getDb().getField( - inviter, - "total_invite_count"); - if (total instanceof Number) { - return ((Number) total).intValue(); - } - } catch (Exception ignored) { - } - return 0; - } - - /** - * 发放提成 - * 被邀请人获得奖励时,邀请人获得提成 - */ - public void payCommission( - String invitee, - double originalAmount) { - // 查找邀请人 - String inviter = getInviter(invitee); - if (inviter == null - || inviter.isEmpty()) return; - - double rate = getCommissionRate( - inviter); - if (rate <= 0) return; - - double commission = - originalAmount * rate; - if (commission <= 0) return; - - // 通过 Vault 发放 - var rsp = main.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault - .economy.Economy.class); - if (rsp == null) return; - net.milkbowl.vault.economy.Economy econ = - rsp.getProvider(); - if (econ == null) return; - - econ.depositPlayer(inviter, commission); - - // 通知邀请人 - Player inviterP = - main.getServer() - .getPlayer(inviter); - if (inviterP != null - && inviterP.isOnline()) { - inviterP.sendMessage( - "§a§l[邀请提成] §f" - + invitee + " 完成任务," - + "你获得 §e" - + String.format("%.2f", - commission) - + " §f游戏币提成 (" - + (int)(rate * 100) + "%)"); - } - - logger.info( - "[Commission] 提成: " - + inviter + " ← " - + invitee + " | " - + String.format("%.2f", - originalAmount) - + " × " - + (int)(rate * 100) + "% = " - + String.format("%.2f", - commission)); - } - - /** - * 获取邀请人 - */ - private String getInviter(String player) { - try { - Object val = - main.getDb().getField( - player, - "invited_by"); - if (val != null - && !val.toString() - .isEmpty()) { - return val.toString(); - } - } catch (Exception ignored) { - } - return null; - } - - /** - * 判断金额是否包含经济内容 - */ - public boolean hasEconomyContent( - String text) { - String t = text.toLowerCase(); - return t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额"); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/ConfigManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/ConfigManager.java deleted file mode 100644 index a15ada0..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/ConfigManager.java +++ /dev/null @@ -1,372 +0,0 @@ -package Sdf1_login; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; - -public class ConfigManager { - - private final File dataFolder; - private final Map messages - = new LinkedHashMap<>(); - private final Map smtpSettings - = new LinkedHashMap<>(); - - // ===== 公共字段 ===== - public String adminTag = "admin"; - public String adminPassword = "qweasd"; - public int maxAccountsPerIP = 3; - public String approvalMode = "manual"; - public int autoApproveDelayMinutes = 30; - public int loginTimeout = 60; - public boolean afkEnabled = false; - public int afkTimeout = 300; - public boolean smtpSsl = true; - public String smtpVerifyCode = ""; - public long smtpVerifyExpire = 0; - public long smtpLastSendTime = 0; - public String smtpConfiguringPlayer = ""; - public String broadcastServerUrl = ""; - public int httpPort = 8080; - public String garbageTag = "admin"; - - public double checkinRewardFixed = 0; - public double checkinRewardMin = 0; - public double checkinRewardMax = 0; - public boolean checkinGivePoints = true; - public int checkinPoints = 10; - public String checkinRewardType = "none"; - public int backCheckPointMultiplier = 5; - public int checkinBondMin = 1; - public int checkinBondMax = 3; - - public double providerPointsPerScore = 2.0; - public int baseEconomyReward = 100; - public double providerEconomyPerScore = 50.0; - public int requesterPointsReward = 10; - - public boolean garbageEnabled = true; - public int garbageInterval = 300; - public int garbageMaxRounds = 1; - - // ★ 构造函数接收 File,和 Main.java 调用一致 ★ - public ConfigManager(File dataFolder) { - this.dataFolder = dataFolder; - } - public String rewardChannel = "债券"; // bonds = 债券,economy = Vault经济 - - - // ==================== 消息 ==================== - - public void loadMessages() { - messages.clear(); - File file = new File(dataFolder, "消息.txt"); - if (!file.exists()) createDefaultMessages(file); - loadMap(file, messages); - } - - public String msg(String key) { - String val = messages.getOrDefault(key, key); - val = val.replace("", "&l"); - val = val.replace("", "&r"); - val = val.replace("
", "\n"); - val = val.replace("\\n", "\n"); - val = val.replaceAll( - "&([0-9a-fk-orA-FK-OR])", "§$1"); - return val; - } - - private void createDefaultMessages(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 消息配置 ====="); - L.add("login_timeout=§c登录超时"); - L.add("not_registered=§e您尚未注册"); - L.add("not_logged_in=§e您尚未登录"); - L.add("already_registered=§c您已注册过"); - L.add("already_logged_in=§c您已登录"); - L.add("reg_success=§a注册成功!"); - L.add("login_success=§a登录成功!"); - L.add("login_failed=§c密码错误"); - L.add("password_wrong=§c密码错误"); - L.add("password_changed=§a密码修改成功!"); - L.add("password_format_error=§c密码需6位以上含大小写和数字"); - L.add("password_same=§c新密码不能与旧密码相同"); - L.add("checkin_already=§c您今日已签到"); - L.add("checkin_success=§a签到成功!获得 {points} 积分"); - L.add("chat_muted=§c你已被禁言"); - L.add("未知参数=§c未知参数或权限不足"); - writeLines(f, L); - } - - // ==================== SMTP ==================== - - public void loadSmtp() { - smtpSettings.clear(); - File file = new File(dataFolder, "SMTP设置.txt"); - if (!file.exists()) createDefaultSmtp(file); - loadMap(file, smtpSettings); - } - - public String getSmtp(String key) { - return smtpSettings.getOrDefault(key, ""); - } - - public void setSmtp(String key, String value) { - smtpSettings.put(key, value); - } - - public void saveSmtp() { - File file = new File(dataFolder, "SMTP设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== SMTP 配置 ====="); - for (Map.Entry e - : smtpSettings.entrySet()) { - L.add(e.getKey() + "=" + e.getValue()); - } - writeLines(file, L); - } - - private void createDefaultSmtp(File f) { - List L = new ArrayList<>(); - L.add("# ===== SMTP 配置 ====="); - L.add("smtp地址=smtp.example.com"); - L.add("smtp端口=465"); - L.add("smtp账号="); - L.add("smtp密码="); - L.add("发件人名称=Sdf1_login"); - L.add("smtp加密=true"); - writeLines(f, L); - } - - // ==================== 插件设置 ==================== - - public void loadSettings() { - File file = new File(dataFolder, "插件设置.txt"); - if (!file.exists()) createDefaultSettings(file); - Map m = new LinkedHashMap<>(); - loadMap(file, m); - - adminTag = m.getOrDefault("管理标签", adminTag); - adminPassword = m.getOrDefault("管理密码", adminPassword); - maxAccountsPerIP = parseInt(m.getOrDefault("每IP最大账号数", "3"), 3); - approvalMode = m.getOrDefault("审批模式", approvalMode); - autoApproveDelayMinutes = parseInt(m.getOrDefault("自动审批延迟分钟", "30"), 30); - loginTimeout = parseInt(m.getOrDefault("登录超时秒数", "60"), 60); - afkEnabled = Boolean.parseBoolean(m.getOrDefault("挂机踢出", "false")); - afkTimeout = parseInt(m.getOrDefault("挂机超时秒数", "300"), 300); - smtpSsl = Boolean.parseBoolean(m.getOrDefault("smtp加密", "true")); - httpPort = parseInt(m.getOrDefault("http-port", "8080"), 8080); - broadcastServerUrl = m.getOrDefault("resource-pack-url", ""); - - providerPointsPerScore = parseDouble(m.getOrDefault("服务商积分倍率", "2.0"), 2.0); - baseEconomyReward = parseInt(m.getOrDefault("基础经济奖励", "100"), 100); - providerEconomyPerScore = parseDouble(m.getOrDefault("经济奖励每评分", "50.0"), 50.0); - requesterPointsReward = parseInt(m.getOrDefault("报单人积分奖励", "10"), 10); - - garbageEnabled = Boolean.parseBoolean(m.getOrDefault("垃圾站_启用", "true")); - garbageInterval = parseInt(m.getOrDefault("垃圾站_清理间隔秒", "300"), 300); - garbageMaxRounds = parseInt(m.getOrDefault("垃圾站_保留轮数", "1"), 1); - - checkinRewardFixed = parseDouble(m.getOrDefault("签到固定金额", "0"), 0); - checkinRewardMin = parseDouble(m.getOrDefault("签到最小金额", "0"), 0); - checkinRewardMax = parseDouble(m.getOrDefault("签到最大金额", "0"), 0); - checkinGivePoints = Boolean.parseBoolean(m.getOrDefault("签到给积分", "true")); - checkinPoints = parseInt(m.getOrDefault("签到积分数量", "10"), 10); - backCheckPointMultiplier = parseInt(m.getOrDefault("补签积分倍率", "5"), 5); - checkinBondMin = parseInt(m.getOrDefault("签到债券最小", "1"), 1); - checkinBondMax = parseInt(m.getOrDefault("签到债券最大", "3"), 3); - if (checkinBondMax < checkinBondMin) checkinBondMax = checkinBondMin; - - if (checkinRewardFixed > 0) checkinRewardType = "fixed"; - else if (checkinRewardMin > 0 && checkinRewardMax > checkinRewardMin) checkinRewardType = "range"; - else checkinRewardType = "none"; - rewardChannel = m.getOrDefault("奖励发放方式", "债券"); - rewardChannel = normalizeRewardChannel(rewardChannel); - -// 验证合法值 - if (!"economy".equalsIgnoreCase(rewardChannel) - && !"bonds".equalsIgnoreCase(rewardChannel)) { - rewardChannel = "bonds"; - } - - - - // ★ 自动补充新配置项 ★ - boolean needAppend = false; - List newKeys = new ArrayList<>(); - if (!m.containsKey("签到债券最小")) { needAppend = true; newKeys.add("签到债券最小=" + checkinBondMin); } - if (!m.containsKey("签到债券最大")) { needAppend = true; newKeys.add("签到债券最大=" + checkinBondMax); } - if (!m.containsKey("奖励发放方式")) { - needAppend = true; - newKeys.add("奖励发放方式=债券"); - } - if (needAppend) { - try { - FileWriter fw = new FileWriter(file, true); - BufferedWriter bw = new BufferedWriter(fw); - bw.newLine(); - for (String key : newKeys) { bw.write(key); bw.newLine(); } - bw.flush(); bw.close(); - System.out.println("[Config] 已补充签到债券配置"); - - } catch (IOException ignored) {} - if (!m.containsKey("奖励发放方式")) { - try { - FileWriter fw = new FileWriter(file, true); - BufferedWriter bw = new BufferedWriter(fw); - bw.newLine(); - bw.write("奖励发放方式=bonds"); - bw.newLine(); - bw.flush(); bw.close(); - } catch (IOException ignored) {} - } - - } - } - /** - * 奖励方式标准化:中英文全兼容 - * "债券" "bonds" "Bond" "BONDS" → "bonds" - * "经济" "economy" "Economy" "ECONOMY" → "economy" - * 其他任何值 → "bonds"(默认债券) - */ - private String normalizeRewardChannel(String raw) { - if (raw == null) return "bonds"; - String s = raw.trim().toLowerCase(); - // 中文 - if (s.contains("债券")) return "bonds"; - if (s.contains("经济")) return "economy"; - // 英文 - if (s.contains("bond")) return "bonds"; - if (s.contains("econ")) return "economy"; - if (s.contains("Vault")) return "economy"; - // 兜底 - return "bonds"; - } - - public void saveSettings() { - File file = new File(dataFolder, "插件设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=" + adminTag); - L.add("管理密码=" + adminPassword); - L.add("每IP最大账号数=" + maxAccountsPerIP); - L.add("审批模式=" + approvalMode); - L.add("自动审批延迟分钟=" + autoApproveDelayMinutes); - L.add("登录超时秒数=" + loginTimeout); - L.add("挂机踢出=" + afkEnabled); - L.add("挂机超时秒数=" + afkTimeout); - L.add("resource-pack-url=" + broadcastServerUrl); - L.add("http-port=" + httpPort); - L.add("垃圾站_启用=" + garbageEnabled); - L.add("垃圾站_清理间隔秒=" + garbageInterval); - L.add("垃圾站_保留轮数=" + garbageMaxRounds); - L.add("签到固定金额=" + checkinRewardFixed); - L.add("签到最小金额=" + checkinRewardMin); - L.add("签到最大金额=" + checkinRewardMax); - L.add("签到给积分=" + checkinGivePoints); - L.add("签到积分数量=" + checkinPoints); - L.add("签到债券最小=" + checkinBondMin); - L.add("签到债券最大=" + checkinBondMax); - L.add("补签积分倍率=" + backCheckPointMultiplier); - L.add("奖励发放方式=债券"); - writeLines(file, L); - } - - private void createDefaultSettings(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=admin"); - L.add("管理密码=qweasd"); - L.add("每IP最大账号数=3"); - L.add("审批模式=manual"); - L.add("自动审批延迟分钟=30"); - L.add("登录超时秒数=60"); - L.add("挂机踢出=false"); - L.add("挂机超时秒数=300"); - L.add("resource-pack-url="); - L.add("http-port=8080"); - L.add("垃圾站_启用=true"); - L.add("垃圾站_清理间隔秒=300"); - L.add("垃圾站_保留轮数=1"); - L.add("签到固定金额=0"); - L.add("签到最小金额=50"); - L.add("签到最大金额=500"); - L.add("签到给积分=true"); - L.add("签到积分数量=10"); - L.add("签到债券最小=1"); - L.add("签到债券最大=3"); - L.add("补签积分倍率=5"); - L.add("奖励发放方式=债券"); - writeLines(f, L); - } - public List getAfkWhitelist() { - List list = new ArrayList<>(); - File file = new File(dataFolder, "挂机白名单.txt"); - if (!file.exists()) { - try { - file.createNewFile(); - FileWriter fw = new FileWriter(file); - fw.write("# 每行一个玩家名\n"); - fw.flush(); - fw.close(); - } catch (IOException ignored) {} - return list; - } - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(file), - StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (!line.isEmpty() && !line.startsWith("#")) - list.add(line); - } - } catch (IOException ignored) {} - return list; - } - - // ==================== 其他 ==================== - - public String generateSmtpVerifyCode() { - String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(); - java.util.Random r = new java.util.Random(); - for (int i = 0; i < 6; i++) sb.append(chars.charAt(r.nextInt(chars.length()))); - smtpVerifyCode = sb.toString(); - smtpVerifyExpire = System.currentTimeMillis() + 300000L; - return smtpVerifyCode; - } - - public boolean checkSmtpVerifyCode(String code) { - if (System.currentTimeMillis() > smtpVerifyExpire) { smtpVerifyCode = ""; return false; } - return smtpVerifyCode.equalsIgnoreCase(code); - } - - public void clearSmtpVerifyCode() { smtpVerifyCode = ""; smtpVerifyExpire = 0; } - public boolean canSendSmtpVerify() { return System.currentTimeMillis() - smtpLastSendTime >= 60000L; } - public void recordSmtpSendTime() { smtpLastSendTime = System.currentTimeMillis(); } - - // ==================== 文件工具 ==================== - - private void loadMap(File file, Map map) { - try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); - } - } catch (IOException ignored) {} - } - - private void writeLines(File file, List lines) { - try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { - for (String l : lines) { w.write(l); w.newLine(); } - } catch (IOException ignored) {} - } - - private double parseDouble(String s, double def) { try { return Double.parseDouble(s.trim()); } catch (Exception e) { return def; } } - private int parseInt(String s, int def) { try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/CypayCommand.java b/backup/20260605_152529/src/main/java/Sdf1_login/CypayCommand.java deleted file mode 100644 index 7fd9067..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/CypayCommand.java +++ /dev/null @@ -1,577 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.stream.Collectors; - -public class CypayCommand - implements CommandExecutor, TabCompleter { - - private final Main plugin; - private static final SimpleDateFormat SDF = - new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - public CypayCommand(Main plugin) { - this.plugin = plugin; - } - - private boolean hasAdminPerm(CommandSender s) { - if (s.isOp()) return true; - if (!(s instanceof Player)) return true; - Player p = (Player) s; - String tag = - plugin.getConfig2().adminTag; - return !tag.isEmpty() - && p.getScoreboardTags().contains(tag); - } - - private int parseAmount(String s) { - return Integer.parseInt( - s.trim().replaceAll("[+]", "")); - } - - private List filter( - List list, String prefix) { - String low = prefix.toLowerCase(); - return list.stream() - .filter(x -> x.toLowerCase() - .startsWith(low)) - .collect(Collectors.toList()); - } - - @Override - public boolean onCommand(CommandSender sender, - Command cmd, - String label, - String[] args) { - - // ===== /cypay — 查自己 ===== - if (args.length == 0) { - String name = sender instanceof Player - ? sender.getName() : "Console"; - int bonds = - plugin.getBonds().getBonds(name); - String status = plugin.getBonds() - .getAccountStatus(name); - sender.sendMessage( - "§e§l[债券] §f" + name - + " 余额: §a" + bonds); - sender.sendMessage( - "§e§l[债券] §f状态: " - + ("frozen".equals(status) - ? "§c冻结中" - : "§a正常")); - return true; - } - - // ===== /cypay import ===== - /* if (args.length == 2 - && "import".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String file = args[1]; - if (!file.endsWith(".txt")) - file += ".txt"; - int result = plugin.getCDK() - .importFromFile(file); - if (result == -1) - sender.sendMessage("§c文件不存在: " - + file); - else - sender.sendMessage("§a导入完成: " - + result + " 条"); - return true; - }*/ - - // ===== /cypay freeze ===== - if (args.length == 2 - && "freeze".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - if (plugin.getBonds().isFrozen(target)) { - sender.sendMessage("§c" + target - + " 已处于冻结状态"); - return true; - } - plugin.getBonds().freezeAccount(target); - sender.sendMessage("§a已冻结 " + target - + " 的债券账户"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§c§l[系统] 你的债券账户" - + "已被管理员冻结"); - return true; - } - - // ===== /cypay unfreeze ===== - if (args.length == 2 - && "unfreeze".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - if (!plugin.getBonds() - .isFrozen(target)) { - sender.sendMessage("§c" + target - + " 未被冻结"); - return true; - } - plugin.getBonds().unfreezeAccount(target); - sender.sendMessage("§a已解冻 " + target - + " 的债券账户"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§a§l[系统] 你的债券账户" - + "已被解冻"); - return true; - } - - // ===== /cypay water [days] ===== - if (args.length >= 2 - && "water".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - int days = 7; - if (args.length >= 3) { - try { - days = Integer.parseInt(args[2]); - } catch (NumberFormatException ignored) { - } - } - days = Math.min(days, - BondManager.ADMIN_MAX_WATER_DAYS); - - if (days <= 0) days = 1; - - List> txs = - plugin.getBonds() - .getTransactions( - target, days); - - sender.sendMessage( - "§e§l===== " + target - + " 流水记录 (近" - + days + "天) ====="); - sender.sendMessage("§f当前余额: §a" - + plugin.getBonds() - .getBonds(target) - + " §f状态: " - + (plugin.getBonds() - .isFrozen(target) - ? "§c冻结" : "§a正常")); - - if (txs.isEmpty()) { - sender.sendMessage("§7 (暂无记录)"); - } else { - for (Map tx : txs) { - sender.sendMessage( - formatTxLine(tx)); - } - } - sender.sendMessage( - "§e§l==========================" - + "=========="); - return true; - } - // ===== /cypay printer [player] ===== - if ("printer".equalsIgnoreCase(args[0])) { - if (!hasAdminPerm(sender) - && !(sender instanceof Player) - == false) { - // 控制台也可以用 - if (!(sender instanceof Player)) - ; // 放行 - else if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - } - if (args.length == 1) { - // 打印全服 - sender.sendMessage( - "§e正在导出全服流水..."); - plugin.getBonds(); // 确保实例就绪 - if (plugin.getBondPrinter() != null) { - plugin.getBondPrinter() - .printAll(sender); - } else { - sender.sendMessage("§cPrinter未初始化"); - } - return true; - } - if (args.length == 2) { - // 打印指定玩家 - String target = args[1]; - sender.sendMessage("§e正在导出 " - + target + " 的流水..."); - if (plugin.getBondPrinter() != null) { - plugin.getBondPrinter() - .printPlayer(target, sender); - } else { - sender.sendMessage("§cPrinter未初始化"); - } - return true; - } - } - - // ===== /cypay give/remove ===== - if (args.length == 3) { - String action = args[0].toLowerCase(); - if ("give".equals(action) - || "remove".equals(action)) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - try { - int amount = - parseAmount(args[2]); - if (amount <= 0) { - sender.sendMessage( - "§c金额必须大于0"); - return true; - } - String txType = - "give".equals(action) - ? BondManager - .TX_ADMIN_GIVE - : BondManager - .TX_ADMIN_DEDUCT; - String opName = - sender instanceof Player - ? sender.getName() - : "Console"; - - if ("give".equals(action)) { - plugin.getBonds().addBonds( - target, amount, - txType, "", - opName, "管理员发放"); - sender.sendMessage( - "§a已给 " + target - + " " + amount - + " 债券"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§a管理员给了你 " - + amount - + " 债券"); - } else { - if (plugin.getBonds() - .deductBonds(target, - amount, - txType, "", - opName, - "管理员扣除")) { - sender.sendMessage( - "§a已扣除 " + target - + " " + amount - + " 债券"); - Player tp = plugin - .getServer() - .getPlayerExact( - target); - if (tp != null) - tp.sendMessage( - "§c管理员扣除了" - + "你的 " - + amount - + " 债券"); - } else { - sender.sendMessage( - "§c" + target - + " 债券不足" - + "或账户冻结"); - } - } - } catch (NumberFormatException e) { - sender.sendMessage( - "§c金额格式错误"); - } - return true; - } - } - - // ===== /cypay — 仅玩家转账 ===== - if (args.length == 2) { - try { - int amount = parseAmount(args[1]); - if (amount <= 0) { - sender.sendMessage( - "§c金额必须大于0"); - return true; - } - // ★ 控制台禁止转账 - if (!(sender instanceof Player)) { - sender.sendMessage( - "§c控制台不能转账,请使用 " - + "/cypay give <玩家> <金额>"); - return true; - } - Player p = (Player) sender; - String target = args[0]; - BondManager.TransferResult r = - plugin.getBonds().transfer( - p.getName(), - target, amount); - if (r.success) { - p.sendMessage("§e§l[债券转账] §f向 §a" - + target + " §f转出 §c" - + amount + " §f枚债券"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage("§e§l[债券转账] §f收到 §a" - + p.getName() - + " §f转入 §a" - + amount + " §f枚债券"); - } else { - p.sendMessage("§c" + r.message - + ",当前: " - + plugin.getBonds() - .getBonds(p.getName())); - } - return true; - } catch (NumberFormatException ignored) { - } - } - - // ===== /cypay — 查指定玩家 ===== - if (args.length == 1) { - String target = args[0]; - // ★ 验证玩家在 login.db 中存在 ★ - if (!plugin.getDb().userExists(target)) { - sender.sendMessage("§c玩家 " + target - + " 不存在于服务器"); - return true; - } - - // String target = args[0]; - if (hasAdminPerm(sender)) { - int bonds = plugin.getBonds() - .getBonds(target); - String status = plugin.getBonds() - .getAccountStatus(target); - sender.sendMessage( - "§e§l[债券] §f" + target - + " 余额: §a" - + bonds); - sender.sendMessage( - "§e§l[债券] §f状态: " - + ("frozen".equals( - status) - ? "§c冻结中" - : "§a正常")); - } else { - int bonds = plugin.getBonds() - .getBonds( - sender.getName()); - sender.sendMessage( - "§e§l[债券] §f你的余额: §a" - + bonds); - } - return true; - } - - showHelp(sender); - return true; - } - - // ===== 流水行格式化 ===== - - private String formatTxLine( - Map tx) { - 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 pos = amount > 0; - String sign = pos ? "+" : ""; - String color = pos ? "§a" : "§c"; - - StringBuilder sb = new StringBuilder(); - sb.append("§7[") - .append(SDF.format(new Date(time))) - .append("] ") - .append(formatTxType(type)) - .append(" ") - .append(color).append(sign) - .append(amount).append("§7"); - if (tgt != null && !tgt.isEmpty()) - sb.append(" §e↔ ").append(tgt); - if (reason != null && !reason.isEmpty()) - sb.append(" §7(").append(reason) - .append(")"); - sb.append(" §7余额:§f").append(bal); - return sb.toString(); - } - - private String formatTxType(String type) { - if (type == null) return "§7未知"; - switch (type) { - case BondManager.TX_ADMIN_GIVE: - return "§a管理员发放"; - case BondManager.TX_ADMIN_DEDUCT: - return "§c管理员扣除"; - case BondManager.TX_TRANSFER_OUT: - return "§c转出"; - case BondManager.TX_TRANSFER_IN: - return "§a转入"; - case BondManager.TX_REDEEM: - return "§aCDK兑换"; - case BondManager.TX_SHOP_BUY: - return "§e商城购买"; - case BondManager.TX_DAILY_SIGN: - return "§a签到奖励"; - case BondManager.TX_FREEZE: - return "§c冻结"; - case BondManager.TX_UNFREEZE: - return "§a解冻"; - default: - return "§7" + type; - } - } - - private void showHelp(CommandSender sender) { - sender.sendMessage("§e§l债券命令帮助:"); - sender.sendMessage( - "§e/cypay §7- 查询自己余额"); - sender.sendMessage( - "§e/cypay <玩家> §7- 查余额(管理员)"); - sender.sendMessage( - "§e/cypay <玩家> <金额> §7- 转账"); - if (hasAdminPerm(sender)) { - sender.sendMessage( - "§e/cypay give <玩家> <金额> §7- 给债券"); - sender.sendMessage( - "§e/cypay remove <玩家> <金额> §7- 扣债券"); - sender.sendMessage( - "§e/cypay import <文件> §7- 导入CDK"); - sender.sendMessage( - "§e/cypay freeze <玩家> §7- 冻结"); - sender.sendMessage( - "§e/cypay unfreeze <玩家> §7- 解冻"); - sender.sendMessage( - "§e/cypay water <玩家> [天数] §7- 流水"); - sender.sendMessage( - "§e/cypay printer §7- 导出全服流水xlsx"); - sender.sendMessage( - "§e/cypay printer <玩家> §7- 导出指定玩家xlsx"); - - } - } - - @Override - public List onTabComplete( - CommandSender sender, Command command, - String label, String[] args) { - List list = new ArrayList<>(); - - if (args.length == 1) { - String last = args[0].toLowerCase(); - if (hasAdminPerm(sender)) { - list.add("give"); - list.add("remove"); - list.add("import"); - list.add("freeze"); - list.add("unfreeze"); - list.add("water"); - list.add("printer"); - - File cdkDir = new File( - plugin.getDataFolder(), - "cdk"); - if (cdkDir.exists()) { - File[] files = cdkDir.listFiles( - (d, n) -> n - .endsWith(".txt")); - if (files != null) - for (File f : files) - list.add(f.getName()); - } - } - for (Player o : - Bukkit.getOnlinePlayers()) - list.add(o.getName()); - return filter(list, last); - } - - if (args.length == 2) { - String first = args[0].toLowerCase(); - if ("give".equals(first) - || "remove".equals(first) - || "freeze".equals(first) - || "unfreeze".equals(first) - || "water".equals(first)) { - for (Player o : - Bukkit.getOnlinePlayers()) - list.add(o.getName()); - return filter(list, - args[1].toLowerCase()); - } - list.add("10"); - list.add("50"); - list.add("100"); - return filter(list, - args[1].toLowerCase()); - } - - if (args.length == 3) { - String first = args[0].toLowerCase(); - if ("give".equals(first) - || "remove".equals(first)) { - list.add("10"); - list.add("50"); - list.add("100"); - list.add("999"); - return filter(list, - args[2].toLowerCase()); - } - if ("water".equals(first)) { - list.add("1"); - list.add("3"); - list.add("7"); - list.add("14"); - return filter(list, - args[2].toLowerCase()); - } - } - - return list; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/DatabaseManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/DatabaseManager.java deleted file mode 100644 index cb2b8f7..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/DatabaseManager.java +++ /dev/null @@ -1,1758 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.logging.Logger; - -public class DatabaseManager { - - private Connection db; - private final File dataFolder; - private final Logger logger; - - public DatabaseManager(File dataFolder) { - this.dataFolder = dataFolder; - this.logger = Logger.getLogger("Sdf1_login"); - } - - // ==================== 初始化 ==================== - - public void init() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(dataFolder, - "login.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 用户表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "users (" - + "player_name TEXT PRIMARY KEY," - + "password_hash TEXT NOT NULL," - + "password_salt TEXT NOT NULL," - + "email TEXT DEFAULT ''," - + "register_time INTEGER DEFAULT 0," - + "last_login_time INTEGER DEFAULT 0," - + "total_online_time INTEGER DEFAULT 0," - + "last_online_check INTEGER DEFAULT 0," - + "is_logged_in INTEGER DEFAULT 0," - + "blocks_broken INTEGER DEFAULT 0," - + "blocks_placed INTEGER DEFAULT 0," - + "gift_stage INTEGER DEFAULT 0," - + "gift_claimed TEXT DEFAULT ''," - + "invite_code TEXT DEFAULT ''," - + "invited_by TEXT DEFAULT ''," - + "monthly_invite_count INTEGER DEFAULT 0," - + "invite_month TEXT DEFAULT ''," - + "points INTEGER DEFAULT 0," - + "checkin_streak INTEGER DEFAULT 0," - + "last_checkin_date TEXT DEFAULT ''," - + "total_checkin_days INTEGER DEFAULT 0," - + "temp_password TEXT DEFAULT ''," - + "temp_pw_expire INTEGER DEFAULT 0," - + "temp_pw_used INTEGER DEFAULT 0," - + "ip_address TEXT DEFAULT ''," - + "tasks_completed TEXT DEFAULT ''," - + "premium_uuid TEXT DEFAULT ''," - + "register_ip TEXT DEFAULT ''," - + "register_type TEXT DEFAULT 'manual'," - + "backcheck_dates TEXT DEFAULT ''" - + ")"); - - - // 安全添加 users 缺失列 - safeAdd(st, "tasks_completed", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_password", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_pw_expire", - "INTEGER DEFAULT 0"); - safeAdd(st, "temp_pw_used", - "INTEGER DEFAULT 0"); - safeAdd(st, "ip_address", - "TEXT DEFAULT ''"); - safeAdd(st, "premium_uuid", - "TEXT DEFAULT ''"); - safeAdd(st, "register_ip", - "TEXT DEFAULT ''"); - safeAdd(st, "register_type", - "TEXT DEFAULT 'manual'"); - - // ===== 背包备份表:安全迁移 ===== - // 检查旧表是否存在(用 inventory_data 列判断) - boolean hasOldTable = false; - try { - ResultSet rs = st.executeQuery( - "SELECT inventory_data " - + "FROM inventory_backups " - + "LIMIT 1"); - hasOldTable = true; - rs.close(); - } catch (SQLException e) { - hasOldTable = false; - } - - if (hasOldTable) { - // 旧表存在,迁移数据到新表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups_new (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - // 迁移旧数据 - try { - st.execute("INSERT INTO " - + "inventory_backups_new " - + "(player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience) " - + "SELECT player_name, " - + "inventory_data, " - + "armor_data, " - + "extra_data, " - + "player_level, " - + "player_exp " - + "FROM inventory_backups"); - } catch (SQLException ignored) { - } - st.execute("DROP TABLE " - + "inventory_backups"); - st.execute("ALTER TABLE " - + "inventory_backups_new " - + "RENAME TO " - + "inventory_backups"); - } else { - // 无旧表或已迁移,直接创建 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - } - - // 安全报警表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "security_alerts (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "alert_ip TEXT NOT NULL," - + "alert_time INTEGER NOT NULL," - + "alert_date TEXT NOT NULL," - + "notified INTEGER DEFAULT 0," - + "notified_time INTEGER DEFAULT 0)"); - // PVP区域表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "name TEXT NOT NULL UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER DEFAULT 0" - + ")"); - -// PVP击杀总榜表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT NOT NULL," - + "region_name TEXT NOT NULL," - + "kills INTEGER DEFAULT 0," - + "deaths INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, region_name)" - + ")"); - - -// 菜单图标表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "menu_icons (" - + "player_name TEXT PRIMARY KEY," - + "icon_b64 TEXT DEFAULT ''," - + "item_name TEXT DEFAULT ''," - + "save_time INTEGER DEFAULT 0" - + ")"); - - // 工单表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "tickets (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "type TEXT NOT NULL," - + "status TEXT DEFAULT 'pending'," - + "priority TEXT DEFAULT 'normal'," - + "requester TEXT NOT NULL," - + "assigned_to TEXT DEFAULT ''," - + "title TEXT DEFAULT ''," - + "description TEXT DEFAULT ''," - + "reward_amount INTEGER DEFAULT 0," - + "score INTEGER DEFAULT 0," - + "target_player TEXT DEFAULT ''," - + "create_time INTEGER NOT NULL," - + "update_time INTEGER DEFAULT 0," - + "complete_time INTEGER DEFAULT 0," - + "admin_confirmed INTEGER DEFAULT 0," - + "reject_reason TEXT DEFAULT ''" - + ")"); - - // 工单回复表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "ticket_replies (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "ticket_id INTEGER NOT NULL," - + "sender TEXT NOT NULL," - + "message TEXT NOT NULL," - + "create_time INTEGER NOT NULL" - + ")"); - - // 服务商表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "service_providers (" - + "player_name TEXT PRIMARY KEY," - + "role TEXT DEFAULT 'waiter'," - + "active INTEGER DEFAULT 1," - + "join_time INTEGER DEFAULT 0" - + ")"); - // 垃圾回收站物品表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "items (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "item_data TEXT DEFAULT ''," - + "amount INTEGER DEFAULT 1," - + "create_time INTEGER DEFAULT 0" - + ")"); - safeAdd(st, "menu_snowball", - "INTEGER DEFAULT 1"); - st.close(); - logger.info("[Sdf1_login] 数据库初始化完成"); - } catch (Exception e) { - throw new RuntimeException( - "[Sdf1_login] DB初始化失败: " - + e.getMessage(), e); - } - } - - - private void safeAdd(Statement st, - String col, String def) { - try { - st.executeUpdate("ALTER TABLE users " - + "ADD COLUMN " + col + " " + def); - } catch (SQLException ignored) { - } - } - - public void close() { - try { - if (db != null && !db.isClosed()) - db.close(); - } catch (Exception ignored) { - } - } -// ==================== 菜单图标 ==================== - - public void saveMenuIcon(String name, - String iconB64, String itemName) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO menu_icons " - + "(player_name, icon_b64, " - + "item_name, save_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, iconB64); - ps.setString(3, itemName); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT icon_b64 FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - String b64 = null; - if (rs.next()) { - b64 = rs.getString("icon_b64"); - } - rs.close(); - ps.close(); - return b64; - } catch (SQLException e) { - return null; - } - } - - public void deleteMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM menu_icons " - + "WHERE player_name=? " - + "AND icon_b64 IS NOT NULL " - + "AND icon_b64 != '' " - + "LIMIT 1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - // ==================== 用户操作 ==================== - - public boolean userExists(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public void createUser(String name, String hash, - String salt) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO users " - + "(player_name, " - + "password_hash, " - + "password_salt, " - + "register_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, hash); - ps.setString(3, salt); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ========== 临时密码管理 ========== - - public void clearTempPassword(String name) { - setField(name, "temp_password", null); - setField(name, "temp_pw_expire", 0); - setField(name, "temp_pw_used", 1); - } - - public void clearAllTempPasswords() { - try { - Statement st = db.createStatement(); - st.executeUpdate( - "UPDATE users SET " - + "temp_password=NULL," - + "temp_pw_expire=0," - + "temp_pw_used=1 " - + "WHERE temp_password " - + "IS NOT NULL"); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getPlayerIPByName( - String name) { - Object ip = getField(name, "ip"); - return ip != null - ? String.valueOf(ip) : null; - } - - public boolean checkPasswordOrTemp( - String name, String hash) { - // 先检查主密码 - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String mainHash = - rs.getString("password_hash"); - rs.close(); - ps.close(); - if (hash.equals(mainHash)) - return true; - } else { - rs.close(); - ps.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - // 再检查临时密码 - try { - PreparedStatement ps2 = db.prepareStatement( - "SELECT temp_password, " - + "temp_pw_expire, " - + "temp_pw_used " - + "FROM users " - + "WHERE player_name=?"); - ps2.setString(1, name); - ResultSet rs2 = ps2.executeQuery(); - if (rs2.next()) { - String tempHash = - rs2.getString( - "temp_password"); - long expire = - rs2.getLong( - "temp_pw_expire"); - int used = - rs2.getInt( - "temp_pw_used"); - rs2.close(); - ps2.close(); - if (tempHash != null - && !tempHash.isEmpty() - && hash.equals(tempHash) - && System.currentTimeMillis() - < expire - && used != 1) { - return true; - } - } else { - rs2.close(); - ps2.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - public Map getUser(String name) { - Map r = - new LinkedHashMap<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - r.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return r; - } - - public void setField(String name, String field, - Object val) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE users SET " + field - + "=? " - + "WHERE player_name=?"); - if (val instanceof Integer) - ps.setInt(1, (Integer) val); - else if (val instanceof Long) - ps.setLong(1, (Long) val); - else - ps.setString(1, - String.valueOf(val)); - ps.setString(2, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Object getField(String name, - String field) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT " + field - + " FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - Object val = rs.next() - ? rs.getObject(1) : null; - rs.close(); - ps.close(); - return val; - } catch (SQLException e) { - return null; - } - } - - public boolean checkPassword(String name, - String hash) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String stored = - rs.getString("password_hash"); - rs.close(); - ps.close(); - return hash.equals(stored); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - - public boolean isUsingTempPassword(String name) { - String h = (String) getField(name, - "temp_password"); - return h != null && !h.isEmpty(); - } - - public void setLoggedIn(String name, - boolean val) { - setField(name, "is_logged_in", - val ? 1 : 0); - } - - public void addPoints(String name, int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - setField(name, "points", curVal + amount); - } - - public boolean deductPoints(String name, - int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - if (curVal < amount) return false; - setField(name, "points", - curVal - amount); - return true; - } - public List> - getAllUsers() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM users"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - public List> - getTicketsByProviderAll( - String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deleteUser(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void recordIP(String name, String ip) { - if (ip != null && !ip.isEmpty()) { - setField(name, "ip_address", ip); - } - } - - // ==================== IP查询 ==================== - - /** - * 统计指定IP注册的账号数量 - */ - public int countIPAccounts(String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address=?"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count; - } catch (SQLException e) { - return 0; - } - } - - /** - * 获取指定IP下所有账号详情 - */ - public List> - getAccountDetailsByIP(String ip) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, " - + "register_time " - + "FROM users " - + "WHERE ip_address=? " - + "ORDER BY register_time"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("register_time", - rs.getLong("register_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 背包备份 ==================== - - public void saveInventoryBackup(String name, - String contents, String armor, - String extra, int level, - double experience) { - try { - PreparedStatement del = - db.prepareStatement( - "DELETE FROM " - + "inventory_backups " - + "WHERE player_name=?"); - del.setString(1, name); - del.executeUpdate(); - del.close(); - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO " - + "inventory_backups" - + " (player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience, " - + "save_time) " - + "VALUES " - + "(?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, contents); - ps.setString(3, armor); - ps.setString(4, extra); - ps.setInt(5, level); - ps.setDouble(6, experience); - ps.setLong(7, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public String[] loadInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time " - + "DESC LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void deleteInventoryBackup( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM inventory_backups " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - boolean has = rs.next(); - rs.close(); - ps.close(); - return has; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } -// ==================== PVP区域 ==================== - - public void savePVPRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions " - + "(name, world, " - + "x1, y1, z1, " - + "x2, y2, z2, " - + "create_time) " - + "VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map - getPVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getAllPVPRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - db.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deletePVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - -// ==================== PVP击杀 ==================== - - public int getPVPKills( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int kills = 0; - if (rs.next()) - kills = rs.getInt("kills"); - rs.close(); - ps.close(); - return kills; - } catch (SQLException e) { - return 0; - } - } - - public int getPVPDeaths( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int deaths = 0; - if (rs.next()) - deaths = rs.getInt("deaths"); - rs.close(); - ps.close(); - return deaths; - } catch (SQLException e) { - return 0; - } - } - - public void addPVPKill( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,1,0) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "kills=kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void addPVPDeath( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,0,1) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "deaths=deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getPVPKillTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT player_name, " - + "kills, deaths " - + "FROM pvp_kills " - + "WHERE " - + "region_name=? " - + "AND kills>0 " - + "ORDER BY kills " - + "DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - // ==================== 安全报警 ==================== - - public void recordSecurityAlert( - String playerName, String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO security_alerts " - + "(player_name, alert_ip, " - + "alert_time, alert_date) " - + "VALUES (?,?,?,?)"); - ps.setString(1, playerName); - ps.setString(2, ip); - long now = System.currentTimeMillis(); - ps.setLong(3, now); - ps.setString(4, - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date( - now))); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - - } - - public List> - getUnnotifiedAlerts( - String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM security_alerts " - + "WHERE player_name=? " - + "AND notified=0 " - + "ORDER BY alert_time ASC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void markAlertNotified(int alertId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE security_alerts " - + "SET notified=1, " - + "notified_time=? " - + "WHERE id=?"); - ps.setLong(1, - System.currentTimeMillis()); - ps.setInt(2, alertId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - /** - * 获取玩家最近N条背包备份 - */ - public List> - getInventoryBackups(String name, - int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id, save_time " - + "FROM inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time DESC " - + "LIMIT ?"); - ps.setString(1, name); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", - rs.getInt("id")); - row.put("save_time", - rs.getLong("save_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - /** - * 获取指定ID的备份数据 - */ - public String[] getInventoryBackupById( - int backupId) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM inventory_backups " - + "WHERE id=?"); - ps.setInt(1, backupId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - - // ==================== 工单操作 ==================== - - public int createTicket(String type, - String status, String priority, - String requester, String title, - String description, - int rewardAmount, - String targetPlayer) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO tickets " - + "(type, status, priority, " - + "requester, title, " - + "description, " - + "reward_amount, " - + "target_player, " - + "create_time) " - + "VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, type); - ps.setString(2, status); - ps.setString(3, priority); - ps.setString(4, requester); - ps.setString(5, title); - ps.setString(6, description); - ps.setInt(7, rewardAmount); - ps.setString(8, targetPlayer); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ResultSet rs = - ps.getGeneratedKeys(); - int id = -1; - if (rs.next()) id = rs.getInt(1); - rs.close(); - ps.close(); - return id; - } catch (SQLException e) { - e.printStackTrace(); - return -1; - } - } - - public void updateTicketStatus(int ticketId, - String status) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET status=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, status); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void assignTicket(int ticketId, - String provider) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "assigned_to=?, " - + "status='processing', " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, provider); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void completeTicket(int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='completed', " - + "complete_time=?, " - + "update_time=? " - + "WHERE id=?"); - long now = System.currentTimeMillis(); - ps.setLong(1, now); - ps.setLong(2, now); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void confirmTicket(int ticketId, - int score) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "admin_confirmed=1, " - + "score=?, " - + "status='resolved', " - + "update_time=? " - + "WHERE id=?"); - ps.setInt(1, score); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public void rejectTicketComplete(int ticketId, - String reason) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='rejected', " - + "reject_reason=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, reason); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 更新工单任意字段 - */ - public void updateTicketField(int ticketId, - String field, Object value) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " + field - + "=? WHERE id=?"); - if (value instanceof Integer) - ps.setInt(1, (Integer) value); - else if (value instanceof Long) - ps.setLong(1, (Long) value); - else - ps.setString(1, - String.valueOf(value)); - ps.setInt(2, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map getTicket( - int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE id=?"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getTicketsByStatus(String status) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status=? " - + "ORDER BY create_time DESC"); - ps.setString(1, status); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void addTicketReply(int ticketId, - String sender, String message) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO ticket_replies " - + "(ticket_id, sender, " - + "message, create_time) " - + "VALUES (?,?,?,?)"); - ps.setInt(1, ticketId); - ps.setString(2, sender); - ps.setString(3, message); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - // 更新工单状态为已回复 - updateTicketStatus(ticketId, - "replied"); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getTicketReplies(int ticketId) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM ticket_replies " - + "WHERE ticket_id=? " - + "ORDER BY create_time ASC"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - /** - * 获取已回复但超时未互动的工单 - */ - public List> - getRepliedStaleTickets( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='replied' " - + "AND update_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByPlayer(String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE requester=? " - + "ORDER BY create_time DESC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByProvider(String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "AND (status='processing' " - + "OR status='replied') " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getUnconfirmedCompleted( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='completed' " - + "AND admin_confirmed=0 " - + "AND complete_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 服务商操作 ==================== - - public void addServiceProvider(String name, - String role) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO " - + "service_providers " - + "(player_name, role, " - + "active, join_time) " - + "VALUES (?,?,1,?)"); - ps.setString(1, name); - ps.setString(2, role); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void removeServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM service_providers " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean isServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM service_providers " - + "WHERE player_name=? " - + "AND active=1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public List> - getAllServiceProviders() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM service_providers " - + "WHERE active=1"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Connection getDb() { - return db; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/EmailManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/EmailManager.java deleted file mode 100644 index b6906e5..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/EmailManager.java +++ /dev/null @@ -1,284 +0,0 @@ -package Sdf1_login; - -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.Socket; -import java.util.Base64; -import java.util.logging.Logger; - -public class EmailManager { - - private final ConfigManager config; - private final Logger logger; - - public EmailManager(ConfigManager config) { - this.config = config; - this.logger = Logger.getLogger("Sdf1_login"); - } - - public boolean isConfigured() { - String host = config.getSmtp("smtp地址"); - String user = config.getSmtp("smtp账号"); - String pass = config.getSmtp("smtp密码"); - return host != null && !host.isEmpty() - && user != null && !user.isEmpty() - && pass != null && !pass.isEmpty(); - } - - public boolean sendVerifyCode(String to, - String code) { - if (!isConfigured()) { - logger.warning( - "[Sdf1_login] SMTP未配置," - + "无法发送验证码"); - return false; - } - logger.info("[Sdf1_login] 发送验证码到 " + to); - return sendEmailSync(to, - "[Sdf1_login] 验证码", - "您的验证码: " + code - + "\n5分钟内有效。\n" - + "如非本人操作请忽略。"); - } - - public boolean sendTempPassword(String to, - String player, String tempPwd) { - if (!isConfigured()) { - logger.warning( - "[Sdf1_login] SMTP未配置"); - return false; - } - return sendEmailSync(to, - "[Sdf1_login] 临时密码", - "玩家 " + player - + " 的临时密码: " + tempPwd - + "\n请尽快登录并修改密码!"); - } - - public boolean sendBody(String to, String subject, String body) { - if (!isConfigured()) { - logger.warning("[Sdf1_login] SMTP未配置"); - return false; - } - return sendEmailSync(to, subject, body); - } - - - private boolean sendEmailSync(String to, - String subject, String body) { - final boolean[] result = {false}; - Thread t = new Thread(() -> { - try { - result[0] = sendEmail( - to, subject, body); - } catch (Exception e) { - logger.severe( - "[Sdf1_login] 邮件线程异常: " - + e.getMessage()); - } - }, "Sdf1_login-SMTP"); - t.setDaemon(true); - t.start(); - try { - t.join(15000); - } catch (InterruptedException ignored) { - } - if (t.isAlive()) { - t.interrupt(); - logger.severe( - "[Sdf1_login] SMTP超时(15秒)"); - return false; - } - return result[0]; - } - - private boolean sendEmail(String to, - String subject, String body) { - String host = config.getSmtp("smtp地址"); - String portStr = config.getSmtp("smtp端口"); - String user = config.getSmtp("smtp账号"); - String pass = config.getSmtp("smtp密码"); - String fromName = config.getSmtp( - "发件人名称"); - - int port; - try { - port = Integer.parseInt(portStr); - } catch (Exception e) { - port = 465; - } - - try { - String encSubject = "=?UTF-8?B?" - + Base64.getEncoder() - .encodeToString( - subject.getBytes("UTF-8")) - + "?="; - String encBody = Base64.getEncoder() - .encodeToString( - body.getBytes("UTF-8")); - - if (port == 465) { - SSLSocketFactory factory = - (SSLSocketFactory) - SSLSocketFactory - .getDefault(); - SSLSocket socket = (SSLSocket) - factory.createSocket( - host, port); - socket.setSoTimeout(10000); - doSmtp(socket, host, user, pass, - to, encSubject, encBody, - fromName); - socket.close(); - } else { - Socket plainSocket = - new Socket(host, port); - plainSocket.setSoTimeout(10000); - BufferedReader preReader = - new BufferedReader( - new InputStreamReader( - plainSocket - .getInputStream(), - "UTF-8")); - PrintWriter preWriter = - new PrintWriter( - plainSocket - .getOutputStream(), - true); - checkResponse(preReader, "220"); - sendLine(preWriter, - "EHLO " + host); - checkResponse(preReader, "250"); - sendLine(preWriter, "STARTTLS"); - checkResponse(preReader, "220"); - SSLSocketFactory factory = - (SSLSocketFactory) - SSLSocketFactory - .getDefault(); - SSLSocket sslSocket = (SSLSocket) - factory.createSocket( - plainSocket, host, - port, true); - sslSocket.startHandshake(); - doSmtp(sslSocket, host, user, pass, - to, encSubject, encBody, - fromName); - sslSocket.close(); - } - logger.info( - "[Sdf1_login] 邮件发送成功 -> " - + to); - return true; - } catch (Exception e) { - logger.severe( - "[Sdf1_login] 邮件异常: " - + e.getClass() - .getSimpleName() - + ": " + e.getMessage()); - if (e.getCause() != null) { - logger.severe( - "[Sdf1_login] 根因: " - + e.getCause() - .getMessage()); - } - return false; - } - } - - private void doSmtp(SSLSocket socket, - String host, String user, String pass, - String to, String subject, String body, - String fromName) throws Exception { - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - socket.getInputStream(), - "UTF-8")); - OutputStream rawOut = - socket.getOutputStream(); - PrintWriter writer = - new PrintWriter( - new java.io.BufferedOutputStream( - rawOut), true); - - checkResponse(reader, "220"); - sendLine(writer, "EHLO " + host); - checkResponse(reader, "250"); - sendLine(writer, "AUTH LOGIN"); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder() - .encodeToString( - user.getBytes("UTF-8"))); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder() - .encodeToString( - pass.getBytes("UTF-8"))); - checkResponse(reader, "235"); - sendLine(writer, - "MAIL FROM:<" + user + ">"); - checkResponse(reader, "250"); - sendLine(writer, - "RCPT TO:<" + to + ">"); - checkResponse(reader, "250"); - sendLine(writer, "DATA"); - checkResponse(reader, "354"); - sendLine(writer, - "From: " + fromName - + " <" + user + ">"); - sendLine(writer, - "To: <" + to + ">"); - sendLine(writer, - "Reply-To: <" + user + ">"); - sendLine(writer, - "Subject: " + subject); - sendLine(writer, "MIME-Version: 1.0"); - sendLine(writer, - "Content-Type: text/plain; " - + "charset=UTF-8"); - sendLine(writer, - "Content-Transfer-Encoding: " - + "base64"); - sendLine(writer, ""); - sendLine(writer, body); - sendLine(writer, "."); - checkResponse(reader, "250"); - sendLine(writer, "QUIT"); - } - - private void sendLine(PrintWriter writer, - String line) { - writer.print(line + "\r\n"); - writer.flush(); - } - - private void checkResponse( - BufferedReader reader, - String expected) throws Exception { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - if (line.length() >= 4 - && Character.isDigit( - line.charAt(0)) - && line.charAt(3) == ' ') - break; - } - String resp = sb.toString().trim(); - if (!resp.startsWith(expected)) { - if (resp.startsWith("535")) - logger.severe( - "[Sdf1_login] SMTP 535 " - + "认证失败"); - throw new Exception( - "SMTP响应异常: 期望 " - + expected - + ",实际: " + resp); - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/GUIManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/GUIManager.java deleted file mode 100644 index cbc7102..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/GUIManager.java +++ /dev/null @@ -1,2681 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.*; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - - - - -import java.util.*; - -public class GUIManager implements Listener { - - private final Main plugin; - - public static final String T_MAIN = - "§6§l服务器菜单"; - public static final String T_MORE = - "§6§l更多功能"; - public static final String T_EDITOR = - "§6§l编辑菜单项"; - public static final String T_BIOME = - "§b§l目标群系传送"; - public static final String T_MY_INFO = - "§6§l我的信息"; - public static final String T_GIFT_STAGES = - "§6§l礼包阶段"; - public static final String T_ADMIN = - "§6§l管理员面板"; - public static final String T_INVITE = - "§6§l邀请数据"; - public static final String T_USER_MGMT = - "§6§l用户管理"; - public static final String T_TASK_CENTER = - "§6§l任务中心"; - private final Map userMgmtPages = new HashMap<>(); - private static final int UMGMT_PAGE_SIZE = 45; - - - - public GUIManager(Main plugin) { - this.plugin = plugin; - } - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_MAIN); -// ===== 自定义菜单项 ===== - if (plugin.getMenu() != null) { - List customs = - plugin.getMenu().getItems(); - - // 已占用的槽位 - Set occupied = new HashSet<>(); - occupied.add(10); - occupied.add(12); - occupied.add(14); - occupied.add(16); - occupied.add(19); - occupied.add(21); - occupied.add(23); - occupied.add(25); - occupied.add(29); - occupied.add(31); - occupied.add(32); - occupied.add(34); - - // 自定义区域:第1排(0-8) + 第5排(36-44) + 第6排(45-53) - int[] customArea = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, - 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 52, 53 - }; - - int autoIdx = 0; - for (int ci = 0; - ci < customs.size() && ci < 27; ci++) { - MenuManager.MenuItem mi = - customs.get(ci); - - // 权限过滤 - if ("OP".equals(mi.permType) - && !p.isOp() - && !isAdmin(p)) continue; - - int targetSlot = -1; - - // 玩家指定了格子? - if (mi.slot != null) { - targetSlot = findBestSlot( - mi.slot - 1, occupied); - } - - // 未指定或冲突 → 自动排队 - if (targetSlot < 0) { - while (autoIdx < customArea.length) { - int s = customArea[autoIdx++]; - if (!occupied.contains(s)) { - targetSlot = s; - break; - } - } - } - - if (targetSlot < 0 - || targetSlot >= 54) continue; - occupied.add(targetSlot); - - // 放置物品 - ItemStack icon = - mi.icon.clone(); - ItemMeta im = - icon.getItemMeta(); - im.setDisplayName( - "§e§l" + mi.title); - im.setLore(java.util.Arrays.asList( - "§7指令: §f" - + mi.command, - "§7权限: §f" - + mi.permType, - "", - "§a左键执行")); - icon.setItemMeta(im); - g.setItem(targetSlot, icon); - } - } - -// 背景填充 - fillBg(g); - p.openInventory(g); - - // ===== Row1(9-17): 核心功能 ===== - g.setItem(10, mkItem(Material.PLAYER_HEAD, - "§a§l我的信息")); - g.setItem(12, mkItem(Material.PAPER, - "§b§l邀请数据")); - g.setItem(14, mkItem(Material.EMERALD, - "§d§l积分商城")); - g.setItem(16, mkItem(Material.COMPASS, - "§b§l群系传送", - "§7传送到指定群系")); - - // ===== Row2(18-26): 日常功能 ===== - g.setItem(19, mkItem(Material.BOOK, - "§6工单系统")); - g.setItem(21, mkItem(Material.EMERALD_BLOCK, - "§a§l每日签到", - "§7签到获取积分和惊喜奖励")); - g.setItem(23, mkItem(Material.ENDER_CHEST, - "§6§l任务中心")); - if (p.isOp() || isAdmin(p)) { - g.setItem(25, mkItem(Material.HOPPER, - "§6§l垃圾回收站", - "§7查看和取回清理的物品")); - - - } - // ===== Row3(27-35): 管理功能 ===== - // ★ 商店 - 所有人都能看 ★ - g.setItem(27, mkItem(Material.CHEST, - "§6§l商店", - "§7浏览、购买、出售物品", - "§7货币: 债券")); - - if (isAdmin(p)) { - g.setItem(29, mkItem( - Material.REDSTONE_BLOCK, - "§c§l管理员面板")); - } - if (p.isOp()) { - g.setItem(31, mkItem(Material.ECHO_SHARD, - "§c§l管理菜单", - "§7添加、编辑、删除菜单项")); - } - // 在 openMain 方法中,Row3 区域加: - - // ★ 余额查询 - 所有人都能看 ★ - double myBal = 0; - try { - var reg = plugin.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault.economy - .Economy.class); - if (reg != null) - myBal = reg.getProvider() - .getBalance(p); - } catch (Exception ignored) {} - int myBonds = plugin.getBonds().getBonds(p.getName()); - g.setItem(32, mkItem(Material.EMERALD, - "§b§l我的钱包", - "§7查看债券余额与流水记录")); - -// ★ 余额操作 - 仅OP和tag玩家 ★ - if (isAdmin(p)) { - g.setItem(34, mkItem(Material.GOLD_INGOT, - "§6§l余额操作", - "§7给钱、扣钱")); - } - - - - // ===== Row0(0-8): 自定义菜单项 ===== - if (plugin.getMenu() != null) { - List customs = - plugin.getMenu().getItems(); - for (int i = 0; - i < customs.size() && i < 9; - i++) { - MenuManager.MenuItem mi = - customs.get(i); - if ("OP".equals(mi.permType) - && !p.isOp() - && !isAdmin(p)) continue; - ItemStack icon = - mi.icon.clone(); - ItemMeta im = - icon.getItemMeta(); - im.setDisplayName( - "§e§l" + mi.title); - im.setLore(Arrays.asList( - "§7指令: §f" - + mi.command, - "§7权限: §f" - + mi.permType, - "", - "§a左键执行")); - icon.setItemMeta(im); - g.setItem(i, icon); - } - } - - // 背景最后填充(不覆盖物品) - fillBg(g); - p.openInventory(g); - } - private int findBestSlot( - int requested, - Set occupied) { - // 目标位空闲直接用 - if (requested >= 0 && requested < 54 - && !occupied.contains(requested)) { - return requested; - } - // BFS上下左右找最近空位 - int row = requested / 9; - int col = requested % 9; - boolean[][] visited = - new boolean[6][9]; - java.util.Queue queue = - new java.util.LinkedList<>(); - queue.add(new int[]{row, col}); - visited[row][col] = true; - int[][] dirs = { - {-1, 0}, {1, 0}, - {0, -1}, {0, 1}}; - while (!queue.isEmpty()) { - int[] cur = queue.poll(); - for (int[] d : dirs) { - int nr = cur[0] + d[0]; - int nc = cur[1] + d[1]; - if (nr >= 0 && nr < 6 - && nc >= 0 && nc < 9 - && !visited[nr][nc]) { - visited[nr][nc] = true; - int s = nr * 9 + nc; - if (!occupied.contains(s)) { - return s; - } - queue.add(new int[]{nr, nc}); - } - } - } - return -1; - } - - public void openMenuManager(Player p) { - List list = - plugin.getMenu().getItems(); - Inventory g = Bukkit.createInventory( - null, 27, - "§c§l菜单管理"); - fillBg(g); - - for (int i = 0; - i < list.size() && i < 9; i++) { - MenuManager.MenuItem mi = list.get(i); - g.setItem(i, mkItem( - mi.icon.getType(), - "§e" + mi.title, - "§7指令: " + mi.command, - "§7权限: " + mi.permType, - "", - "§a左键编辑", - "§7右键切换权限")); - } - - g.setItem(22, mkItem(Material.LIME_WOOL, - "§a§l添加菜单项")); - - g.setItem(26, mkItem(Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - - public void openBalanceOps(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§d§l余额操作"); - fillBg(g); - - net.milkbowl.vault.economy.Economy eco = null; - try { - var reg = plugin.getServer().getServicesManager() - .getRegistration(net.milkbowl.vault.economy.Economy.class); - if (reg != null) eco = reg.getProvider(); - } catch (Exception ignored) {} - - int slot = 0; - for (Player op : Bukkit.getOnlinePlayers()) { - if (slot >= 45) break; - double bal = 0; - if (eco != null) bal = eco.getBalance(op); - boolean isSelf = op.getName().equals(p.getName()); - int bondBal = plugin.getBonds().getBonds(op.getName()); - g.setItem(slot, mkItem( - isSelf ? Material.EMERALD_BLOCK : Material.PLAYER_HEAD, - "§e" + op.getName(), - "§7金币: §a$" + String.format("%.2f", bal), - "§7债券: §e" + bondBal + " §6枚", - "", - "§a左键经济(±) §6右键债券(±)")); - slot++; - } - - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - public void openShop(Player p) { - p.closeInventory(); - plugin.getShopManager().openShopMain(p); - } - - public void openTicket(Player p) { - p.closeInventory(); - p.performCommand("sdf1_login ticket"); - } - @EventHandler - public void onInvClose( - - InventoryCloseEvent e) { - if (!(e.getPlayer() instanceof Player)) - return; - if (!(e.getPlayer() instanceof Player)) return; - Player p = (Player) e.getPlayer(); - Player p3 = (Player) e.getPlayer(); - String t = e.getView().getTitle(); - if (!(e.getPlayer() instanceof Player)) return; - Player p2 = (Player) e.getPlayer(); - - if (!t.equals(T_EDITOR)) return; - - // ★ 编辑器关闭时清理空项 ★ - List list = - plugin.getMenu().getItems(); - if (!list.isEmpty()) { - MenuManager.MenuItem last = - list.get(list.size() - 1); - if ((last.command == null - || last.command.trim().isEmpty() - || last.command.trim() - .equals("/")) - && "新菜单项" - .equals(last.title)) { - list.remove(list.size() - 1); - } - } - } - - public void openSubMenu( - Player p, String fileName) { - String menuTitle = - "§6§l" + fileName - .replace(".txt", ""); - Inventory g = Bukkit.createInventory( - null, 54, menuTitle); - fillBg(g); - - java.io.File f = new java.io.File( - plugin.getDataFolder(), fileName); - if (!f.exists()) { - p.sendMessage("§c文件不存在: " - + fileName); - return; - } - try { - List lines = - java.nio.file.Files - .readAllLines( - f.toPath(), - java.nio.charset - .StandardCharsets - .UTF_8); - List items = - new ArrayList<>(); - StringBuilder buf = - new StringBuilder(); - boolean inItem = false; - - for (String raw : lines) { - String l = raw.trim(); - if (l.startsWith("#") - || l.startsWith("//")) - continue; - if (l.contains("")) { - inHtml = false; - l = l.substring( - l.indexOf("-->") + 3) - .trim(); - } else { - plugin.getLogger().info( - "[Menu] L" + lineNum - + ": [HTML注释中]"); - continue; - } - } - - // ===== 第3优先:整行注释(最高裁决) ===== - // 以 # 或 // 开头的行,直接跳过 - // 不管后面包含什么 /* "); - pw.println(); - pw.println("{"); - pw.println(" 标题:主城传送"); - pw.println(" 指令:/spawn"); - pw.println(" 权限类型:玩家"); - pw.println(" 图标:COMPASS"); - pw.println("}"); - pw.println(); - pw.println("{"); - pw.println(" 标题:随机传送"); - pw.println(" 指令:/rtp"); - pw.println(" 权限类型:玩家"); - pw.println(" 图标:ENDER_PEARL"); - pw.println("}"); - } catch (Exception ignored) { - } - } - - // ===== 保存 ===== - public void saveMenu() { - File f = new File( - plugin.getDataFolder(), - "菜单.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# 服务器菜单"); - pw.println(); - for (MenuItem mi : items) { - pw.println("{"); - pw.println(" 标题:" - + mi.title); - pw.println(" 指令:" - + mi.command); - pw.println(" 权限类型:" - + mi.permType); - // ★ 保存实际材质名 ★ - pw.println(" 图标:" - + mi.icon.getType().name()); - pw.println("}"); - pw.println(); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Menu] 保存失败: " - + e.getMessage()); - } - } - - // ===== 聊天编辑 ===== - public boolean isEditing(String name) { - return chatWait.contains(name); - } - - public void editTitle( - Player p, int idx) { - chatIdx.put(p.getName(), idx); - chatWait.add(p.getName()); - chatField.put(p.getName(), "title"); - p.closeInventory(); - p.sendMessage("§e请输入菜单标题:"); - } - - public void editCommand( - Player p, int idx) { - chatIdx.put(p.getName(), idx); - chatWait.add(p.getName()); - chatField.put(p.getName(), "command"); - p.closeInventory(); - p.sendMessage("§e请输入指令" - + "(自动补全/):"); - } - - public void onChat(Player p, String input) { - String name = p.getName(); - if (!chatWait.remove(name)) return; - - String field = chatField.remove(name); - Integer idx = chatIdx.get(name); - if (idx == null || idx >= items.size()) return; - - MenuItem mi = items.get(idx); - - if ("title".equals(field)) { - mi.title = input; - p.sendMessage("§a标题: " + input); - } else if ("command".equals(field)) { - if (!input.startsWith("/")) - input = "/" + input; - mi.command = input; - p.sendMessage("§a指令: " + input); - } - - Bukkit.getScheduler().runTask(plugin, () -> - plugin.gui.openEditor(p, idx)); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/OrderManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/OrderManager.java deleted file mode 100644 index 9dcb625..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/OrderManager.java +++ /dev/null @@ -1,1345 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.logging.Level; - -public class OrderManager implements Listener { - private final Main plugin; - private final List orders = - Collections.synchronizedList(new ArrayList<>()); - private final Map userViewPage = new HashMap<>(); - private final Map adminViewPage = new HashMap<>(); - private final Map adminViewTarget = new HashMap<>(); - private final Set partialRefundListening = new HashSet<>(); - private final Map partialRefundTarget = new HashMap<>(); - private final Map receiptBuilderSection = new HashMap<>(); - private String tplHeader = ""; - private String tplItemLine = "{品名} x{数量} {原价}→{执行价} 小计{小计}枚"; - private String tplDiscountLine = "优惠: -{优惠额}枚 ({优惠类型})"; - private String tplPackLine = "打包: +{打包费}枚 ({打包颜色})"; - private String tplSummary = "原价{原价合计} → 实付{实付}"; - private String tplFooter = "感谢惠顾,欢迎再来!"; - private static final long RETENTION_30D = 30L * 24 * 60 * 60 * 1000; - private static final long SELF_REFUND_WINDOW = 5 * 60 * 1000; - private static final int PAGE_SIZE = 21; - private static final int[] SLOTS = { - 10,11,12,13,14,15,16,19,20,21,22,23,24,25,28,29,30,31,32,33,34}; - // ===== 字段 ===== - private final Set shulkerUsedOrders = - Collections.synchronizedSet(new HashSet<>()); - // ===== 待审批的退货退款 ===== - private final Map pendingReturns = - new HashMap<>(); - // ===== 退货申请确认 ===== - private final Map returnConfirmWaiting = - new HashMap<>(); // uuid -> orderId - private static final int RETURN_CONFIRM_TIMEOUT = 600; // 30秒 - - - private static class PendingReturn { - long orderId; - UUID playerUUID; - String playerName; - int totalPaid; - long createTime; - String packColor; - String packType; - List items; - - PendingReturn(OrderRecord r) { - this.orderId = r.orderId; - this.playerUUID = r.uuid; - this.playerName = r.player; - this.totalPaid = r.totalPaid; - this.createTime = System.currentTimeMillis(); - this.packColor = r.packColor; - this.packType = r.packType; - this.items = new ArrayList<>(r.items); - } - } - - - public static class OrderItem { - public String name; public String mat; - public int originalPrice; public int finalPrice; public int qty; - public OrderItem(String n,String m,int op,int fp,int q){ - name=n;mat=m;originalPrice=op;finalPrice=fp;qty=q;} - public int subtotal(){return finalPrice*qty;} - public int originalSubtotal(){return originalPrice*qty;} - } - public static class OrderRecord { - public long orderId; public UUID uuid; public String player; - public List items; public int totalOriginal; public int totalPaid; - public int discount; public String discountType; public String couponCode; - public int packFee; public String packType; public String packColor; - public long timestamp; public long printTime; public int status; - public int refundedAmount; public String refundType; - } - - public OrderManager(Main plugin) { - this.plugin = plugin; - loadReceiptConfig(); - loadOrders(); - startCleanupTask(); - Bukkit.getPluginManager().registerEvents(this, plugin); - } - public List getOrders() { return orders; } - - // ===== 小票模板 ===== - private void loadReceiptConfig() { - File f = new File(plugin.getDataFolder(), "小票设置.txt"); - if (!f.exists()) createDefaultReceiptConfig(f); - Map m = loadMap(f); - tplHeader = m.getOrDefault("头部", tplHeader); - tplItemLine = m.getOrDefault("商品行", tplItemLine); - tplDiscountLine = m.getOrDefault("优惠行", tplDiscountLine); - tplPackLine = m.getOrDefault("打包行", tplPackLine); - tplSummary = m.getOrDefault("汇总", tplSummary); - tplFooter = m.getOrDefault("页脚", tplFooter); - } - private void saveReceiptConfig() { - File f = new File(plugin.getDataFolder(), "小票设置.txt"); - List L = new ArrayList<>(); - L.add("# 小票模板配置"); - L.add("头部=" + tplHeader); - L.add("商品行=" + tplItemLine); - L.add("优惠行=" + tplDiscountLine); - L.add("打包行=" + tplPackLine); - L.add("汇总=" + tplSummary); - L.add("页脚=" + tplFooter); - writeLines(f, L); - } - private void createDefaultReceiptConfig(File f) { - List L = new ArrayList<>(); - L.add("# 小票模板配置"); - L.add("头部=§6§l{商店名}§r\n购物小票\n订单#{订单号} {订单时间}\n玩家: {玩家}"); - L.add("商品行=§e{品名}§r x{数量} {原价}→{执行价} 小计{小计}枚"); - L.add("优惠行=优惠: §a-{优惠额}枚§r ({优惠类型}{优惠券码})"); - L.add("打包行=打包: §c+{打包费}枚§r ({打包颜色})"); - L.add("汇总=原价{原价合计}枚 → 实付§6{实付}枚§r 余额{余额}枚"); - L.add("页脚=感谢惠顾,欢迎再来!"); - writeLines(f, L); - } - - // ===== 订单记录 ===== - public void recordOrder(Player p, List items, - int totalOriginal, int totalPaid, int discount, - String discountType, String couponCode, int packFee, - String packType, String packColor) { - OrderRecord r = new OrderRecord(); - r.orderId = System.currentTimeMillis(); - r.uuid = p.getUniqueId(); r.player = p.getName(); - r.items = items; r.totalOriginal = totalOriginal; - r.totalPaid = totalPaid; r.discount = discount; - r.discountType = discountType; r.couponCode = couponCode; - r.packFee = packFee; r.packType = packType; - r.packColor = packColor; r.timestamp = r.orderId; - r.printTime = 0; r.status = 0; r.refundedAmount = 0; - r.refundType = null; - orders.add(r); saveOrders(); - } - @EventHandler - public void onBlockPlace(org.bukkit.event.block.BlockPlaceEvent e) { - ItemStack item = e.getItemInHand(); - if (item == null) return; - if (!item.getType().name().contains("SHULKER")) return; - - ItemMeta meta = item.getItemMeta(); - if (meta == null || meta.getLore() == null) return; - - for (String line : meta.getLore()) { - if (line.startsWith("§8#")) { - try { - long orderId = Long.parseLong( - line.replace("§8#", "").trim()); - shulkerUsedOrders.add(orderId); - } catch (NumberFormatException ignored) {} - } - } - } - - // ===== 小票聊天栏 ===== - /** - * 聊天栏打印小票(给定参数 Player + OrderRecord) - * 不要改动方法签名 - */ - public void sendReceiptChat(Player p, OrderRecord r) { - if (p == null || r == null) return; - - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - p.sendMessage("§7§m "); - p.sendMessage("§6§l======== 商店购物小票 ========"); - - // 退款标记 - String statusTag = getStatusTag(r); - if (!statusTag.isEmpty()) { - p.sendMessage(statusTag); - } - - p.sendMessage("§7订单号: §f#" + r.orderId); - p.sendMessage("§7下单时间: §f" - + df.format(new Date(r.timestamp))); - p.sendMessage("§7玩家: §f" + r.player); - p.sendMessage("§7------------------------------"); - - for (OrderItem it : r.items) { - String price; - if (it.originalPrice != it.finalPrice) { - price = "§f" + it.originalPrice - + "§7→§6" + it.finalPrice; - } else { - price = "§6" + it.finalPrice; - } - p.sendMessage("§7 §e" + it.name - + " §7x" + it.qty - + " " + price + "枚" - + " §6小计" + it.subtotal() + "枚"); - } - - p.sendMessage("§7------------------------------"); - p.sendMessage("§7原价: §f" + r.totalOriginal + "枚"); - - if (r.discount > 0) { - int pct = (r.totalOriginal > 0) - ? (r.discount * 100 / r.totalOriginal) - : 0; - String dt = "coupon".equals(r.discountType) - ? "§7(优惠券 -" + pct + "%)" - : "eco".equals(r.discountType) - ? "§7(环保 -" + pct + "%)" - : "§7(-" + pct + "%)"; - p.sendMessage("§7优惠: §a-" + r.discount - + "枚 " + dt); - } - - if (r.packFee > 0) { - String cn = r.packColor != null - ? r.packColor - .replace("_SHULKER_BOX", "") - .replace("SHULKER_BOX", "原色") - : "默认"; - p.sendMessage("§7打包: §c+" + r.packFee - + "枚 §7(" + cn + "潜影盒)"); - } else { - p.sendMessage("§7包装: 直接放入背包"); - } - - p.sendMessage("§7------------------------------"); - p.sendMessage("§7实付: §6" + r.totalPaid + "枚"); - - int bal = 0; - try { - bal = plugin.getBonds().getBonds(r.player); - } catch (Exception ignored) {} - p.sendMessage("§7余额: §a" + bal + "枚"); - - // 退款标记 - if (!statusTag.isEmpty()) { - p.sendMessage(statusTag); - } - - p.sendMessage("§6§l=============================="); - r.printTime = System.currentTimeMillis(); - } - - /** - * 生成书本小票内容 - */ - private String buildReceiptText(OrderRecord r) { - String statusTag = getStatusTag(r); - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - StringBuilder sb = new StringBuilder(); - - sb.append("§6§l===== 商店小票 =====\n"); - - // 退款标记 - String tag = getStatusTag(r); - if (!tag.isEmpty()) sb.append(tag).append("\n"); - - sb.append("订单号: #").append(r.orderId).append("\n"); - sb.append("时间: ").append( - df.format(new Date(r.timestamp))).append("\n"); - sb.append("玩家: ").append(r.player).append("\n"); - sb.append("------------------------------\n"); - - for (OrderItem it : r.items) { - String pr = (it.originalPrice != it.finalPrice) - ? it.originalPrice + "->" + it.finalPrice - : String.valueOf(it.finalPrice); - sb.append(it.name).append(" x").append(it.qty) - .append(" ").append(pr).append("枚") - .append(" 小计").append(it.subtotal()) - .append("枚\n"); - } - - sb.append("------------------------------\n"); - sb.append("原价: ").append(r.totalOriginal) - .append("枚\n"); - - if (r.discount > 0) { - int pct = (r.totalOriginal > 0) - ? (r.discount * 100 / r.totalOriginal) - : 0; - String dt = "coupon".equals(r.discountType) - ? "优惠券(-" + pct + "%)" - : "环保(-" + pct + "%)"; - sb.append("优惠: -").append(r.discount) - .append("枚 ").append(dt).append("\n"); - } - - if (r.packFee > 0) { - sb.append("打包: +").append(r.packFee) - .append("枚\n"); - } - - sb.append("------------------------------\n"); - sb.append("实付: ").append(r.totalPaid).append("枚\n"); - - int bal = 0; - try { - bal = plugin.getBonds().getBonds(r.player); - } catch (Exception ignored) {} - sb.append("余额: ").append(bal).append("枚\n"); - - // 退款标记 - if (!statusTag.isEmpty()) sb.append("\n").append(tag).append("\n"); - - return sb.toString(); - } - - /** - * 退款状态标记文本 - */ - private String getStatusTag(OrderRecord r) { - switch (r.status) { - case 2: - return "§c§l<<<<<<< 已全额退款 >>>>>>>" - + "\n§c退款金额: " - + r.refundedAmount + "枚"; - case 3: - return "§c§l<<<<<<< 已部分退款 >>>>>>>" - + "\n§c退款金额: " - + r.refundedAmount + "枚"; - case 4: - return "§c§l<<<<<<< 退款申请已拒绝 >>>>>>>"; - default: - return ""; - } - } - - /** - * 创建小票书 - */ - public ItemStack createReceiptBook(OrderRecord r) { - String text = buildReceiptText(r); - String[] lines = text.split("\n"); - - ItemStack book = new ItemStack(Material.WRITTEN_BOOK); - BookMeta bm = (BookMeta) book.getItemMeta(); - bm.setTitle("购物小票 #" + r.orderId); - bm.setAuthor("商店"); - - StringBuilder page = new StringBuilder(); - int lineCount = 0; - for (String line : lines) { - // ★ 保留颜色代码 - page.append(line).append("\n"); - lineCount++; - if (lineCount >= 13) { - bm.addPage(page.toString().trim()); - page = new StringBuilder(); - lineCount = 0; - } - } - if (page.length() > 0) { - bm.addPage(page.toString().trim()); - } - - book.setItemMeta(bm); - return book; - } - - // ===== 退款工具 ===== - private boolean withinSelfRefundWindow(OrderRecord r) { return System.currentTimeMillis() - r.timestamp < SELF_REFUND_WINDOW; } - private int countDirectItems(Player p, String matName) { - Material m = Material.matchMaterial(matName); if (m == null) return 0; - int c = 0; for (ItemStack is : p.getInventory().getContents()) if (is != null && is.getType() == m) c += is.getAmount(); - return c; - } - private int countShulkers(Player p, String matName) { - Material m = Material.matchMaterial(matName); if (m == null) return 0; - int c = 0; for (ItemStack is : p.getInventory().getContents()) if (is != null && is.getType() == m) c += is.getAmount(); - return c; - } - private void removeItems(Player p, String matName, int amt) { - Material m = Material.matchMaterial(matName); if (m != null) p.getInventory().removeItem(new ItemStack(m, amt)); - } - private boolean removeOneShulker(Player p, - String materialName) { - // 先尝试精确匹配 - Material mat = Material.matchMaterial(materialName); - if (mat != null) { - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - p.getInventory().clear(i); - return true; - } - } - } - // 匹配不到,移除任意一个潜影盒 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType().name() - .endsWith("_SHULKER_BOX")) { - p.getInventory().clear(i); - return true; - } - } - return false; - } - public String validateRefund(OrderRecord r, Player p) { - if (r.status != 0) - return "订单状态: " + getStatusName(r.status); - - String pt = r.packType != null ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // ★ 检查:潜影盒是否被放置过 - if (shulkerUsedOrders.contains(r.orderId)) { - return "§c潜影盒已被使用过,无法自助退款"; - } - - // ★ 检查:背包里是否有同一订单的潜影盒 - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is == null) continue; - if (!is.getType().name().contains("SHULKER")) - continue; - ItemMeta im = is.getItemMeta(); - if (im == null || im.getLore() == null) - continue; - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - return null; // 找到了,可退 - } - } - } - return "§c对应的潜影盒已不在背包中"; - } else { - // ★ 散装:检测物品数量 - for (OrderItem it : r.items) { - int have = countDirectItems(p, it.mat); - if (have < it.qty) - return "§c" + it.name + "不足(有" - + have + "/" + it.qty + ")"; - } - } - return null; - } - - - - private void notifyPlayer(UUID uuid, String msg) { Player p = Bukkit.getPlayer(uuid); if (p != null && p.isOnline()) p.sendMessage(msg); } - private void notifyAdmins(Player p, OrderRecord r) { - String m = "§e[订单] " + p.getName() + " 申请退款 #" + r.orderId + " §6" + r.totalPaid + "枚"; - for (Player a : Bukkit.getOnlinePlayers()) if (isAdmin(a) && !a.equals(p)) a.sendMessage(m); - } - private String getStatusName(int s) { switch(s){case 0:return "正常";case 1:return "申请中";case 2:return "已退";case 3:return "部分退";case 4:return "已拒绝";default:return "未知";} } - private boolean isAdmin(Player p) { - if (p.isOp()) return true; - try { ConfigManager cm = plugin.getConfigMgr(); if (cm == null) return false; return p.hasPermission("sdf1.admin") || p.hasPermission("sdf1." + cm.adminTag); } catch (Exception e) { return p.hasPermission("sdf1.admin"); } - } - - // ===== 订单中心 ===== - public void openOrderCenter(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l订单中心"); - g.setItem(13, mkItem(Material.BOOK, "§e§l购买记录", "§7查看历史订单", "", "§a点击进入")); - if (isAdmin(p)) { g.setItem(31, mkItem(Material.NETHER_STAR, "§c§l管理员面板", "§7在线玩家购物记录", "§7支持退款操作", "", "§a点击进入")); - g.setItem(33, mkItem(Material.CRAFTING_TABLE, "§e§l小票配置", "§7配置小票模板样式", "", "§a点击进入")); } - g.setItem(49, mkItem(Material.ARROW, "§7返回商店")); - p.openInventory(g); - } - - // ===== 购买记录 ===== - public void openMyOrders(Player p, int page) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l购买记录"); - List list = getPlayerAllOrders(p.getUniqueId()); - int total = Math.max(1, (int) Math.ceil((double) list.size() / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); - userViewPage.put(p.getUniqueId(), page); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, list.size()); - for (int i = start; i < end; i++) { - int si = i - start; if (si >= SLOTS.length) break; - OrderRecord r = list.get(i); - Material icon; String st; String act; - if (r.status != 0) { icon = r.status == 1 ? Material.PAPER : Material.BARRIER; st = "§7状态: " + getStatusName(r.status); act = "§b左键: 补打小票"; } - else if (withinSelfRefundWindow(r)) { icon = Material.EMERALD; st = "§7状态: §a可退款(5分钟内)"; act = "§b左键: 补打小票\n§a右键: 退款退货\n§eShift+右键: 仅退款"; } - else { icon = Material.BOOK; st = "§7状态: §7已过自助退款期"; act = "§b左键: 补打小票\n§e右键: 申请退款退货\n§6Shift+右键: 申请仅退款"; } - ItemStack is = new ItemStack(icon); ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e订单 #" + r.orderId); - List lore = new ArrayList<>(); lore.add("§7" + fmtDate(r.timestamp)); - for (OrderItem it : r.items) lore.add("§7 §e" + it.name + " §7x" + it.qty + " §6" + it.subtotal() + "枚"); - lore.add("§7─────────"); lore.add("§7原价: §f" + r.totalOriginal + "枚"); - if (r.discount > 0) lore.add("§7优惠: §a-" + r.discount + "枚"); - lore.add("§7实付: §6" + r.totalPaid + "枚"); lore.add(st); lore.add(""); - for (String line : act.split("\n")) lore.add(line); - im.setLore(lore); is.setItemMeta(im); - } - g.setItem(SLOTS[si], is); - } - if (list.isEmpty()) g.setItem(31, mkItem(Material.BARRIER, "§7暂无订单记录")); - addPageButtons(g, page, total); - p.openInventory(g); - } - - // ===== 管理员面板 ===== - public void openAdminPanel(Player p, int page) { - if (!isAdmin(p)) { p.sendMessage("§c权限不足"); return; } - Inventory g = Bukkit.createInventory(null, 54, "§c§l管理员订单面板"); - Player[] online = Bukkit.getOnlinePlayers().toArray(new Player[0]); - int total = Math.max(1, (int) Math.ceil((double) online.length / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); adminViewPage.put(p.getUniqueId(), page); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, online.length); - // ★ 替换 openAdminPanel 中的物品创建部分 - for (int i = start; i < end; i++) { - int si = i - start; - if (si >= SLOTS.length) break; - Player t = online[i]; - List tl = getPlayerOrdersWithin( - t.getUniqueId(), RETENTION_30D); - int ts = 0; - for (OrderRecord r : tl) ts += r.totalPaid; - - ItemStack is = new ItemStack(Material.PLAYER_HEAD); - org.bukkit.inventory.meta.SkullMeta sm = - (org.bukkit.inventory.meta.SkullMeta) - is.getItemMeta(); - if (sm != null) { - sm.setOwner(t.getName()); // ★ 设置头颅皮肤 - sm.setDisplayName("§e" + t.getName()); - sm.setLore(Arrays.asList( - "§7订单: §f" + tl.size() + "笔", - "§7消费: §6" + ts + "枚", - "", "§a点击进入")); - is.setItemMeta(sm); // ★ 这行之前缺失了 - } - g.setItem(SLOTS[si], is); - } - - if (online.length == 0) g.setItem(31, mkItem(Material.BARRIER, "§7无在线玩家")); - addPageButtons(g, page, total); p.openInventory(g); - } - - // ===== 管理员订单 ===== - public void openAdminOrders(Player p, UUID targetUUID, int page) { - Inventory g = Bukkit.createInventory(null, 54, "§c§l订单管理"); - String name = Bukkit.getOfflinePlayer(targetUUID).getName(); - List list = getPlayerOrdersWithin(targetUUID, RETENTION_30D); - int total = Math.max(1, (int) Math.ceil((double) list.size() / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); - adminViewPage.put(p.getUniqueId(), page); adminViewTarget.put(p.getUniqueId(), targetUUID); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, list.size()); - for (int i = start; i < end; i++) { - int si = i - start; if (si >= SLOTS.length) break; - OrderRecord r = list.get(i); - Material icon = r.status == 2 || r.status == 4 ? Material.BARRIER : r.status == 1 ? Material.PAPER : Material.BOOK; - ItemStack is = new ItemStack(icon); ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e订单 #" + r.orderId); - List lore = new ArrayList<>(); lore.add("§7" + fmtDate(r.timestamp)); - for (OrderItem it : r.items) lore.add("§7 §e" + it.name + " §7x" + it.qty + " §6" + it.subtotal() + "枚"); - lore.add("§7─────────"); lore.add("§7实付: §6" + r.totalPaid + "枚"); - if (r.status == 1) { lore.add("§7状态: §e申请中(" + nn(r.refundType) + ")"); lore.add(""); lore.add("§a右键: 批准"); lore.add("§c左键: 拒绝"); } - else if (r.status == 2) lore.add("§7已退: §a" + r.refundedAmount + "枚"); - else if (r.status == 3) lore.add("§7部分退: §a" + r.refundedAmount + "枚"); - else if (r.status == 4) lore.add("§7已拒绝"); - else { lore.add(""); lore.add("§a右键: 整单退款"); lore.add("§e左键: 部分退款(输入%)"); } - im.setLore(lore); is.setItemMeta(im); - } - g.setItem(SLOTS[si], is); - } - if (list.isEmpty()) g.setItem(31, mkItem(Material.BARRIER, "§7该玩家暂无订单")); - addPageButtons(g, page, total); - g.setItem(49, mkItem(org.bukkit.Material.PAPER, name + " §e" + (page + 1) + "/" + total)); - p.openInventory(g); - } - private void addPageButtons(Inventory g, int page, int total) { - g.setItem(48, page > 0 ? mkItem(Material.ARROW, "§7上一页") : mkItem(Material.BLACK_STAINED_GLASS_PANE, " ")); - if (g.getItem(49) == null) g.setItem(49, mkItem(Material.PAPER, "§e" + (page + 1) + "/" + total)); - g.setItem(50, page < total - 1 ? mkItem(Material.ARROW, "§7下一页") : mkItem(Material.BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(53, mkItem(Material.ARROW, "§7返回")); - } - - // ===== 小票配置 ===== - public void openReceiptConfig(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l小票配置"); - g.setItem(10, mkItem(Material.PAPER, "§e§l头部", "§7" + brief(tplHeader), "", "§a点击编辑")); - g.setItem(11, mkItem(Material.PAPER, "§e§l商品行", "§7" + brief(tplItemLine), "", "§a点击编辑")); - g.setItem(12, mkItem(Material.PAPER, "§e§l优惠行", "§7" + brief(tplDiscountLine), "", "§a点击编辑")); - g.setItem(14, mkItem(Material.PAPER, "§e§l打包行", "§7" + brief(tplPackLine), "", "§a点击编辑")); - g.setItem(15, mkItem(Material.PAPER, "§e§l汇总", "§7" + brief(tplSummary), "", "§a点击编辑")); - g.setItem(16, mkItem(Material.PAPER, "§e§l页脚", "§7" + brief(tplFooter), "", "§a点击编辑")); - g.setItem(22, mkItem(Material.BOOK, "§6§l预览(随机价格)", "§7预览小票效果")); - g.setItem(48, mkItem(Material.LIME_WOOL, "§a§l保存")); g.setItem(49, mkItem(Material.ARROW, "§7返回")); - g.setItem(50, mkItem(Material.RED_WOOL, "§c§l重置")); - p.openInventory(g); - } - public void openReceiptBuilder(Player p, String section) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l编辑: " + sectionName(section)); - g.setItem(10, mkItem(Material.EMERALD, "§a{品名}")); g.setItem(11, mkItem(Material.EMERALD, "§a{原价}")); - g.setItem(12, mkItem(Material.EMERALD, "§a{执行价}")); g.setItem(13, mkItem(Material.EMERALD, "§a{数量}")); - g.setItem(14, mkItem(Material.EMERALD, "§a{小计}")); g.setItem(15, mkItem(Material.EMERALD, "§a{商店名}")); - g.setItem(16, mkItem(Material.EMERALD, "§a{玩家}")); g.setItem(19, mkItem(Material.DIAMOND, "§b{订单号}")); - g.setItem(20, mkItem(Material.DIAMOND, "§b{订单时间}")); g.setItem(21, mkItem(Material.DIAMOND, "§b{打印时间}")); - g.setItem(22, mkItem(Material.DIAMOND, "§b{日期}")); g.setItem(23, mkItem(Material.DIAMOND, "§b{时间}")); - g.setItem(24, mkItem(Material.DIAMOND, "§b{原价合计}")); g.setItem(25, mkItem(Material.DIAMOND, "§b{实付}")); - g.setItem(28, mkItem(Material.GOLD_INGOT, "§6{优惠额}")); g.setItem(29, mkItem(Material.GOLD_INGOT, "§6{优惠类型}")); - g.setItem(30, mkItem(Material.GOLD_INGOT, "§6{优惠券码}")); g.setItem(31, mkItem(Material.GOLD_INGOT, "§6{打包费}")); - g.setItem(32, mkItem(Material.GOLD_INGOT, "§6{打包颜色}")); g.setItem(33, mkItem(Material.GOLD_INGOT, "§6{余额}")); - g.setItem(37, mkItem(Material.STONE, "§f═══")); g.setItem(38, mkItem(Material.STONE, "§f───")); - g.setItem(39, mkItem(Material.STONE, "§f═════════════")); g.setItem(40, mkItem(Material.PAPER, "§e换行(\\n)")); - g.setItem(47, mkItem(Material.RED_STAINED_GLASS_PANE, "§c删末尾")); g.setItem(48, mkItem(Material.RED_WOOL, "§c清空")); - g.setItem(49, mkItem(Material.ARROW, "§7返回")); g.setItem(50, mkItem(Material.LIME_WOOL, "§a保存")); - g.setItem(51, mkItem(Material.BLUE_WOOL, "§9重置")); - p.openInventory(g); - } - // ===== 点击处理 ===== - public boolean handleCenterClick(Player p, int raw) { - if (raw == 13) { openMyOrders(p, 0); return true; } - if (raw == 31 && isAdmin(p)) { openAdminPanel(p, 0); return true; } - if (raw == 33 && isAdmin(p)) { openReceiptConfig(p); return true; } - if (raw == 49) { plugin.getShopManager().openShopMain(p); return true; } - return true; - } - public boolean handleMyOrdersClick(Player p, int raw, boolean left, boolean shift) { - List list = getPlayerAllOrders(p.getUniqueId()); - int page = userViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { openMyOrders(p, page - 1); return true; } - if (raw == 50) { openMyOrders(p, page + 1); return true; } - if (raw == 53) { openOrderCenter(p); return true; } - int si = slotIndex(raw); if (si < 0) return true; - int actual = si + page * PAGE_SIZE; if (actual < 0 || actual >= list.size()) return true; - OrderRecord r = list.get(actual); - if (left) { - try { ItemStack book = createReceiptBook(r); - HashMap ov = p.getInventory().addItem(book); - for (ItemStack d : ov.values()) p.getWorld().dropItemNaturally(p.getLocation(), d); - r.printTime = System.currentTimeMillis(); p.sendMessage("§a已补打小票书: #" + r.orderId); - } catch (Exception e) { p.sendMessage("§c补打失败"); } - } else { - if (r.status != 0) { - p.sendMessage("§c状态: " + getStatusName(r.status)); - } else if (withinSelfRefundWindow(r)) { - if (shift) { - // ★ Shift+右键:仅退款申请(需管理员审批) - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请,等待管理员审批"); - notifyAdmins(p, r); - } else { - // ★ 普通右键:5分钟内直接退(自动通过) - String reason = validateRefund(r, p); - if (reason != null) { - p.sendMessage("§c" + reason - + ",Shift+右键可申请仅退款"); - } else { - // 扣货 - String pt = r.packType != null - ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - if (isShulker) { - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory() - .getItem(i); - if (is != null && is.getType() - .name().contains("SHULKER")) { - p.getInventory().clear(i); - break; - } - } - } else { - for (OrderItem it : r.items) - removeItems(p, it.mat, it.qty); - } - // 退钱 - plugin.getBonds().addBonds( - r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - r.refundType = "auto_return"; - saveOrders(); - p.sendMessage("§a退款退货成功: §6" - + r.totalPaid + "枚"); - } - } - - - } else { - if (r.status != 0) { - p.sendMessage("§c状态: " - + getStatusName(r.status)); - } else if (withinSelfRefundWindow(r)) { - if (shift) { - // 仅退款申请 - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请," - + "等待管理员审批"); - notifyAdmins(p, r); - } else { - // 5分钟内退货退款 - String reason = validateRefund(r, p); - if (reason != null) { - // ★ 不满足条件:询问是否申请 - p.sendMessage("§c" + reason); - askReturnConfirm(p, r); - } else { - // 满足条件:自动退 - doAutoReturn(p, r); - } - } - } else { - if (shift) { - // 申请仅退款 - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请"); - notifyAdmins(p, r); - } else { - // ★ 申请退货退款:先检查条件 - String reason = validateRefund(r, p); - if (reason != null) { - p.sendMessage("§c" + reason); - } - // ★ 无论是否满足条件都询问 - askReturnConfirm(p, r); - } - } - } - } - if (left) { - // 已退款订单仍然允许补打小票,但小票内会显示退款标记 - try { - ItemStack book = createReceiptBook(r); - HashMap overflow = - p.getInventory().addItem(book); - for (ItemStack drop : overflow.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - r.printTime = System.currentTimeMillis(); - if (r.status != 0) { - p.sendMessage("§e已补打小票(含退款标记): #" - + r.orderId); - } else { - p.sendMessage("§a已补打小票书: #" - + r.orderId); - } - } catch (Exception e) { - p.sendMessage("§c补打失败"); - } - } - - final int fp = page; Bukkit.getScheduler().runTask(plugin, () -> openMyOrders(p, fp)); - return true; - } - public boolean handleAdminClick(Player p, int raw) { - Player[] online = Bukkit.getOnlinePlayers().toArray(new Player[0]); - int page = adminViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { openAdminPanel(p, page - 1); return true; } - if (raw == 50) { openAdminPanel(p, page + 1); return true; } - if (raw == 53) { openOrderCenter(p); return true; } - int si = slotIndex(raw); if (si < 0) return true; - int actual = si + page * PAGE_SIZE; if (actual < 0 || actual >= online.length) return true; - openAdminOrders(p, online[actual].getUniqueId(), 0); return true; - } - public boolean handleAdminOrdersClick(Player p, int raw, boolean left, boolean shift) { - UUID tu = adminViewTarget.get(p.getUniqueId()); - if (tu == null) return false; - List list = getPlayerOrdersWithin(tu, RETENTION_30D); - int page = adminViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { - openAdminOrders(p, tu, page - 1); - return true; - } - if (raw == 50) { - openAdminOrders(p, tu, page + 1); - return true; - } - if (raw == 53) { - openAdminPanel(p, 0); - return true; - } - int si = slotIndex(raw); - if (si < 0) return true; - int actual = si + page * PAGE_SIZE; - if (actual < 0 || actual >= list.size()) return true; - OrderRecord r = list.get(actual); - if (r.status == 2 || r.status == 3 || r.status == 4) { - p.sendMessage("§c状态: " + getStatusName(r.status)); - return true; - } - if (left) { - if (r.status == 1) { - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝: #" + r.orderId); - notifyPlayer(r.uuid, "§c退款被拒绝"); - openAdminOrders(p, tu, page); - } else if (r.status == 0) { - partialRefundListening.add(p.getUniqueId()); - partialRefundTarget.put(p.getUniqueId(), r); - p.closeInventory(); - p.sendMessage("§e输入退款百分比(1-100): #" + r.orderId + " 实付" + r.totalPaid + "枚"); - } - } else { - if (r.status == 0 || r.status == 1) { - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a全额退款 §e" + r.player + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, "§a退款 §6" + r.totalPaid + "枚"); - openAdminOrders(p, tu, page); - } - } - // ★ 替换 handleAdminOrdersClick 中的审批逻辑 - if (left) { - // 左键 = 拒绝 - if (r.status == 1) { - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝退款: #" + r.orderId); - notifyPlayer(r.uuid, "§c退款申请已被拒绝"); - openAdminOrders(p, tu, page); - } - } else { - // 右键 = 批准 - if (r.status == 1) { - String rt = r.refundType != null - ? r.refundType : ""; - - // ★ 退款退货申请 → 检查潜影盒 - if (rt.equals("refund_return_apply")) { - String pt = r.packType != null - ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // 检查玩家背包里有没有带订单标签的盒子 - Player target = Bukkit.getPlayer(r.uuid); - if (target == null || !target.isOnline()) { - p.sendMessage("§c玩家不在线,无法执行退货退款"); - openAdminOrders(p, tu, page); - return true; - } - - boolean foundTagged = false; - boolean foundAny = false; - for (int i = 0; i < 36; i++) { - ItemStack is = target.getInventory() - .getItem(i); - if (is == null) continue; - if (!is.getType().name() - .contains("SHULKER")) continue; - foundAny = true; - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - foundTagged = true; - // 收走盒子 - target.getInventory().clear(i); - break; - } - } - } - if (foundTagged) break; - } - - if (foundTagged) { - // ★ 有带标记的盒子 → 收走 + 退钱 - plugin.getBonds().addBonds( - r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" - + r.player + " §6" - + r.totalPaid + "枚(盒子已收走)"); - notifyPlayer(r.uuid, - "§a[订单] 退货退款成功,§6" - + r.totalPaid + "枚已退还"); - openAdminOrders(p, tu, page); - return true; - } else if (foundAny) { - // ★ 有盒子但没标记 → 等管理员决定 - pendingReturns.put(r.orderId, - new PendingReturn(r)); - p.closeInventory(); - p.sendMessage("§e§l[退货审批] 订单 #" - + r.orderId); - p.sendMessage("§7玩家背包有潜影盒但" - + "无订单标记,可能已被替换"); - p.sendMessage("§a输入 §e1 §a= 收走盒子并退款"); - p.sendMessage("§e输入 §e2 §e= 仅退款(不收盒子)"); - p.sendMessage("§c输入 §c3 §c= 拒绝退款"); - p.sendMessage("§72分钟内未操作自动拒绝"); - // 启动2分钟超时 - startReturnTimeout(r.orderId); - return true; - } else { - // ★ 背包里没有任何盒子 - p.sendMessage("§c玩家背包里没有潜影盒"); - p.sendMessage("§7可拒绝并通知玩家" - + "申请仅退款或把货拿回来"); - openAdminOrders(p, tu, page); - return true; - } - } - // ★ 散装退货退款:扣货+退钱 - Player target = Bukkit.getPlayer(r.uuid); - if (target != null && target.isOnline()) { - for (OrderItem it : r.items) - removeItems(target, it.mat, it.qty); - } - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" + r.player - + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, - "§a[订单] 退货退款成功,§6" - + r.totalPaid + "枚"); - openAdminOrders(p, tu, page); - return true; - } - - // ★ 仅退款申请 / 5分钟内自动退款 - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已批准退款 §e" + r.player - + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, - "§a[订单] 退款已批准,§6" - + r.totalPaid + "枚已退还"); - openAdminOrders(p, tu, page); - } - } - return true; - } - - /** - * 询问用户是否提交退货退款申请 - */ - private void askReturnConfirm(Player p, OrderRecord r) { - returnConfirmWaiting.put(p.getUniqueId(), r.orderId); - p.sendMessage(""); - p.sendMessage("§e§l[退货退款申请]"); - p.sendMessage("§7订单 #" + r.orderId - + " 实付" + r.totalPaid + "枚"); - p.sendMessage("§a输入 §e1 §a= 提交退货退款申请"); - p.sendMessage("§c输入 §e2 §c= 取消"); - p.sendMessage("§730秒内未操作自动取消"); - p.sendMessage(""); - - // 30秒超时 - final UUID uuid = p.getUniqueId(); - final long oid = r.orderId; - new BukkitRunnable() { - @Override - public void run() { - Long waiting = returnConfirmWaiting.get(uuid); - if (waiting != null && waiting == oid) { - returnConfirmWaiting.remove(uuid); - Player pl = Bukkit.getPlayer(uuid); - if (pl != null && pl.isOnline()) { - pl.sendMessage("§7退货退款申请已超时取消"); - } - } - } - }.runTaskLater(plugin, RETURN_CONFIRM_TIMEOUT); - } - - /** - * 执行5分钟内自动退货退款 - */ - private void doAutoReturn(Player p, OrderRecord r) { - String pt = r.packType != null ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // 找并收走带标签的盒子 - boolean found = false; - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is == null) continue; - if (!is.getType().name().contains("SHULKER")) - continue; - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - p.getInventory().clear(i); - found = true; - break; - } - } - } - if (found) break; - } - // 没找到标签盒子,收任意潜影盒 - if (!found) { - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType().name() - .contains("SHULKER")) { - p.getInventory().clear(i); - break; - } - } - } - } else { - for (OrderItem it : r.items) - removeItems(p, it.mat, it.qty); - } - - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - r.refundType = "auto_return"; - saveOrders(); - p.sendMessage("§a退款退货成功: §6" - + r.totalPaid + "枚"); - } - - - // ===== 2分钟超时 ===== - private void startReturnTimeout(long orderId) { - new BukkitRunnable() { - @Override - public void run() { - PendingReturn pr = pendingReturns - .remove(orderId); - if (pr == null) return; // 已处理 - Player admin = Bukkit.getPlayer(pr.playerUUID); - if (admin != null && admin.isOnline()) { - admin.sendMessage("§c[退货审批] 订单 #" - + orderId + " 超时,自动拒绝"); - } - // 通知玩家 - notifyPlayer(pr.playerUUID, - "§c[订单] 退货退款申请已超时拒绝"); - // 恢复订单状态 - OrderRecord r = findOrder(orderId); - if (r != null) { - r.status = 0; - r.refundType = null; - saveOrders(); - } - } - }.runTaskLater(plugin, 2400L); // 2分钟 - } - - // ===== 查找订单 ===== - private OrderRecord findOrder(long orderId) { - synchronized (orders) { - for (OrderRecord r : orders) { - if (r.orderId == orderId) return r; - } - } - return null; - } - - - public boolean handleReceiptConfigClick(Player p, int raw) { - if (raw == 49) { openOrderCenter(p); return true; } - if (raw == 48) { saveReceiptConfig(); p.sendMessage("§a已保存"); openReceiptConfig(p); return true; } - if (raw == 50) { createDefaultReceiptConfig(new File(plugin.getDataFolder(), "小票设置.txt")); loadReceiptConfig(); p.sendMessage("§e已重置"); openReceiptConfig(p); return true; } - if (raw == 22) { sendRandomPreview(p); return true; } - String sec = null; - if (raw == 10) sec = "header"; else if (raw == 11) sec = "itemLine"; else if (raw == 12) sec = "discountLine"; - else if (raw == 14) sec = "packLine"; else if (raw == 15) sec = "summary"; else if (raw == 16) sec = "footer"; - if (sec != null) { receiptBuilderSection.put(p.getUniqueId(), sec); openReceiptBuilder(p, sec); } - return true; - } - public boolean handleReceiptBuilderClick(Player p, int raw) { - String sec = receiptBuilderSection.get(p.getUniqueId()); if (sec == null) return false; - String token = getTokenBySlot(raw); - if (token != null) { String cur = getSection(sec); setSection(sec, cur.isEmpty() ? token : cur + token); openReceiptBuilder(p, sec); return true; } - if (raw == 47) { String c = getSection(sec); int lb = c.lastIndexOf('}'); if (lb >= 0) { int ls = c.lastIndexOf('{', lb); if (ls >= 0) setSection(sec, c.substring(0, ls)); } else setSection(sec, c.substring(0, Math.max(0, c.length() - 8))); openReceiptBuilder(p, sec); return true; } - if (raw == 48) { setSection(sec, ""); openReceiptBuilder(p, sec); return true; } - if (raw == 49) { receiptBuilderSection.remove(p.getUniqueId()); openReceiptConfig(p); return true; } - if (raw == 50) { saveReceiptConfig(); receiptBuilderSection.remove(p.getUniqueId()); p.sendMessage("§a已保存"); openReceiptConfig(p); return true; } - if (raw == 51) { setSection(sec, getDefaultVal(sec)); openReceiptBuilder(p, sec); return true; } - return true; - } - - // ===== 聊天事件 ===== - @EventHandler - public void onAdminChat(AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); UUID uuid = p.getUniqueId(); - // ★ 在 onAdminChat 方法开头添加 -// ===== 退货审批输入 ===== - for (Map.Entry entry : - new HashMap<>(pendingReturns).entrySet()) { - Long pendingOrderId = entry.getKey(); - PendingReturn pr = entry.getValue(); - if (pr.playerUUID.equals(p.getUniqueId()) - || isAdmin(p)) { - try { - int choice = Integer.parseInt( - e.getMessage().trim()); - pendingReturns.remove(pendingOrderId); - OrderRecord r = findOrder(pendingOrderId); - if (r == null) { - p.sendMessage("§c订单不存在"); - return; - } - - if (choice == 1) { - // ★ 收走盒子并退款 - Player target = Bukkit.getPlayer( - pr.playerUUID); - if (target != null && target.isOnline()) { - for (int i = 0; i < 36; i++) { - ItemStack is = target.getInventory() - .getItem(i); - if (is != null && is.getType().name() - .contains("SHULKER")) { - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + pendingOrderId)) { - target.getInventory().clear(i); - break; - } - } - } - break; - } - } - } - plugin.getBonds().addBonds(pr.playerName, pr.totalPaid); - r.status = 2; - r.refundedAmount = pr.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" + pr.playerName - + " §6" + pr.totalPaid + "枚(盒子已收走)"); - notifyPlayer(pr.playerUUID, - "§a[订单] 退货退款成功,§6" - + pr.totalPaid + "枚已退还"); - - } else if (choice == 2) { - // ★ 仅退款(不收盒子) - plugin.getBonds().addBonds(pr.playerName, pr.totalPaid); - r.status = 2; - r.refundedAmount = pr.totalPaid; - saveOrders(); - p.sendMessage("§e已仅退款 §e" + pr.playerName - + " §6" + pr.totalPaid + "枚(未收盒子)"); - notifyPlayer(pr.playerUUID, - "§e[订单] 仅退款成功,§6" - + pr.totalPaid + "枚已退还" - + ",盒子无需退回"); - - } else if (choice == 3) { - // ★ 拒绝 - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝退款 #" + pendingOrderId); - notifyPlayer(pr.playerUUID, - "§c[订单] 退货退款已被拒绝" - + ",您可以申请仅退款" - + "或把货拿回来后重新申请"); - } else { - p.sendMessage("§c请输入 1、2 或 3"); - pendingReturns.put(pendingOrderId, pr); - } - return; - - } catch (NumberFormatException ignored) { - // 不是数字,跳过 - } - } - } -// ★ 在 onAdminChat 方法开头,处理用户退货确认 - if (returnConfirmWaiting.containsKey(uuid)) { - String msg = e.getMessage().trim(); - if (msg.equals("1")) { - // ★ 确认提交 - Long orderId = returnConfirmWaiting.remove(uuid); - if (orderId != null) { - OrderRecord r = findOrder(orderId); - if (r != null) { - r.status = 1; - r.refundType = "refund_return_apply"; - saveOrders(); - p.sendMessage("§e已提交退货退款申请," - + "等待管理员审批"); - notifyAdmins(p, r); - } - } - e.setCancelled(true); - return; - } else if (msg.equals("2")) { - // ★ 取消 - returnConfirmWaiting.remove(uuid); - p.sendMessage("§7已取消退货退款申请"); - e.setCancelled(true); - return; - } else { - // 输入其他内容:取消等待 - returnConfirmWaiting.remove(uuid); - // 不 return,让消息正常发送 - } - } - - if (partialRefundListening.contains(uuid)) { - e.setCancelled(true); partialRefundListening.remove(uuid); - OrderRecord t = partialRefundTarget.remove(uuid); if (t == null) return; - try { int pct = Integer.parseInt(e.getMessage().trim()); if (pct < 1 || pct > 100) { p.sendMessage("§c1-100"); return; } - int ref = t.totalPaid * pct / 100; if (ref <= 0) ref = 1; - plugin.getBonds().addBonds(t.player, ref); t.status = 3; t.refundedAmount = ref; saveOrders(); - p.sendMessage("§a部分退款 §e" + t.player + " §6" + ref + "枚(" + pct + "%)"); - notifyPlayer(t.uuid, "§a退款 §6" + ref + "枚"); - final UUID tu = adminViewTarget.get(uuid); final int pg = adminViewPage.getOrDefault(uuid, 0); - Bukkit.getScheduler().runTask(plugin, () -> openAdminOrders(p, tu, pg)); - } catch (NumberFormatException ex) { p.sendMessage("§c请输入数字"); } catch (Exception ex) { plugin.getLogger().log(Level.WARNING, "[Order]退款异常", ex); } - return; - } - if (receiptBuilderSection.containsKey(uuid)) { - e.setCancelled(true); String sec = receiptBuilderSection.remove(uuid); if (sec == null) return; - String msg = e.getMessage().trim(); - if ("cancel".equalsIgnoreCase(msg)) { Bukkit.getScheduler().runTask(plugin, () -> openReceiptConfig(p)); return; } - setSection(sec, msg.replace("\\n", "\n")); p.sendMessage("§a已更新 §e" + sec); - Bukkit.getScheduler().runTask(plugin, () -> openReceiptConfig(p)); - } - } - - // ===== 数据查询 ===== - private List getPlayerAllOrders(UUID uuid) { - List r = new ArrayList<>(); - synchronized (orders) { for (OrderRecord o : orders) if (o.uuid.equals(uuid) && System.currentTimeMillis() - o.timestamp < RETENTION_30D) r.add(o); } - Collections.reverse(r); return r; - } - private List getPlayerOrdersWithin(UUID uuid, long ms) { - List r = new ArrayList<>(); long cut = System.currentTimeMillis() - ms; - synchronized (orders) { for (OrderRecord o : orders) if (o.uuid.equals(uuid) && o.timestamp >= cut) r.add(o); } - Collections.reverse(r); return r; - } - - // ===== 持久化 ===== - private void loadOrders() { - File f = new File(plugin.getDataFolder(), "orders.dat"); if (!f.exists()) return; - try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); - OrderRecord cur = null; String line; - while ((line = br.readLine()) != null) { line = line.trim(); - if (line.equals("==")) { cur = new OrderRecord(); cur.items = new ArrayList<>(); } - else if (cur != null && line.contains("=")) { int eq = line.indexOf('='); String k = line.substring(0, eq); String v = line.substring(eq + 1); - switch (k) { case "id": cur.orderId = Long.parseLong(v); cur.timestamp = cur.orderId; break; case "uuid": cur.uuid = UUID.fromString(v); break; - case "player": cur.player = v; break; case "totalOriginal": cur.totalOriginal = Integer.parseInt(v); break; case "totalPaid": cur.totalPaid = Integer.parseInt(v); break; - case "discount": cur.discount = Integer.parseInt(v); break; case "discountType": cur.discountType = v; break; case "couponCode": cur.couponCode = v; break; - case "packFee": cur.packFee = Integer.parseInt(v); break; case "packType": cur.packType = v; break; case "packColor": cur.packColor = v; break; - case "status": cur.status = Integer.parseInt(v); break; case "refundedAmount": cur.refundedAmount = Integer.parseInt(v); break; - case "printTime": cur.printTime = Long.parseLong(v); break; case "refundType": cur.refundType = v; break; - case "item": String[] pp = v.split("\\|"); if (pp.length >= 5) cur.items.add(new OrderItem(pp[0], pp[1], Integer.parseInt(pp[2]), Integer.parseInt(pp[3]), Integer.parseInt(pp[4]))); break; } - } else if (line.isEmpty() && cur != null && cur.uuid != null) { orders.add(cur); cur = null; } - } if (cur != null && cur.uuid != null) orders.add(cur); br.close(); - } catch (Exception e) { plugin.getLogger().warning("[Order]加载失败: " + e.getMessage()); } - } - private void saveOrders() { - File f = new File(plugin.getDataFolder(), "orders.dat"); - try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); - synchronized (orders) { for (OrderRecord r : orders) { pw.println("=="); - pw.println("id=" + r.orderId); pw.println("uuid=" + r.uuid); pw.println("player=" + r.player); - pw.println("totalOriginal=" + r.totalOriginal); pw.println("totalPaid=" + r.totalPaid); - pw.println("discount=" + r.discount); pw.println("discountType=" + nn(r.discountType)); - pw.println("couponCode=" + nn(r.couponCode)); pw.println("packFee=" + r.packFee); - pw.println("packType=" + nn(r.packType)); pw.println("packColor=" + nn(r.packColor)); - pw.println("status=" + r.status); pw.println("refundedAmount=" + r.refundedAmount); - pw.println("printTime=" + r.printTime); pw.println("refundType=" + nn(r.refundType)); - for (OrderItem it : r.items) pw.println("item=" + it.name + "|" + it.mat + "|" + it.originalPrice + "|" + it.finalPrice + "|" + it.qty); - } } pw.flush(); pw.close(); - } catch (Exception e) { plugin.getLogger().warning("[Order]保存失败: " + e.getMessage()); } - } - private void startCleanupTask() { new BukkitRunnable() { public void run() { synchronized (orders) { if (orders.removeIf(OrderManager.this::isExpired)) saveOrders(); } } }.runTaskTimer(plugin, 36000L, 36000L); } - /** - * 将小票书塞入潜影盒 - */ - public void addBookToShulker(ItemStack shulker, ItemStack book) { - try { - org.bukkit.inventory.meta.BlockStateMeta bm = - (org.bukkit.inventory.meta.BlockStateMeta) - shulker.getItemMeta(); - if (bm == null) return; - org.bukkit.block.BlockState st = bm.getBlockState(); - if (st instanceof org.bukkit.block.Container) { - ((org.bukkit.block.Container) st) - .getInventory().addItem(book); - bm.setBlockState(st); - shulker.setItemMeta(bm); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Order] 塞书失败: " + e.getMessage()); - } - } - - // ===== 工具 ===== - private String nn(String s) { return s != null ? s : ""; } - private boolean isExpired(OrderRecord r) { return System.currentTimeMillis() - r.timestamp > RETENTION_30D; } - private String fmtDate(long ts) { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ts)); } - private int slotIndex(int raw) { for (int i = 0; i < SLOTS.length; i++) if (SLOTS[i] == raw) return i; return -1; } - private String brief(String s) { if (s == null || s.isEmpty()) return "(空)"; String c = sc(s); return c.length() > 30 ? c.substring(0, 30) + "..." : c; } - private String sectionName(String s) { switch(s){case "header":return "头部";case "itemLine":return "商品行";case "discountLine":return "优惠行";case "packLine":return "打包行";case "summary":return "汇总";case "footer":return "页脚";default:return s;} } - private String sc(String s) { - if (s == null) return ""; - return s.replaceAll("§[0-9a-fk-orA-FK-OR]", ""); - } - private String getSection(String s) { switch(s){case "header":return tplHeader;case "itemLine":return tplItemLine;case "discountLine":return tplDiscountLine;case "packLine":return tplPackLine;case "summary":return tplSummary;case "footer":return tplFooter;default:return "";} } - private void setSection(String s, String v) { switch(s){case "header":tplHeader=v;break;case "itemLine":tplItemLine=v;break;case "discountLine":tplDiscountLine=v;break;case "packLine":tplPackLine=v;break;case "summary":tplSummary=v;break;case "footer":tplFooter=v;break;} } - private String getDefaultVal(String s) { switch(s){case "header":return "§6§l{商店名}§r\n购物小票\n订单#{订单号} {订单时间}\n玩家: {玩家}";case "itemLine":return "§e{品名}§r x{数量} {原价}→{执行价} 小计{小计}枚";case "discountLine":return "优惠: §a-{优惠额}枚§r ({优惠类型}{优惠券码})";case "packLine":return "打包: §c+{打包费}枚§r ({打包颜色})";case "summary":return "原价{原价合计}枚 → 实付§6{实付}枚§r 余额{余额}枚";case "footer":return "感谢惠顾,欢迎再来!";default:return "";} } - private String getTokenBySlot(int raw) { switch(raw){case 10:return "{品名}";case 11:return "{原价}";case 12:return "{执行价}";case 13:return "{数量}";case 14:return "{小计}";case 15:return "{商店名}";case 16:return "{玩家}";case 19:return "{订单号}";case 20:return "{订单时间}";case 21:return "{打印时间}";case 22:return "{日期}";case 23:return "{时间}";case 24:return "{原价合计}";case 25:return "{实付}";case 28:return "{优惠额}";case 29:return "{优惠类型}";case 30:return "{优惠券码}";case 31:return "{打包费}";case 32:return "{打包颜色}";case 33:return "{余额}";case 37:return "═══";case 38:return "───";case 39:return "═════════════";case 40:return "\\n";default:return null;} } - private ItemStack mkItem(Material mat, String name, String... lore) { ItemStack it = new ItemStack(mat); ItemMeta im = it.getItemMeta(); if (im != null) { im.setDisplayName(name); if (lore.length > 0) im.setLore(Arrays.asList(lore)); it.setItemMeta(im); } return it; } - private Map loadMap(File f) { Map m = new LinkedHashMap<>(); try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) continue; int eq = line.indexOf('='); if (eq > 0) m.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); } br.close(); } catch (Exception ignored) {} return m; } - private void writeLines(File f, List lines) { try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); for (String l : lines) pw.println(l); pw.flush(); pw.close(); } catch (Exception ignored) {} } - private void sendRandomPreview(Player p) { String[] n = {"钻石剑","钻石镐","铁剑","金苹果"}; String[] m = {"DIAMOND_SWORD","DIAMOND_PICKAXE","IRON_SWORD","GOLDEN_APPLE"}; Random r = new Random(); List items = new ArrayList<>(); for (int i = 0; i < 2 + r.nextInt(3); i++) { int idx = r.nextInt(n.length); items.add(new OrderItem(n[idx], m[idx], 100 + r.nextInt(5000), 100 + r.nextInt(5000), 1 + r.nextInt(10))); } OrderRecord o = new OrderRecord(); o.orderId = System.currentTimeMillis(); o.uuid = p.getUniqueId(); o.player = p.getName(); o.items = items; o.totalOriginal = 0; for (OrderItem it : items) o.totalOriginal += it.originalSubtotal(); o.totalPaid = o.totalOriginal; o.timestamp = o.orderId; o.status = 0; p.sendMessage("§6§l===== 小票预览(随机) ====="); sendReceiptChat(p, o); p.sendMessage("§6§l===== 预览结束 ====="); } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/PVPManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/PVPManager.java deleted file mode 100644 index 326c0fb..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/PVPManager.java +++ /dev/null @@ -1,1380 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.NamespacedKey; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.scoreboard.*; - - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class PVPManager implements Listener { - - private final Main plugin; - private Connection pvpDb; - - private final Map - regions = new ConcurrentHashMap<>(); - private final Map - inRegion = new ConcurrentHashMap<>(); - private final Map - sessionKills = - new ConcurrentHashMap<>(); - private final Map - lastKillTime = - new ConcurrentHashMap<>(); - private final Map - selectState = - new ConcurrentHashMap<>(); - private final Map - killSessions = - new ConcurrentHashMap<>(); - private final Set tripleAnnounced = - ConcurrentHashMap.newKeySet(); - private final Map lastScoreboardRefresh = new ConcurrentHashMap<>(); - - - // 边框显示 - private final Set - permanentBorders = - ConcurrentHashMap.newKeySet(); - private final Set - tempBorders = - ConcurrentHashMap.newKeySet(); - - - public static class KillSession { - public int count = 0; - public long firstKill = 0; - public long lastKill = 0; - public boolean tripleAnnounced = - false; - public String region = ""; - } - - public static class PVPRegion { - public String name; - public String world; - public int x1, y1, z1; - public int x2, y2, z2; - public int minX, minY, minZ; - public int maxX, maxY, maxZ; - - public void calcBounds() { - minX = Math.min(x1, x2); - minY = Math.min(y1, y2); - minZ = Math.min(z1, z2); - maxX = Math.max(x1, x2); - maxY = Math.max(y1, y2); - maxZ = Math.max(z1, z2); - } - - public boolean contains( - Location loc) { - if (loc == null) return false; - if (!loc.getWorld().getName() - .equals(world)) - return false; - int x = loc.getBlockX(); - int y = loc.getBlockY(); - int z = loc.getBlockZ(); - return x >= minX && x <= maxX - && y >= minY && y <= maxY - && z >= minZ && z <= maxZ; - } - } - - public PVPManager(Main plugin) { - this.plugin = plugin; - initDB(); - loadRegions(); - } - - // ===== 独立数据库 ===== - - private void initDB() { - try { - Class.forName( - "org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), - "pvp.db"); - pvpDb = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = - pvpDb.createStatement(); - st.execute( - "PRAGMA journal_mode=WAL"); - st.execute( - "CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "name TEXT NOT NULL " - + "UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER " - + "DEFAULT 0)"); - st.execute( - "CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT " - + "NOT NULL," - + "region_name TEXT " - + "NOT NULL," - + "kills INTEGER " - + "DEFAULT 0," - + "deaths INTEGER " - + "DEFAULT 0," - + "PRIMARY KEY" - + "(player_name, " - + "region_name))"); - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[PVP] DB init failed: " - + e.getMessage(), e); - } - } - - // ===== DB 操作 ===== - - private void saveRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions" - + " (name, world," - + " x1,y1,z1," - + " x2,y2,z2," - + " create_time)" - + " VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private Map - getRegion(String name) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - private List> - getAllRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - pvpDb.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - private void deleteRegion(String name) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private int getKills(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=?" - + " AND " - + "region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = - ps.executeQuery(); - int v = 0; - if (rs.next()) - v = rs.getInt("kills"); - rs.close(); - ps.close(); - return v; - } catch (SQLException e) { - return 0; - } - } - - private int getDeaths(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=?" - + " AND " - + "region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = - ps.executeQuery(); - int v = 0; - if (rs.next()) - v = rs.getInt("deaths"); - rs.close(); - ps.close(); - return v; - } catch (SQLException e) { - return 0; - } - } - - private void addKill(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT INTO pvp_kills" - + " (player_name," - + " region_name," - + " kills,deaths)" - + " VALUES " - + "(?,?,1,0) " - + "ON CONFLICT" - + "(player_name," - + " region_name)" - + " DO UPDATE " - + "SET kills=" - + "kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private void addDeath(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT INTO pvp_kills" - + " (player_name," - + " region_name," - + " kills,deaths)" - + " VALUES " - + "(?,?,0,1) " - + "ON CONFLICT" - + "(player_name," - + " region_name)" - + " DO UPDATE " - + "SET deaths=" - + "deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private List> - getTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT player_name," - + " kills,deaths" - + " FROM " - + "pvp_kills " - + "WHERE " - + "region_name=?" - + " AND kills>0" - + " ORDER BY kills" - + " DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = - ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString( - "player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Set getRegionNames() { - return regions.keySet(); - } - - // ===== 加载 ===== - - private void loadRegions() { - regions.clear(); - List> list = - getAllRegions(); - for (Map row : list) { - PVPRegion r = new PVPRegion(); - r.name = (String) row - .get("name"); - r.world = (String) row - .get("world"); - r.x1 = ((Number) row - .get("x1")).intValue(); - r.y1 = ((Number) row - .get("y1")).intValue(); - r.z1 = ((Number) row - .get("z1")).intValue(); - r.x2 = ((Number) row - .get("x2")).intValue(); - r.y2 = ((Number) row - .get("y2")).intValue(); - r.z2 = ((Number) row - .get("z2")).intValue(); - r.calcBounds(); - regions.put(r.name, r); - } - } - - // ===== 工具 ===== - - public ItemStack createTool() { - ItemStack tool = new ItemStack( - Material.BLAZE_ROD); - ItemMeta m = tool.getItemMeta(); - m.setDisplayName( - "§6§l[PVP] 区域选择工具"); - m.setLore(Arrays.asList( - "§7右键:设置第一个角", - "§7左键:设置第二个角")); - m.getPersistentDataContainer() - .set(new NamespacedKey( - plugin, "pvp_tool"), - PersistentDataType.STRING, - "tool"); - tool.setItemMeta(m); - return tool; - } - - private boolean isPVPTool( - ItemStack item) { - if (item == null) return false; - ItemMeta m = item.getItemMeta(); - if (m == null) return false; - return m.getPersistentDataContainer() - .has(new NamespacedKey( - plugin, "pvp_tool"), - PersistentDataType.STRING); - } - - public void giveTool(Player p) { - boolean has = false; - for (ItemStack it : p.getInventory() - .getContents()) { - if (isPVPTool(it)) { - has = true; - break; - } - } - if (!has) { - p.getInventory() - .addItem(createTool()); - } - } - - // ===== 边框粒子 ===== - - private void drawBorder(Player target, - PVPRegion r) { - if (r == null) return; - World w = Bukkit.getWorld(r.world); - if (w == null) return; - int step = 2; - drawLine(target, w, - r.minX, r.minY, r.minZ, - r.maxX, r.minY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.minZ, - r.maxX, r.minY, r.maxZ, step); - drawLine(target, w, - r.maxX, r.minY, r.maxZ, - r.minX, r.minY, r.maxZ, step); - drawLine(target, w, - r.minX, r.minY, r.maxZ, - r.minX, r.minY, r.minZ, step); - drawLine(target, w, - r.minX, r.maxY, r.minZ, - r.maxX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.maxY, r.minZ, - r.maxX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.maxX, r.maxY, r.maxZ, - r.minX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.minX, r.maxY, r.maxZ, - r.minX, r.maxY, r.minZ, step); - drawLine(target, w, - r.minX, r.minY, r.minZ, - r.minX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.minZ, - r.maxX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.maxZ, - r.maxX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.minX, r.minY, r.maxZ, - r.minX, r.maxY, r.maxZ, step); - } - - private void drawLine(Player target, - World w, - int x1, int y1, int z1, - int x2, int y2, int z2, - int step) { - double dx = x2 - x1; - double dy = y2 - y1; - double dz = z2 - z1; - double dist = Math.sqrt( - dx * dx + dy * dy + dz * dz); - int steps = (int) (dist / step); - if (steps < 1) steps = 1; - for (int i = 0; i <= steps; i++) { - double t = (double) i / steps; - Location loc = new Location(w, - x1 + dx * t, - y1 + dy * t + 0.1, - z1 + dz * t); - w.spawnParticle( - Particle.FLAME, loc, 1, - 0.0, 0.0, 0.0, 0.0); - } - } - - // ===== 每秒 tick ===== - - public void tickRegions() { - long now = - System.currentTimeMillis(); - - // 玩家区域检测 - for (Player p : - Bukkit.getOnlinePlayers()) { - String pn = p.getName(); - Location loc = p.getLocation(); - String inside = null; - for (PVPRegion r : - regions.values()) { - if (r.contains(loc)) { - inside = r.name; - break; - } - } - String was = inRegion.get(pn); - - if (inside != null && !inside.equals(was)) { - inRegion.put(pn, inside); - if (was == null) { - String reg = inside; - final Player pp = p; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> - updateScoreboard( - pp, reg), - 1L); - } - - } else if (inside == null - && was != null) { - inRegion.remove(pn); - // 不删 sessionKills,等倒计时结束再清 - removeScoreboard(p); - final Player pp = p; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> plugin - .giveMenuSnowball(pp), - 5L); - } - - } - - // 5分钟超时重置连杀session - for (String pname : - new ArrayList<>( - killSessions - .keySet())) { - KillSession ks = - killSessions.get(pname); - if (ks == null) continue; - if (now - ks.lastKill - > 5 * 60 * 1000) { - killSessions.remove(pname); - } - } - - // 5分钟无击杀 → 计数榜切总榜 - // 5分钟无击杀 → 归档并清零 - for (String rName : - new ArrayList<>( - lastKillTime - .keySet())) { - long last = lastKillTime - .getOrDefault(rName, 0L); - if (now - last > 5 * 60 * 1000) { - // 清零该区域所有sessionKills - sessionKills.keySet() - .removeIf(k -> k - .startsWith( - rName + ":")); - // 切换到总榜 - refreshScoreboard(rName); - lastKillTime.remove(rName); - lastScoreboardRefresh.remove(rName); - - } - } - - - // 边框渲染 - for (Player p : - Bukkit.getOnlinePlayers()) { - String pr = - inRegion.get(p.getName()); - if (pr == null) continue; - PVPRegion r = regions.get(pr); - if (r == null) continue; - if (permanentBorders - .contains(pr)) { - drawBorder(p, r); - } - if (tempBorders - .contains(pr)) { - drawBorder(p, r); - } - // 每秒刷新活跃区域的计分板倒计时 - for (String rName : new ArrayList<>(lastKillTime.keySet())) { - long last = lastScoreboardRefresh.getOrDefault(rName, 0L); - if (now - last >= 1000) { - lastScoreboardRefresh.put(rName, now); - for (Player tp : Bukkit.getOnlinePlayers()) { - String inR = inRegion.get(tp.getName()); - if (rName.equals(inR)) { - showCounter(tp, rName); - } - } - } - } - - - - } - } - - private void broadcastWithSound(String msg, Sound sound) { - Bukkit.broadcastMessage(msg); - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), sound, 1.0f, 1.0f); - } - } - - - private void initSession(String player, - String region) { - killSessions.remove(player); - tripleAnnounced.remove( - region + ":" + player); - KillSession s = new KillSession(); - s.region = region; - s.firstKill = - System.currentTimeMillis(); - s.lastKill = - System.currentTimeMillis(); - killSessions.put(player, s); - } - - // ===== 击杀事件 ===== - - @EventHandler - public void onDeath( - EntityDeathEvent event) { - if (!(event.getEntity() - instanceof Player)) return; - Player victim = - (Player) event.getEntity(); - Player killer = - victim.getKiller(); - if (killer == null) return; - if (killer.equals(victim)) return; - - String region = - inRegion.get( - victim.getName()); - if (region == null) return; - - String vName = victim.getName(); - String kName = killer.getName(); - - // 总榜 - addKill(kName, region); - addDeath(vName, region); - - // 计数榜 - String kKey = - region + ":" + kName; - sessionKills.merge(kKey, 1, - Integer::sum); - lastKillTime.put(region, - System.currentTimeMillis()); - - // 终结检测 - // 终结检测 - KillSession victimSession = - killSessions.get(vName); - if (victimSession != null - && victimSession.count >= 3) { - String endMsg = - "§b§l" + kName - + " §e终结了 " - + vName + " §e的 " - + victimSession.count - + "连杀!§a恭喜终结者 " - + kName - + "§e!§7快来挑战!"; - killSessions.remove(vName); - broadcastWithSound(endMsg, Sound.ENTITY_ENDER_DRAGON_GROWL); - - killSessions.remove(vName); - } - - - // 击杀者连杀 - KillSession ks = - killSessions.get(kName); - if (ks == null - || !ks.region.equals(region) - || System.currentTimeMillis() - - ks.lastKill > 5 * 60 * 1000) { - tripleAnnounced.remove( - region + ":" + kName); - KillSession ns = - new KillSession(); - ns.region = region; - ns.count = 1; - ns.firstKill = - System.currentTimeMillis(); - ns.lastKill = - System.currentTimeMillis(); - killSessions.put(kName, ns); - } else { - ks.count++; - ks.lastKill = - System.currentTimeMillis(); - } - - // 多杀通报 - KillSession killerSession = - killSessions.get(kName); - if (killerSession != null) { - plugin.getLogger().info( - "[PVP] checkMultiKill: " - + kName + " count=" - + killerSession.count - + " region=" + region); - checkMultiKill(killer, - region, killerSession); - } - - // 刷新计分板 - for (Player p : - Bukkit.getOnlinePlayers()) { - if (region.equals( - inRegion.get( - p.getName()))) { - updateScoreboard(p, region); - } - } - - victim.sendMessage("§c你在「" - + region + "」被 " - + kName + " 击杀"); - killer.sendMessage("§a你在「" - + region + "」击杀了 " - + vName); - } - - private void checkMultiKill( - Player killer, String region, - KillSession session) { - int count = session.count; - String kName = killer.getName(); - String key = region + ":" + kName; - String msg = null; - - if (count >= 5) { - msg = "§c§l" + kName - + " §e在「" + region - + "」达成 " + count - + "连杀!§7§l快来挑战!"; - } else if (count == 3) { - if (tripleAnnounced.add(key)) { - msg = "§6§l三杀!" - + kName - + " 势不可挡!" - + "§7快来挑战!"; - } - } - - if (msg != null) { - if (count >= 5) { - broadcastWithSound(msg, Sound.ENTITY_ENDER_DRAGON_GROWL); - } else { - broadcastWithSound(msg, Sound.BLOCK_END_PORTAL_SPAWN); - } - } - - } - - - - // ===== 计分板(英文ID) ===== - - private void ensureScoreboard( - Player p, String region) { - // 活跃击杀:每秒强制刷新(更新倒计时) - if (lastKillTime.containsKey(region)) { - showCounter(p, region); - return; - } - // 无活跃击杀:只在没有scoreboard时创建总榜 - Scoreboard sb = p.getScoreboard(); - if (sb == null - || sb.getObjective( - "sdf1pvp") == null) { - showTotal(p, region); - } - } - - - private void updateScoreboard( - Player p, String region) { - if (lastKillTime - .containsKey(region)) { - showCounter(p, region); - } else { - showTotal(p, region); - } - } - - private void showTotal(Player p, - String region) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - Scoreboard sb = - mgr.getNewScoreboard(); - Objective obj = - sb.registerNewObjective( - "sdf1pvp", - Criteria.DUMMY, - "§6§l击杀总榜 [" - + region + "]"); - obj.setDisplaySlot( - DisplaySlot.SIDEBAR); - - List> top = - getTop(region, 10); - if (top.isEmpty()) { - obj.getScore("§7暂无记录") - .setScore(0); - } else { - int rank = 10; - for (Map row : - top) { - String name = (String) row - .get("player_name"); - int kills = ((Number) row - .get("kills")) - .intValue(); - int deaths = ((Number) row - .get("deaths")) - .intValue(); - String line = - "§e#" + (11 - rank) - + " §f" + name - + " §a" + kills - + "§7/§c" + deaths; - obj.getScore(line) - .setScore(rank); - rank--; - } - } - p.setScoreboard(sb); - } - private void showCounter(Player p, - String region) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - Scoreboard sb = mgr.getNewScoreboard(); - Objective obj = - sb.registerNewObjective( - "sdf1pvp", - Criteria.DUMMY, - "§c§l实时战况 [" - + region + "]"); - obj.setDisplaySlot( - DisplaySlot.SIDEBAR); - - List entries = - new ArrayList<>(); - for (Map.Entry e : - sessionKills.entrySet()) { - if (!e.getKey().startsWith( - region + ":")) - continue; - if (e.getValue() <= 0) continue; - String pName = e.getKey() - .substring( - region.length() + 1); - entries.add(new String[]{ - pName, - String.valueOf( - e.getValue())}); - } - - if (entries.isEmpty()) { - obj.getScore("§7等待击杀...") - .setScore(1); - } else { - entries.sort((a, b) -> - Integer.compare( - Integer.parseInt(b[1]), - Integer.parseInt(a[1]))); - int rank = 10; - for (String[] entry : entries) { - obj.getScore( - "§e" + entry[0] - + " §a" + entry[1]) - .setScore(rank); - rank--; - if (rank < 1) break; - } - } - - Long last = - lastKillTime.get(region); - if (last != null) { - long elapsed = - System.currentTimeMillis() - - last; - long remaining = - Math.max(0, - 5 * 60 * 1000 - elapsed); - long secs = remaining / 1000; - obj.getScore( - "§7冷却: " + secs + "s") - .setScore(-1); - } - - // 关键:强制赋值新scoreboard - p.setScoreboard(sb); - } - - private void removeScoreboard( - Player p) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - p.setScoreboard( - mgr.getNewScoreboard()); - } - - private void refreshScoreboard( - String regionName) { - for (Player p : - Bukkit.getOnlinePlayers()) { - String pr = - inRegion.get(p.getName()); - if (regionName.equals(pr)) { - showTotal(p, regionName); - } - } - } - - // ===== 工具选择事件 ===== - - @EventHandler - public void onInteract( - PlayerInteractEvent event) { - Player p = event.getPlayer(); - ItemStack hand = - p.getInventory() - .getItemInMainHand(); - if (!isPVPTool(hand)) return; - - if (!event.getAction() - .isRightClick() - && !event.getAction() - .isLeftClick()) return; - - Location loc = p.getLocation(); - String[] state = - selectState.get(p.getName()); - if (state == null) { - state = new String[]{ - loc.getWorld() - .getName(), - "", ""}; - selectState.put( - p.getName(), state); - } - - String coord = - loc.getBlockX() + "," - + loc.getBlockY() + "," - + loc.getBlockZ(); - - if (event.getAction() - .isRightClick()) { - state[0] = loc.getWorld() - .getName(); - state[1] = coord; - p.sendMessage( - "§a第一个角: " + coord); - } else { - state[2] = coord; - p.sendMessage( - "§a第二个角: " + coord); - } - event.setCancelled(true); - } - - // ===== 退出清理 ===== - - @EventHandler - public void onQuit( - PlayerQuitEvent event) { - String name = - event.getPlayer().getName(); - inRegion.remove(name); - selectState.remove(name); - } - - // ===== 指令 ===== - - public boolean onCommand(Player p, - String[] args) { - if (args.length == 0) { - return showStats(p, - p.getName()); - } - switch (args[0].toLowerCase()) { - case "create": - return handleCreate( - p, args); - case "stats": - if (args.length >= 2) - return showStats(p, - args[1]); - return showStats(p, - p.getName()); - case "list": - return handleList(p); - case "delete": - if (args.length >= 2) - return handleDelete(p, - args[1]); - p.sendMessage( - "§c用法: " - + "/pvp delete <名字>"); - return true; - case "tool": - giveTool(p); - p.sendMessage( - "§a已发放" - + "PVP区域选择工具"); - return true; - case "on": - return handleBorderOn( - p, args); - case "off": - return handleBorderOff( - p, args); - case "tempban": - return handleTempBan( - p, args); - default: - return showStats(p, - p.getName()); - } - } - - private boolean handleBorderOn( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp on <区域名>"); - return true; - } - if (!regions - .containsKey(name)) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - permanentBorders.add(name); - p.sendMessage("§a已开启「" - + name + "」永久边框显示"); - return true; - } - - private boolean handleBorderOff( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp off <区域名>"); - return true; - } - permanentBorders.remove(name); - tempBorders.remove(name); - p.sendMessage("§a已关闭「" - + name + "」边框显示"); - return true; - } - - private boolean handleTempBan( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp tempban <区域名>"); - return true; - } - if (!regions - .containsKey(name)) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - tempBorders.add(name); - p.sendMessage("§a「" + name - + "」临时边框显示15秒"); - final String rName = name; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> - tempBorders - .remove(rName), - 300L); - return true; - } - - private boolean handleCreate( - Player p, String[] args) { - if (args.length >= 8) { - try { - int x1 = Integer.parseInt( - args[1]); - int y1 = Integer.parseInt( - args[2]); - int z1 = Integer.parseInt( - args[3]); - int x2 = Integer.parseInt( - args[4]); - int y2 = Integer.parseInt( - args[5]); - int z2 = Integer.parseInt( - args[6]); - String rName = args[7]; - return createRegion(p, - rName, - p.getWorld() - .getName(), - x1, y1, z1, - x2, y2, z2); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c坐标格式错误"); - return true; - } - } - - if (args.length >= 2) { - String[] state = - selectState - .get(p.getName()); - if (state == null - || state[1].isEmpty() - || state[2].isEmpty()) { - p.sendMessage( - "§c请先用工具选择两个角"); - return true; - } - String[] p1 = - state[1].split(","); - String[] p2 = - state[2].split(","); - return createRegion(p, - args[1], state[0], - Integer.parseInt(p1[0]), - Integer.parseInt(p1[1]), - Integer.parseInt(p1[2]), - Integer.parseInt(p2[0]), - Integer.parseInt(p2[1]), - Integer.parseInt(p2[2])); - } - - p.sendMessage( - "§c用法: /pvp create " - + " " - + " <名字>"); - p.sendMessage( - "§c或: /pvp create " - + "<名字> (先用工具选择)"); - return true; - } - - private boolean createRegion( - Player p, String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - if (getRegion(name) != null) { - p.sendMessage( - "§c区域已存在: " + name); - return true; - } - PVPRegion r = new PVPRegion(); - r.name = name; - r.world = world; - r.x1 = x1; - r.y1 = y1; - r.z1 = z1; - r.x2 = x2; - r.y2 = y2; - r.z2 = z2; - r.calcBounds(); - saveRegion(name, world, - x1, y1, z1, - x2, y2, z2); - regions.put(name, r); - p.sendMessage( - "§a§lPVP区域创建成功!"); - p.sendMessage( - "§7名称: §f" + name); - p.sendMessage("§7范围: §f" - + r.minX + "," + r.minY - + "," + r.minZ + " → " - + r.maxX + "," + r.maxY - + "," + r.maxZ); - selectState.remove(p.getName()); - return true; - } - - private boolean handleList( - Player p) { - List> all = - getAllRegions(); - if (all.isEmpty()) { - p.sendMessage( - "§7暂无PVP区域"); - return true; - } - p.sendMessage( - "§6§lPVP区域列表:"); - for (Map row : - all) { - p.sendMessage("§e- §f" - + row.get("name") - + " §7(" + row.get("world") - + ")"); - } - return true; - } - - private boolean handleDelete( - Player p, String name) { - if (getRegion(name) == null) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - deleteRegion(name); - regions.remove(name); - permanentBorders.remove(name); - tempBorders.remove(name); - sessionKills.keySet() - .removeIf(k -> k - .startsWith( - name + ":")); - lastKillTime.remove(name); - lastScoreboardRefresh.remove(name); - p.sendMessage( - "§a已删除: " + name); - return true; - } - - private boolean showStats(Player p, - String target) { - String region = - inRegion.get(target); - if (region == null) { - p.sendMessage("§6§l" + target - + " 的PVP战绩:"); - List> all = - getAllRegions(); - boolean has = false; - for (Map row : - all) { - String rn = (String) row - .get("name"); - int kills = - getKills(target, rn); - int deaths = - getDeaths(target, rn); - if (kills > 0 - || deaths > 0) { - p.sendMessage( - "§e- §f" + rn - + " §a击杀:" + kills - + " §c死亡:" + deaths); - has = true; - } - } - if (!has) - p.sendMessage( - "§7暂无记录"); - } else { - int kills = - getKills(target, region); - int deaths = - getDeaths(target, region); - double kd = deaths > 0 - ? (double) kills / deaths - : kills; - p.sendMessage("§6§l" + target - + " @ " + region); - p.sendMessage( - "§a击杀: " + kills - + " §c死亡: " + deaths - + " §eKD: " - + String.format( - "%.2f", kd)); - } - return true; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/PasswordUtils.java b/backup/20260605_152529/src/main/java/Sdf1_login/PasswordUtils.java deleted file mode 100644 index 4029ed4..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/PasswordUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -package Sdf1_login; - -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; - -public class PasswordUtils { - - private static final SecureRandom RANDOM = - new SecureRandom(); - - public static String generateSalt() { - byte[] salt = new byte[16]; - RANDOM.nextBytes(salt); - return Base64.getEncoder().encodeToString(salt); - } - - public static String hash(String password, String salt) { - try { - MessageDigest md = - MessageDigest.getInstance("SHA-256"); - md.update(Base64.getDecoder().decode(salt)); - byte[] hash = - md.digest(password.getBytes("UTF-8")); - return Base64.getEncoder().encodeToString(hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static boolean validate(String password) { - if (password == null) return false; - if (password.length() < 6 || password.length() > 20) - return false; - boolean hasUpper = false; - boolean hasLower = false; - boolean hasDigit = false; - for (char c : password.toCharArray()) { - if (Character.isUpperCase(c)) hasUpper = true; - if (Character.isLowerCase(c)) hasLower = true; - if (Character.isDigit(c)) hasDigit = true; - } - return hasUpper && hasLower && hasDigit; - } - - public static String generateTempPassword() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(8); - for (int i = 0; i < 8; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateInviteCode() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - StringBuilder sb = new StringBuilder(6); - for (int i = 0; i < 6; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateVerifyCode() { - int code = 100000 + RANDOM.nextInt(900000); - return String.valueOf(code); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/PlayerStorage.java b/backup/20260605_152529/src/main/java/Sdf1_login/PlayerStorage.java deleted file mode 100644 index f93caa3..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/PlayerStorage.java +++ /dev/null @@ -1,130 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.logging.Logger; - -public class PlayerStorage { - private Connection conn; - private final Logger logger; - - public PlayerStorage(File dataFolder, - Logger logger) { - this.logger = logger; - File dbFile = new File( - dataFolder, - "quest_data.db"); - try { - Class.forName( - "org.sqlite.JDBC"); - conn = DriverManager - .getConnection( - "jdbc:sqlite:" - + dbFile - .getAbsolutePath()); - conn.setAutoCommit(true); - Statement s = - conn.createStatement(); - s.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_kv (" - + "player TEXT NOT NULL," - + "key TEXT NOT NULL," - + "value TEXT," - + "PRIMARY KEY" - + "(player, key))"); - s.close(); - logger.info( - "[QuestStorage] 初始化: " - + dbFile.getAbsolutePath()); - } catch (Exception e) { - logger.severe( - "[QuestStorage] 失败: " - + e.getMessage()); - } - } - - public Object getCondition( - String player, String key) { - if (conn == null) return null; - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value " - + "FROM quest_kv " - + "WHERE player=? " - + "AND key=?"); - ps.setString(1, - player.toLowerCase()); - ps.setString(2, key); - ResultSet rs = - ps.executeQuery(); - String val = rs.next() - ? rs.getString("value") - : null; - rs.close(); - ps.close(); - return parseValue(val); - } catch (SQLException e) { - return null; - } - } - - public void setCondition( - String player, String key) { - setConditionRaw( - player, key, "true"); - } - public void debugPlayer(String player) { - - } - public void setConditionRaw( - String player, String key, - String value) { - if (conn == null) return; - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO quest_kv" - + "(player,key,value)" - + " VALUES(?,?,?)"); - ps.setString(1, - player.toLowerCase()); - ps.setString(2, key); - ps.setString(3, value); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - logger.warning( - "[QuestStorage] 写入失败: " - + key + "=" + value - + " | " + e.getMessage()); - } - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) - conn.close(); - } catch (Exception e) {} - } - - private Object parseValue(String v) { - if (v == null) return null; - try { - return Long.parseLong(v); - } catch (Exception e1) { - try { - return Double.parseDouble(v); - } catch (Exception e2) { - if ("true".equalsIgnoreCase(v)) - return true; - if ("false".equalsIgnoreCase(v)) - return false; - return v; - } - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/PointsManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/PointsManager.java deleted file mode 100644 index 77fc195..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/PointsManager.java +++ /dev/null @@ -1,374 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - - -import java.lang.reflect.Method; -import java.util.*; - -public class PointsManager { - - private final Main plugin; - private Object cyInstance = null; - private Method cyActivationMethod = null; - private Method cyGetShopItemsMethod = null; - - private final List> cyItems = - new ArrayList<>(); - - public PointsManager(Main plugin) { - this.plugin = plugin; - discoverCY(); - } - - private void discoverCY() { - try { - org.bukkit.plugin.Plugin p = Bukkit - .getPluginManager() - .getPlugin("CY_beibao"); - if (p != null && p.isEnabled()) { - cyInstance = p; - cyGetShopItemsMethod = p.getClass() - .getMethod("onSdf1GetShopItems"); - cyActivationMethod = p.getClass() - .getMethod("onSdf1Activation", - String.class, - int.class, int.class); - plugin.getLogger().info( - "[Sdf1_login] CY_beibao 联控已连接"); - } - } catch (Exception ignored) { - } - } - - private void refreshCYItems() { - cyItems.clear(); - if (cyInstance == null - || cyGetShopItemsMethod == null) return; - try { - Object result = - cyGetShopItemsMethod.invoke(cyInstance); - if (result == null) return; - String data = result.toString(); - if (data.isEmpty()) return; - String[] items = data.split(";"); - for (String itemStr : items) { - String[] p = itemStr.split("\\|"); - if (p.length < 8) continue; - Map item = - new LinkedHashMap<>(); - item.put("id", p[0]); - item.put("name", p[1]); - item.put("days", - Integer.parseInt(p[2])); - item.put("slots", - Integer.parseInt(p[3])); - item.put("price", - Integer.parseInt(p[4])); - item.put("stock", - Integer.parseInt(p[5])); - item.put("icon", p[6]); - item.put("lifetime", - "1".equals(p[7])); - cyItems.add(item); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 拉取CY商品失败: " - + e.getMessage()); - } - } - - private void activateCY(String name, - int slots, int days) { - try { - if (cyInstance == null - || cyActivationMethod == null) return; - cyActivationMethod.invoke( - cyInstance, name, slots, days); - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] CY激活失败: " - + e.getMessage()); - } - } - - public int getPoints(String name) { - return ((Number) plugin.getDb() - .getField(name, "points")).intValue(); - } - - public Inventory createShopGUI(Player p) { - int points = getPoints(p.getName()); - Inventory inv = Bukkit.createInventory( - null, 54, - "§d§l积分商城 | 当前积分: " + points); - fillBg(inv); - inv.setItem(10, mkItem(Material.EMERALD, - "§a游戏币充值", - "§7消耗积分兑换游戏币", - "§7100积分 = $1000", - "", - "§e点击兑换")); - inv.setItem(12, mkItem(Material.ENDER_CHEST, - "§b背包空间", - "§7消耗积分兑换背包格子", - "§750积分 = 9格永久空间", - "", - "§e点击兑换")); - inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE, - "§e经验瓶", - "§7消耗积分兑换经验", - "§720积分 = 10级经验", - "", - "§e点击兑换")); - if (cyInstance != null) { - inv.setItem(31, mkItem(Material.CHEST, - "§6背包商城", - "§7来自CY_beibao的商品", - "", - "§e点击查看")); - } - inv.setItem(49, mkItem(Material.ARROW, "§7返回")); - return inv; - } - - public Inventory createCYShopGUI(Player p) { - refreshCYItems(); - int points = getPoints(p.getName()); - int size = Math.max(54, - ((cyItems.size() / 9) + 1) * 9); - if (size > 54) size = 54; - Inventory inv = Bukkit.createInventory( - null, size, - "§d§lCY背包商城 | 当前积分: " + points); - fillBg(inv); - for (int i = 0; i < cyItems.size() - && i < 45; i++) { - Map item = cyItems.get(i); - String name = (String) item.get("name"); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - List lore = new ArrayList<>(); - lore.add("§7价格: §e" + price + " 积分"); - if (lifetime) { - lore.add("§7格子: §e终身 +" + slots); - } else { - lore.add("§7格子: §e+" + slots); - lore.add("§7时间: §e" + days + "天"); - } - if (stock == 0) { - lore.add("§7库存: §a无限"); - } else if (stock == -1) { - lore.add("§7库存: §c已下架"); - } else { - lore.add("§7库存: §e" + stock); - } - lore.add(""); - if (points >= price && stock != -1) { - lore.add("§e点击购买"); - } else if (stock == -1) { - lore.add("§c已下架"); - } else { - lore.add("§c积分不足"); - } - inv.setItem(i, mkItem(Material.CHEST, - "§a" + name, - lore.toArray(new String[0]))); - } - inv.setItem(size - 1, - mkItem(Material.ARROW, "§7返回")); - return inv; - } - - public void addItem(String name, - int price, String cmd2) { - // 追加到商品配置文件 - File file = new File( - plugin.getDataFolder(), - "ShopItems.txt"); - try { - java.io.FileWriter fw = - new java.io.FileWriter(file, true); - fw.write(name + ":" + price - + ":" + cmd2 + "\n"); - fw.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void removeItem(int index) { - File file = new File( - plugin.getDataFolder(), - "ShopItems.txt"); - try { - java.util.List lines = - new java.util.ArrayList<>(); - java.io.BufferedReader br = - new java.io.BufferedReader( - new java.io.FileReader( - file)); - String line; - int i = 0; - while ((line = br.readLine()) != null) { - if (line.trim().isEmpty() - || line.startsWith("#")) { - lines.add(line); - continue; - } - if (i != index) { - lines.add(line); - } - i++; - } - br.close(); - java.io.PrintWriter pw = - new java.io.PrintWriter(file); - for (String l : lines) pw.println(l); - pw.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - public void handleClick(Player p, int slot) { - int points = getPoints(p.getName()); - switch (slot) { - case 10: - if (points >= 100) { - plugin.getDb().deductPoints( - p.getName(), 100); - if (plugin.getEconomy() != null) { - plugin.getEconomy() - .depositPlayer(p, 1000); - } - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "100")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 12: - if (points >= 50) { - plugin.getDb().deductPoints( - p.getName(), 50); - activateCY(p.getName(), 9, 0); - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "50")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 14: - if (points >= 20) { - plugin.getDb().deductPoints( - p.getName(), 20); - p.setLevel(p.getLevel() + 10); - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "20")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 31: - if (cyInstance != null) { - p.openInventory( - createCYShopGUI(p)); - } - break; - } - } - - public void handleCYClick(Player p, int slot) { - if (slot < 0 || slot >= cyItems.size() - || slot >= 45) return; - Map item = cyItems.get(slot); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - if (stock == -1) { - p.sendMessage("§c该商品已下架"); - return; - } - int points = getPoints(p.getName()); - if (points < price) { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - return; - } - - plugin.getDb().deductPoints(p.getName(), price); - int actDays = lifetime ? 0 : days; - activateCY(p.getName(), slots, actDays); - - String tl = lifetime - ? "终身" : (days + "天"); - p.sendMessage("§a购买成功!获得 " - + tl + " +" + slots + "格 背包空间"); - } - - private void fillBg(Inventory inv) { - ItemStack gl = mkItem( - Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < inv.getSize(); i++) - inv.setItem(i, gl); - } - - private ItemStack mkItem(Material mat, String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/QuestStorage.java b/backup/20260605_152529/src/main/java/Sdf1_login/QuestStorage.java deleted file mode 100644 index 45dbabf..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/QuestStorage.java +++ /dev/null @@ -1,138 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -public class QuestStorage { - - private Connection conn; - - public QuestStorage(File dataFolder) { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - dataFolder, - "quest_data.db"); - conn = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getPath()); - initTable(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void initTable() - throws SQLException { - Statement stmt = - conn.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_conditions (" - + "player TEXT NOT NULL, " - + "condition_key TEXT NOT NULL, " - + "value TEXT DEFAULT '1', " - + "timestamp INTEGER, " - + "PRIMARY KEY " - + "(player, condition_key))"); - stmt.close(); - } - - /** - * 标记条件已完成(value = "1") - */ - public void setCondition( - String player, - String condition) { - setConditionRaw( - player, condition, "1"); - } - - /** - * 写入任意值(计数器等) - */ - public void setConditionRaw( - String player, - String condition, - String value) { - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO " - + "quest_conditions " - + "(player, " - + "condition_key, " - + "value, timestamp) " - + "VALUES " - + "(?, ?, ?, ?)"); - ps.setString(1, player); - ps.setString(2, condition); - ps.setString(3, value); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 查询条件值 - */ - public Object getCondition( - String player, - String condition) { - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value FROM " - + "quest_conditions " - + "WHERE player = ? " - + "AND " - + "condition_key = ?"); - ps.setString(1, player); - ps.setString(2, condition); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - String val = - rs.getString("value"); - rs.close(); - ps.close(); - // 尝试解析为数字 - try { - if (val.contains(".")) { - return Double.parseDouble( - val); - } - return Long.parseLong(val); - } catch (NumberFormatException e) { - return val; - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) { - conn.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/QuestTracker.java b/backup/20260605_152529/src/main/java/Sdf1_login/QuestTracker.java deleted file mode 100644 index f2eda67..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/QuestTracker.java +++ /dev/null @@ -1,3662 +0,0 @@ -package Sdf1_login; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.generator.structure.Structure; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -// 如果 Bonds 在 Sdf1_login 包下: -import Sdf1_login.BondManager; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - - -public class QuestTracker { - - private final Plugin plugin; - private final QuestStorage storage; - private final Map - playerBiome = new HashMap<>(); - private final Map> - questCache = new HashMap<>(); - private final List - checkInRecords = new ArrayList<>(); - private final Map - lastCheckInPos = new HashMap<>(); - private static boolean loggedStructureRegistry - = false; - - public void recordCheckIn( - String player, - String biomeKey, - String category, - String worldName, - double x, double y, double z) { - String record = player - + "|" + worldName - + "|" + biomeKey - + "|" + category - + "|" + String.format( - "%.1f", x) - + "|" + String.format( - "%.1f", y) - + "|" + String.format( - "%.1f", z) - + "|" + System.currentTimeMillis(); - checkInRecords.add(record); - plugin.getLogger().info( - "[QuestTracker] 打卡记录: " - + record); - } - - public List getCheckInRecords() { - return new ArrayList<>(checkInRecords); - } - - public List getPlayerRecords( - String player) { - List list = new ArrayList<>(); - for (String r : checkInRecords) { - if (r.startsWith( - player.toLowerCase() - + "|")) { - list.add(r); - } - } - return list; - } - - - public QuestTracker(Plugin plugin) { - this.plugin = plugin; - this.storage = new QuestStorage( - plugin.getDataFolder()); - - loadAllQuests(); - } - - public void debugPlayer(String player) { - plugin.getLogger().info( - "[QuestTracker] === " - + player + " 任务数据 ==="); - } - public void shutdown() { - if (storage != null) { - storage.close(); - } - } - - /** - * 玩家移动时记录当前群系 - */ - public void onPlayerMove( - String player, - String biomeName) { - playerBiome.put(player, biomeName); - } - /** - * 标记玩家已打卡该群系 - */ - // ========== 群系分类映射表 ========== - private static final Map - BIOME_CATEGORY = new HashMap<>(); - static { - // 海洋群系 - BIOME_CATEGORY.put("minecraft:ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋"); - // 平原群系 - BIOME_CATEGORY.put("minecraft:plains", "平原"); - BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原"); - BIOME_CATEGORY.put("minecraft:meadow", "平原"); - // 森林群系 - BIOME_CATEGORY.put("minecraft:forest", "森林"); - BIOME_CATEGORY.put("minecraft:flower_forest", "森林"); - BIOME_CATEGORY.put("minecraft:birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:dark_forest", "森林"); - BIOME_CATEGORY.put("minecraft:taiga", "森林"); - BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:cherry_grove", "森林"); - BIOME_CATEGORY.put("minecraft:jungle", "森林"); - BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林"); - BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林"); - // 沙漠群系 - BIOME_CATEGORY.put("minecraft:desert", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠"); - BIOME_CATEGORY.put("minecraft:beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠"); - // 恶地群系 - BIOME_CATEGORY.put("minecraft:badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地"); - // 雪原群系 - BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原"); - BIOME_CATEGORY.put("minecraft:grove", "雪原"); - BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原"); - BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_river", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原"); - // 沙砾群系 - BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾"); - // 沼泽群系 - BIOME_CATEGORY.put("minecraft:swamp", "沼泽"); - BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽"); - // 河流 - BIOME_CATEGORY.put("minecraft:river", "河流"); - // 蘑菇岛 - BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛"); - // 地下群系 - BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下"); - BIOME_CATEGORY.put("minecraft:lush_caves", "地下"); - BIOME_CATEGORY.put("minecraft:deep_dark", "地下"); - // 下界群系 - BIOME_CATEGORY.put("minecraft:nether_wastes", "下界"); - BIOME_CATEGORY.put("minecraft:crimson_forest", "下界"); - BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); - BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); - BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); - // 岩岸/沙滩 - BIOME_CATEGORY.put("minecraft:stony_shore", - "石岸"); -// 海岸 - BIOME_CATEGORY.put("minecraft:mushroom_fields", - "蘑菇岛"); -// 苍白之园 - BIOME_CATEGORY.put( - "minecraft:pale_garden", "森林"); -// 末地小型岛屿 - BIOME_CATEGORY.put( - "minecraft:small_end_islands", "末地"); -// 主世界结构归类 - BIOME_CATEGORY.put("minecraft:village", "平原"); - BIOME_CATEGORY.put( - "minecraft:desert_pyramid", "沙漠"); - BIOME_CATEGORY.put( - "minecraft:jungle_pyramid", "森林"); - BIOME_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - - // 末地群系 - BIOME_CATEGORY.put("minecraft:the_end", "末地"); - BIOME_CATEGORY.put("minecraft:the_void", "末地"); - BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); - - - } - - // 所有群系大类列表 - private static final String[] - ALL_CATEGORIES = { - "海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "石岸", "蘑菇岛", "地下", - "下界", "末地" - }; - - // ===== 结构映射(PaperMC 1.21.4 实际结构 key)===== - private static final Map - STRUCTURE_CATEGORY = - new HashMap<>(); - static { - // 主世界结构 - STRUCTURE_CATEGORY.put( - "minecraft:village_plains", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_desert", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_savanna", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_snowy", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_taiga", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:desert_pyramid", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:jungle_pyramid", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - STRUCTURE_CATEGORY.put( - "minecraft:igloo", "雪原"); - STRUCTURE_CATEGORY.put( - "minecraft:mansion", "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:pillager_outpost", - "掠夺者前哨站"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft_mesa", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:stronghold", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:ancient_city", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:trail_ruins", - "远古遗迹"); - STRUCTURE_CATEGORY.put( - "minecraft:trial_chambers", - "试炼密室"); - STRUCTURE_CATEGORY.put( - "minecraft:monument", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_cold", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_warm", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck_beached", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:buried_treasure", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_desert", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_jungle", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_mountain", - "山地"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_ocean", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_swamp", - "沼泽"); - // 下界结构 - STRUCTURE_CATEGORY.put( - "minecraft:fortress", "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:bastion_remnant", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:nether_fossil", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_nether", - "下界"); - // 末地结构 - STRUCTURE_CATEGORY.put( - "minecraft:end_city", "末地"); - } - - // 所有群系分类的条件关键词映射 - private static final Map - CATEGORY_KEYWORDS = new HashMap<>(); - static { - CATEGORY_KEYWORDS.put("海洋", "海洋"); - CATEGORY_KEYWORDS.put("平原", "平原"); - CATEGORY_KEYWORDS.put("森林", "森林"); - CATEGORY_KEYWORDS.put("沙漠", "沙漠"); - CATEGORY_KEYWORDS.put("恶地", "恶地"); - CATEGORY_KEYWORDS.put("雪原", "雪原"); - CATEGORY_KEYWORDS.put("沙砾", "沙砾"); - CATEGORY_KEYWORDS.put("沼泽", "沼泽"); - CATEGORY_KEYWORDS.put("河流", "河流"); - CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛"); - CATEGORY_KEYWORDS.put("地下", "地下"); - CATEGORY_KEYWORDS.put("下界", "下界"); - CATEGORY_KEYWORDS.put("末地", "末地"); - // 条件文本中的别名映射 - CATEGORY_KEYWORDS.put("地狱", "下界"); - CATEGORY_KEYWORDS.put("下届", "下界"); - CATEGORY_KEYWORDS.put("主岛", "末地"); - CATEGORY_KEYWORDS.put("石岸", "石岸"); - CATEGORY_KEYWORDS.put("沙滩", "石岸"); - CATEGORY_KEYWORDS.put("海岸", "石岸"); - - // ===== 结构名称关键词映射 ===== - // 主世界结构 - CATEGORY_KEYWORDS.put("村庄", "村庄"); - CATEGORY_KEYWORDS.put("沙漠神殿", "沙漠"); - CATEGORY_KEYWORDS.put("丛林神庙", "森林"); - CATEGORY_KEYWORDS.put("沼泽小屋", "沼泽"); - CATEGORY_KEYWORDS.put("雪屋", "雪原"); - CATEGORY_KEYWORDS.put("林地府邸", "森林"); - CATEGORY_KEYWORDS.put("掠夺者前哨站", "平原"); - CATEGORY_KEYWORDS.put("废弃矿井", "地下"); - CATEGORY_KEYWORDS.put("要塞", "地下"); - CATEGORY_KEYWORDS.put("远古城市", "地下"); - CATEGORY_KEYWORDS.put("古迹废墟", "地下"); - CATEGORY_KEYWORDS.put("试炼密室", "地下"); - CATEGORY_KEYWORDS.put("海底废墟", "海洋"); - CATEGORY_KEYWORDS.put("沉船", "海洋"); - CATEGORY_KEYWORDS.put("埋藏的宝箱", "海洋"); - CATEGORY_KEYWORDS.put("废弃传送门", "主世界"); - CATEGORY_KEYWORDS.put("化石", "主世界"); - - // 下界结构 - CATEGORY_KEYWORDS.put("下界要塞", "下界"); - CATEGORY_KEYWORDS.put("猪灵堡垒", "下界"); - CATEGORY_KEYWORDS.put("玄武岩结构", "下界"); - - // 末地结构 - CATEGORY_KEYWORDS.put("末地城", "末地"); - CATEGORY_KEYWORDS.put("末地船", "末地"); - - // 下界相关别名 - CATEGORY_KEYWORDS.put("地狱要塞", "下界"); - CATEGORY_KEYWORDS.put("下界堡垒", "下界"); - - } - - /** - * 用世界维度名分类(最可靠) - * 世界名含 the_nether → 下界 - * 世界名含 the_end → 末地 - * 其余 → 主世界 - */ - public String mapBiomeCategory( - String rawKey, String worldName) { - // 优先用世界名判断维度 - if (worldName != null) { - String w = - worldName.toLowerCase(); - if (w.contains("nether")) - return "下界"; - if (w.contains("end")) - return "末地"; - } - - // 用群系映射表 - if (rawKey != null) { - String lower = - rawKey.toLowerCase(); - // 带 minecraft: 前缀的直接查 - String cat = - BIOME_CATEGORY.get(lower); - if (cat != null) - return cat; - - // 不带前缀的尝试补全 - String prefixed = - "minecraft:" + lower; - cat = BIOME_CATEGORY.get(prefixed); - if (cat != null) - return cat; - - // 模糊匹配群系名 - for (Map.Entry entry - : BIOME_CATEGORY.entrySet()) { - if (entry.getKey() - .contains(lower) - || lower.contains( - entry.getKey() - .replace( - "minecraft:", - ""))) { - return entry.getValue(); - } - } - } - - return "主世界"; - } - - // ========== 结构检测 ========== - - /** - * 检测指定位置是否存在自然生成的结构 - * 返回结构分类(如"村庄"、"沙漠"等),未找到返回null - * 使用 PaperMC World.hasStructureAt() + Registry.STRUCTURE API - */ - private String detectStructureAt( - String worldName, double x, - double y, double z) { - try { - World w = Bukkit.getWorld(worldName); - if (w == null) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测: 世界 '" - + worldName + "' 不存在"); - return null; - } - - int bx = (int) Math.floor(x); - int by = (int) Math.floor(y); - int bz = (int) Math.floor(z); - - // 首次调用时打印所有注册表结构键 - if (!loggedStructureRegistry) { - plugin.getLogger().info( - "[QuestTracker] === 结构注册表诊断 ==="); - int count = 0; - for (Structure s : Registry.STRUCTURE) { - NamespacedKey key = - Registry.STRUCTURE.getKey(s); - plugin.getLogger().info( - "[QuestTracker] 注册表结构 #" - + count + ": " + key); - count++; - } - plugin.getLogger().info( - "[QuestTracker] 注册表共 " - + count + " 个结构"); - plugin.getLogger().info( - "[QuestTracker] === 诊断结束 ==="); - loggedStructureRegistry = true; - } - - // 第1步: 精确位置检查 - io.papermc.paper.math.Position exactPos = - io.papermc.paper.math.Position.block( - bx, by, bz); - for (Structure structure : Registry.STRUCTURE) { - boolean hasAt = - w.hasStructureAt(exactPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey(structure); - String keyStr = (key != null) - ? key.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY.get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(精确): " - + keyStr + " -> category=" - + (cat != null - ? cat : "未映射")); - if (cat != null) return cat; - } - } - - // 第2步: Y轴偏移检查 (-5 到 +10) - for (int dy = -5; dy <= 10; dy++) { - if (dy == 0) continue; - int testY = by + dy; - io.papermc.paper.math.Position testPos = - io.papermc.paper.math.Position.block( - bx, testY, bz); - for (Structure structure - : Registry.STRUCTURE) { - boolean hasAt = w.hasStructureAt( - testPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey(structure); - String keyStr = (key != null) - ? key.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY - .get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(Y偏移" - + dy + "): " - + keyStr - + " -> category=" - + (cat != null - ? cat : "未映射")); - if (cat != null) return cat; - } - } - } - - // 第3步: 水平偏移检查 (±1格) - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) continue; - io.papermc.paper.math.Position offsetPos = - io.papermc.paper.math.Position - .block(bx + dx, by, - bz + dz); - for (Structure structure - : Registry.STRUCTURE) { - boolean hasAt = w.hasStructureAt( - offsetPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey( - structure); - String keyStr = (key != null) - ? key.toString() - : "unknown"; - String cat = - STRUCTURE_CATEGORY - .get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(偏移" - + dx + "," - + dz + "): " - + keyStr - + " -> category=" - + (cat != null - ? cat - : "未映射")); - if (cat != null) return cat; - } - } - } - } - - plugin.getLogger().info( - "[QuestTracker] 所有位置检查完毕, " - + "未匹配任何结构 (pos=" - + bx + "," + by + "," + bz - + ")"); - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测异常: " - + ex.getClass().getSimpleName() - + ": " + ex.getMessage()); - ex.printStackTrace(); - } - return null; - } - - /** - * 将结构中文名映射到对应的大类 - * 用于匹配任务条件中的结构名 - */ - private String mapStructureCategory( - String structureName) { - if (structureName == null) return ""; - - // 主世界结构 - if (structureName.contains("村庄")) - return "村庄"; - if (structureName.contains("沙漠神殿")) - return "沙漠"; - if (structureName.contains("丛林神庙") || structureName.contains("丛林神殿")) - return "森林"; - if (structureName.contains("沼泽小屋")) - return "沼泽"; - if (structureName.contains("雪屋") || structureName.contains("冰屋")) - return "雪原"; - if (structureName.contains("林地府邸")) - return "森林"; - if (structureName.contains("掠夺者前哨站")) - return "掠夺者前哨站"; - if (structureName.contains("废弃矿井") || structureName.contains("恶地废弃矿井")) - return "地下"; - if (structureName.contains("要塞")) - return "地下"; - if (structureName.contains("远古城市")) - return "地下"; - if (structureName.contains("远古遗迹") || structureName.contains("古迹废墟")) - return "远古遗迹"; - if (structureName.contains("试炼密室")) - return "试炼密室"; - if (structureName.contains("海底神殿") || structureName.contains("海洋神殿")) - return "海洋"; - if (structureName.contains("海底废墟") || structureName.contains("冻洋废墟") || structureName.contains("暖洋废墟")) - return "海洋"; - if (structureName.contains("沉船")) - return "海洋"; - if (structureName.contains("埋藏的宝箱") || structureName.contains("埋藏的宝藏")) - return "海洋"; - if (structureName.contains("废弃传送门")) - return "平原"; - if (structureName.contains("化石")) - return "主世界"; - - // 下界结构 - if (structureName.contains("下界要塞")) - return "下界"; - if (structureName.contains("猪灵堡垒") || structureName.contains("堡垒遗迹")) - return "下界"; - if (structureName.contains("玄武岩结构") || structureName.contains("下界化石")) - return "下界"; - - // 末地结构 - if (structureName.contains("末地城")) - return "末地"; - if (structureName.contains("末地船")) - return "末地"; - - return ""; - } - - -// ========== 解析为区块 ========== - - private List
parseIntoSections( - List lines) { - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - return parseHashSections(lines); - } else if (hasDash) { - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - continue; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseHashSections(converted); - } - return new ArrayList<>(); - } - - private List
parseHashSections( - List lines) { - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add(new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } else if (t.isEmpty()) { - // skip - } else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty() - && curTitle != null) { - curLines.add(cond); - } - } - } - if (curTitle != null) { - sections.add(new Section( - curTitle, curLines)); - } - return sections; - } - - private List
buildFlatSections( - List lines, - String category) { - List conditions = - new ArrayList<>(); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - conditions.add(cond); - } - } - if (conditions.isEmpty()) - return new ArrayList<>(); - List
list = - new ArrayList<>(); - list.add(new Section( - "第一阶段", conditions)); - return list; - } - - // ========== 文件加载入口 ========== - - private void loadAllQuests() { - loadCategory("新人任务"); - loadCategory("主线任务"); - loadCategory("支线任务"); - } - - private void loadCategory(String name) { - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 扫描中..."); - File folder = new File( - plugin.getDataFolder(), - name); - String path = "plugins/Sdf1_login/" - + name + "/"; - - if (!folder.exists()) { - folder.mkdirs(); - plugin.getLogger().warning( - "[QuestTracker] 已创建目录: " - + path); - questCache.put(name, - new ArrayList<>()); - return; - } - - File[] mdFiles = folder.listFiles( - (d, n) -> n.endsWith(".md")); - - if (mdFiles == null - || mdFiles.length == 0) { - plugin.getLogger().warning( - "[QuestTracker] " + path - + " 下无 .md 文件"); - questCache.put(name, - new ArrayList<>()); - return; - } - Arrays.sort(mdFiles); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 发现 " + mdFiles.length - + " 个文件,开始扫描..."); - - // 合并所有文件的阶段 - TreeMap merged = - new TreeMap<>(); - int fileCount = 0; - - for (int fi = 0; - fi < mdFiles.length; fi++) { - File file = mdFiles[fi]; - List lines = - readFileLines(file); - - if (lines.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 空文件,跳过"); - continue; - } - - List
sections = - parseIntoSections(lines); - - if (sections.isEmpty()) { - sections = buildFlatSections( - lines, name); - } - - if (sections.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 无法识别,跳过"); - continue; - } - - // 每个文件独立处理阶段号 - int defaultNum = fi + 1; - buildSingleFileStages( - sections, name, - merged, defaultNum); - - fileCount++; - } - - if (merged.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " + name - + ": 无有效任务数据"); - questCache.put(name, - new ArrayList<>()); - return; - } - - // 日志 - for (Map.Entry en - : merged.entrySet()) { - int num = en.getKey() >= 1000 - ? en.getKey() - 999 - : en.getKey(); - plugin.getLogger().info( - "[QuestTracker] " + name - + " 阶段" + num - + " \"" - + en.getValue().displayName - + "\" → 条件" - + en.getValue() - .conditions.size() - + "项, 详情" - + en.getValue() - .details.size() - + "项, 奖励" - + en.getValue() - .rewards.size() - + "项"); - } - - List list = - new ArrayList<>( - merged.values()); - questCache.put(name, list); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 解析完成 → " - + list.size() + " 个阶段" - + " (来自 " + fileCount - + " 个文件)"); - } - - private Integer extractStageNumber( - String title) { - if (title == null) return null; - String t = title.toLowerCase().trim(); - - // 含阶段/stage/关/chapter 关键词 - boolean isStage = - t.contains("阶段") - || t.contains("stage") - || t.contains("关卡") - || t.contains("章节") - || t.contains("chapter"); - - if (isStage) { - String digits = title - .replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - try { - return Integer.parseInt(digits); - } catch (NumberFormatException e) { - // fall through - } - } - return parseChineseNumber(title); - } - - // 无关键词但以数字开头(如 "1. 挖掘") - String trimmed = t - .replaceAll("^[.、\\s]+", "") - .trim(); - if (trimmed.length() > 0 - && Character.isDigit( - trimmed.charAt(0))) { - String num = trimmed - .replaceAll("[^0-9]", ""); - if (!num.isEmpty()) { - try { - return Integer.parseInt(num); - } catch (NumberFormatException e) { - // fall through - } - } - } - - return null; - } -// ========== 领取记录 ========== - - /** - * 标记已领取某阶段奖励 - */ - public void markClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return; - // 用阶段编号代替显示名,保证稳定 - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - storage.setCondition(player, key); - } - - public boolean hasClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return false; - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - return toBool( - storage.getCondition( - player, key)); - } - - private int parseChineseNumber( - String title) { - String[] cn = {"一", "二", "三", - "四", "五", "六", "七", - "八", "九", "十"}; - if (title.contains("十")) { - for (int j = 0; j < 9; j++) { - if (title.contains(cn[j])) { - return 10 + j + 1; - } - } - return 10; - } - for (int i = 0; i < 9; i++) { - if (title.contains(cn[i])) { - return i + 1; - } - } - return 0; - } - - // ========== md 解析核心 ========== - - /** - * 读取文件所有行 - */ - private List readFileLines( - File file) { - List lines = - new ArrayList<>(); - try { - BufferedReader br = - new BufferedReader( - new FileReader(file)); - String line; - while ((line = br.readLine()) - != null) { - lines.add(line); - } - br.close(); - } catch (Exception e) { - plugin.getLogger().warning( - "[QuestTracker] 读取 " - + file.getName() - + " 失败: " - + e.getMessage()); - } - return lines; - } - - /** - * 自动检测格式并解析单个文件 - */ - private List parseMdFile( - File file, String category) { - List lines = - readFileLines(file); - if (lines.isEmpty()) - return new ArrayList<>(); - - // 检测分隔符 - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) - hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - // # 分隔格式 - return parseSections( - lines, category); - } else if (hasDash) { - // ---- 分隔格式:转换为 # 后复用 - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - title = "separator"; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseSections( - converted, category); - } else { - // 无分隔符:整文件一个阶段 - return parseFlat(lines, category); - } - } - - /** - * # 分隔格式 → 区块 → 阶段 - */ - private List parseSections( - List lines, - String category) { - // 拆分为区块 - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - - // # 开头 → 新区块 - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add( - new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } - // 空行/分隔线 → 跳过 - else if (t.isEmpty() - || t.startsWith("----") - || t.startsWith("====") - || t.startsWith("***")) { - // skip - } - // 普通行 → 区块内容 - else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - curLines.add(cond); - } - } - } - // 保存最后一个区块 - if (curTitle != null) { - sections.add( - new Section( - curTitle, curLines)); - } - - // 直接转为 QuestFile 列表 - List result = - new ArrayList<>(); - int num = 1; - for (Section sec : sections) { - QuestFile qf = new QuestFile(); - qf.displayName = - extractDisplayName( - sec.title); - qf.fileName = category; - qf.conditions = - new ArrayList<>(sec.lines); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - result.add(qf); - num++; - } - return result; - } - - /** - * 无分隔符:整文件 = 一个阶段 - */ - private List parseFlat( - List lines, - String category) { - List list = - new ArrayList<>(); - QuestFile qf = makeDefaultStage( - category); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - qf.conditions.add(cond); - } - } - if (!qf.conditions.isEmpty()) { - list.add(qf); - } - return list; - } -// ========== Vault 经济奖励发放 ========== - - /** - * 发放阶段奖励 - * 含经济内容的走 Vault API或债券 - */ - public void claimRewards( - Player player, - QuestFile qf) { - if (qf == null - || qf.rewards == null - || qf.rewards.isEmpty()) { - player.sendMessage("§c无可领取的奖励"); - return; - } - - Main main = (Main) plugin; - // ★ 读取配置:economy = Vault,bonds = 债券(默认) - boolean useEconomy = "economy".equalsIgnoreCase( - main.getConfigMgr().rewardChannel); - - // Vault 经济引用(仅 useEconomy 时使用) - net.milkbowl.vault.economy.Economy econ = null; - if (useEconomy) { - econ = main.getEconomy(); - } - - int sentCount = 0; - - for (String reward : qf.rewards) { - String rt = reward.trim(); - if (rt.isEmpty()) continue; - - // 检查可选奖励标记 - if (hasOptionalMarker(rt)) { - if (!areAllConditionsMet(player.getName(), qf)) { - player.sendMessage( - "§7[可选奖励] 需完成所有可选条件: " + rt); - continue; - } - } - - // 按 + 分割子项 - String[] parts = rt.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 尝试识别经济金额 - double amt = extractEconomyAmount(part); - if (amt > 0) { - // ★ 根据配置分发 - if (useEconomy && econ != null) { - // —— Vault 经济 —— - double before = - econ.getBalance(player); - econ.depositPlayer(player, amt); - double after = - econ.getBalance(player); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(余额: §e" - + String.format("%.2f", before) - + " §7→ §a" - + String.format("%.2f", after) - + "§7)"); - if (plugin instanceof Main) { - ((Main) plugin).getCommission() - .payCommission( - player.getName(), amt); - } - } else { - // —— 债券(默认) —— - int bondAmt = - (int) Math.round(amt); - BondManager bonds = main.getBonds(); - if (bonds != null) { - int before = bonds.getBonds( - player.getName()); - bonds.addBonds( - player.getName(), - bondAmt, - "quest_reward", - qf.displayName, - "任务系统", - "任务奖励:" + qf.displayName); - int after = bonds.getBonds( - player.getName()); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(债券: §e" - + before - + " §7→ §a" - + after + "§7)"); - } else { - player.sendMessage( - "§c债券系统不可用"); - } - } - sentCount++; - continue; - } - - // 尝试发物品 - java.util.List items = - parseRewardItems(part); - if (!items.isEmpty()) { - for (ItemStack is : items) { - player.getInventory().addItem(is); - } - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - continue; - } - - // 非经济非物品 → 仅消息 - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - } - } - - plugin.getLogger().info( - "[QuestTracker] " + player.getName() - + " 预取了 \"" + qf.displayName - + "\" 共 " + sentCount + "项奖励"); - } - - /** - * 检查奖励文本是否含可选标记 - */ - private boolean hasOptionalMarker( - String text) { - return text.contains("(可选)") - || text.contains("(进阶)") - || text.contains("【可选】") - || text.contains("【进阶】"); - } - - /** - * 检查阶段所有条件(含可选)是否全部满足 - * 用于决定可选奖励是否发放 - */ - private boolean areAllConditionsMet( - String player, QuestFile qf) { - if (qf.conditions == null - || qf.conditions.isEmpty()) - return true; - for (String cond : qf.conditions) { - if (!isConditionMet(player, cond)) - return false; - } - return true; - } - - /** - * 从奖励文本中提取经济金额 - * 支持: "500游戏币" "500" "金币500" - * "$500" "1000-5000"(随机) "1000~5000" - */ - private double extractEconomyAmount( - String text) { - String t = text.replaceAll(" ", ""); - - // 必须包含经济关键词才算经济奖励 - boolean hasEcon = - t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额") - || t.contains("发钱") - || t.contains("债券"); // ★ 新增 - - - if (!hasEcon) return 0; - - // 范围: "1000-5000" 或 "1000~5000" - Matcher rm = Pattern - .compile("(\\d+)[-~](\\d+)") - .matcher(t); - if (rm.find()) { - double min = Double.parseDouble( - rm.group(1)); - double max = Double.parseDouble( - rm.group(2)); - return min + Math.random() - * (max - min); - } - - // 纯数字(含 $ 前缀) - Matcher nm = Pattern - .compile("\\$?(\\d+(\\.\\d+)?)") - .matcher(t); - if (nm.find()) { - return Double.parseDouble( - nm.group(1)); - } - - return 0; - } - /** - * 解析奖励物品文本 - * "皮革套装*1" → 4件皮革装备 - * "木剑*1" → 1把木剑 - * "钻石*64" → 64颗钻石 - */ - private List parseRewardItems( - String text) { - List result = - new ArrayList<>(); - - // 去掉条件前缀 - // 如 "完成注册登录:皮革套装*1" - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - - // 按 + 分割多个物品 - String[] parts = - text.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 跳过经济文本 - if (extractEconomyAmount(part) > 0) - continue; - - int amount = 1; - String itemName = part; - - // 解析 *N 数量 - if (part.contains("*")) { - String[] sp = - part.split("\\*"); - itemName = sp[0].trim(); - try { - amount = Integer.parseInt( - sp[1].trim()); - } catch (Exception e) { - amount = 1; - } - } - - List mats = - matchMaterials(itemName); - for (Material m : mats) { - int give = m.name() - .contains("HELMET") - || m.name() - .contains("CHESTPLATE") - || m.name() - .contains("LEGGINGS") - || m.name() - .contains("BOOTS") - ? 1 : amount; - result.add( - new ItemStack(m, give)); - } - } - return result; - } - - /** - * 中文物品名 → Material 列表 - * 套装返回多个部件 - */ - private List matchMaterials( - String name) { - List list = - new ArrayList<>(); - String n = name.toLowerCase().trim(); - - // 套装 - if (n.contains("皮革套装") - || n.contains("皮套装")) { - list.add(Material.LEATHER_HELMET); - list.add(Material.LEATHER_CHESTPLATE); - list.add(Material.LEATHER_LEGGINGS); - list.add(Material.LEATHER_BOOTS); - return list; - } - if (n.contains("铁套装") - || n.contains("铁甲")) { - list.add(Material.IRON_HELMET); - list.add(Material.IRON_CHESTPLATE); - list.add(Material.IRON_LEGGINGS); - list.add(Material.IRON_BOOTS); - return list; - } - if (n.contains("钻石套装") - || n.contains("钻甲")) { - list.add(Material.DIAMOND_HELMET); - list.add(Material.DIAMOND_CHESTPLATE); - list.add(Material.DIAMOND_LEGGINGS); - list.add(Material.DIAMOND_BOOTS); - return list; - } - // 木制工具套装 - if (n.contains("木制工具") - || n.contains("木工具")) { - list.add(Material.WOODEN_SWORD); - list.add(Material.WOODEN_PICKAXE); - list.add(Material.WOODEN_AXE); - list.add(Material.WOODEN_SHOVEL); - return list; - } - if (n.contains("石制工具") - || n.contains("石工具")) { - list.add(Material.STONE_SWORD); - list.add(Material.STONE_PICKAXE); - list.add(Material.STONE_AXE); - list.add(Material.STONE_SHOVEL); - return list; - } - if (n.contains("铁工具")) { - list.add(Material.IRON_SWORD); - list.add(Material.IRON_PICKAXE); - list.add(Material.IRON_AXE); - list.add(Material.IRON_SHOVEL); - return list; - } - - // 单个物品 - if (n.contains("木剑")) - list.add(Material.WOODEN_SWORD); - else if (n.contains("木镐")) - list.add(Material.WOODEN_PICKAXE); - else if (n.contains("铁剑")) - list.add(Material.IRON_SWORD); - else if (n.contains("铁镐")) - list.add(Material.IRON_PICKAXE); - else if (n.contains("钻石剑")) - list.add(Material.DIAMOND_SWORD); - else if (n.contains("钻石镐")) - list.add(Material.DIAMOND_PICKAXE); - else if (n.contains("钻石")) - list.add(Material.DIAMOND); - else if (n.contains("铁锭")) - list.add(Material.IRON_INGOT); - else if (n.contains("金锭") - || n.contains("金块")) - list.add(Material.GOLD_INGOT); - else if (n.contains("煤炭")) - list.add(Material.COAL); - else if (n.contains("木棍")) - list.add(Material.STICK); - else if (n.contains("弓")) - list.add(Material.BOW); - else if (n.contains("箭")) - list.add(Material.ARROW); - else if (n.contains("床")) - list.add(Material.RED_BED); - else { - // 尝试直接匹配 Material 名 - try { - list.add(Material.valueOf( - n.toUpperCase() - .replaceAll("[^A-Z_]", - ""))); - } catch (Exception e) { - // 未知物品 - } - } - return list; - } - - private QuestFile getOrCreateStage( - TreeMap map, - int key, - String category) { - if (!map.containsKey(key)) { - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + key; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - } - return map.get(key); - } - - /** - * 单个文件的区块 → 阶段 - * defaultStageNum: 文件无阶段标题时的默认编号 - */ - private void buildSingleFileStages( - List
sections, - String category, - TreeMap stageMap, - int defaultStageNum) { - Integer curKey = null; - - for (Section sec : sections) { - Integer stageNum = - extractStageNumber(sec.title); - - if (stageNum != null) { - curKey = stageNum; - } else if (curKey == null) { - curKey = defaultStageNum; - } - - QuestFile qf = getOrCreateStage( - stageMap, curKey, category); - - String displayName = - extractDisplayName(sec.title); - - // 设置显示名 - if (!displayName.isEmpty() - && qf.displayName - .startsWith("阶段 ")) { - qf.displayName = displayName; - } - - // 路由 - if (stageNum != null - && sec.lines.isEmpty()) { - if (!displayName.isEmpty()) { - qf.displayName = displayName; - } - } - else if (isRewardTitle(sec.title)) { - // 奖励 → 只进 rewards - qf.rewards.addAll(sec.lines); - } - else if (isDetailsTitle(sec.title)) { - // 详情 → 进 details + conditions - qf.details.addAll(sec.lines); - for (String line : sec.lines) { - if (!qf.conditions - .contains(line)) { - qf.conditions.add(line); - } - } - } - else if (isConditionsTitle( - sec.title)) { - // 条件 → 只进 conditions - qf.conditions.addAll(sec.lines); - } - else { - // 其他 → 条件 - qf.conditions.addAll(sec.lines); - } - } - } - - /** - * "主线任务-斗志昂扬" → "斗志昂扬" - * "支线-钓鱼" → "钓鱼" - * "阶段 1" → "阶段 1" - * "第一阶段" → "第一阶段" - */ - private String extractDisplayName( - String title) { - if (title == null) return ""; - // 按 - 或 — 或 – 分割 - String[] parts = title.split( - "\\s*[-—–]\\s*", 2); - if (parts.length >= 2) { - String right = parts[1].trim(); - if (!right.isEmpty()) { - return right; - } - } - return title; - } - private DatabaseManager getDb() { - return ((Main) plugin).getDb(); - } - - private boolean isDetailsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("任务详情") - || t.contains("详情") - || t.contains("任务说明") - || t.contains("任务目标") - || t.contains("description") - || t.contains("任务要求"); - } - - private boolean isConditionsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("解锁条件") - || t.contains("条件") - || t.contains("前置条件") - || t.contains("前置要求") - || t.contains("解锁") - || t.contains("condition"); - } - - - private void ensureStage( - TreeMap map, - String category, - QuestFile current, - int counter) { - if (current != null) return; - // 用大数字确保排在有编号阶段后面 - int key = 1000 + counter; - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + (counter + 1); - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - // 更新调用方的引用(通过返回值) - // 这里用 map 操作,current 无法直接更新 - // 所以改用返回值方式 - } - /** - * 自动将条件中的奖励内容移到 rewards - */ - private void separateRewards(QuestFile qf) { - List remaining = - new ArrayList<>(); - for (String line : qf.conditions) { - if (isRewardContent(line)) { - if (!qf.rewards.contains(line)) { - qf.rewards.add(line); - } - } else { - remaining.add(line); - } - } - qf.conditions = remaining; - } - private boolean checkStageDependency( - String player, - String condition) { - Matcher m = Pattern.compile( - "第([一二三四五六七八九十\\d]+)" - + "阶段") - .matcher(condition); - - while (m.find()) { - String numStr = m.group(1); - int stageNum = - parseChineseNumber(numStr); - if (stageNum == 0) { - try { - stageNum = - Integer.parseInt(numStr); - } catch (Exception e) { - return false; - } - } - - boolean stageDone = false; - for (List quests - : questCache.values()) { - for (QuestFile qf : quests) { - Integer qfNum = - extractStageNumber( - qf.displayName); - if (qfNum != null - && qfNum == stageNum) { - if (isStageComplete(player, - qf.conditions)) { - stageDone = true; - } - break; - } - } - if (stageDone) break; - } - - if (!stageDone) return false; - } - return true; - } - - /** - * 判断一行内容是否为奖励 - */ - private boolean isRewardContent( - String line) { - String l = line.toLowerCase(); - // 含货币关键词 - if (l.contains("游戏币") - || l.contains("金币") - || l.contains("积分") - || l.contains("钻石") - || l.contains("发钱") - || l.contains("发放") - || l.contains("经验") - || l.contains("等级") - || l.contains("经济") - || l.contains("余额") - || l.contains("块钱")) - return true; - // 纯数字+单位(如 "500游戏币") - if (l.matches( - ".*\\d+\\s*(游戏币|金币|元|积分|" - + "钻石|块钱|经验|级|经济|余额).*")) - return true; - // $前缀(如 "$500") - if (l.matches(".*\\$\\d+.*")) - return true; - // 范围格式(如 "1000-5000" 或 "1000~5000") - if (l.matches(".*\\d+[-~]\\d+.*") - && !l.contains("阶段") - && !l.contains("stage")) - return true; - return false; - } - - // ========== 标题分类 ========== - - private boolean isStageTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("阶段") - || t.contains("stage") - || t.contains("第一") - || t.contains("第二") - || t.contains("第三") - || t.contains("第四") - || t.contains("第五"); - } - - private boolean isRewardTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("奖励") - || t.contains("奖品") - || t.contains("报酬") - || t.contains("reward") - || t.contains("奖赏") - || t.contains("礼包内容") - || t.contains("礼包") - || t.contains("物品奖励"); - } - - - private QuestFile makeDefaultStage( - String category) { - QuestFile qf = new QuestFile(); - qf.displayName = "第一阶段"; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - return qf; - } - - private static class Section { - final String title; - final List lines; - Section(String title, - List lines) { - this.title = title; - this.lines = lines; - } - } - - // ========== 公开查询 ========== - - public List getQuests( - String category) { - List list = - questCache.get(category); - return list != null ? list - : new ArrayList<>(); - } - - public int getCompletedStageCount( - String player, - List quests) { - int count = 0; - if (quests == null) return 0; - for (QuestFile qf : quests) { - if (isStageComplete(player, - qf.conditions)) { - count++; - } - } - return count; - } - - public QuestFile getCurrentStage( - String player, - List quests) { - if (quests == null) return null; - for (QuestFile qf : quests) { - if (!isStageComplete( - player, qf.conditions)) { - return qf; - } - } - return null; - } - - public int getStageNumber( - List quests, - QuestFile qf) { - if (quests == null || qf == null) - return 0; - return quests.indexOf(qf) + 1; - } - - public int getStageNumber( - QuestFile qf) { - if (qf == null) return 0; - List quests = - getQuests(qf.fileName); - return getStageNumber(quests, qf); - } - - public boolean isStageCompleted( - String player, - QuestFile qf) { - if (qf == null) return false; - return isStageComplete( - player, qf.conditions); - } - - public List getUnmetConditions( - String player, - List conditions) { - List unmet = - new ArrayList<>(); - if (conditions == null) return unmet; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) { - unmet.add( - getProgress( - player, cond)); - } - } - return unmet; - } - public String getProgress( - String player, String rawCond) { - if (player == null || rawCond == null) - return "§7未知"; - String key = stripOptional(rawCond); - - if (key.contains("打卡") - && key.contains("群系")) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return "§7打卡 " + biome - + "群系: §e" - + catCount + "/1"; - } - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - if (key.contains("完成") - && key.contains("阶段")) { - boolean done = checkStageDependency( - player, key); - return done - ? "§a✓ " + key - : "§c✗ " + key; - } - - if ("完成注册登录".equals(key)) { - boolean done = getDb() != null - && getDb().userExists( - player); - return done - ? "§a✓ 完成注册登录" - : "§c✗ 完成注册登录"; - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - boolean done = val != null - && !val.toString().isEmpty(); - return done - ? "§a✓ 绑定Email" - : "§c✗ 绑定Email"; - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + needed + "小时"; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - int cur = getCounter( - player, "CHAT"); - return "§7聊天: §e" - + cur + "/1"; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return "§7方块操作: §e" - + total + "/" + needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return "§7连续登录: §e" - + streak + "/" + needed + "天"; - } - - if (key.startsWith("MINE:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "MINE:" + parts[1]); - return "§7挖 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "VILLAGER_TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - - } - - // ===== 击杀泛解析(支持中文列表) ===== -// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等 - if (key.contains("击杀") && key.contains("只")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - - String mobPart = key; - java.util.regex.Matcher killM = - java.util.regex.Pattern - .compile("击杀\\d+只(.+)") - .matcher(key); - if (killM.find()) { - mobPart = killM.group(1).trim(); - } - - String[] mobNames = mobPart - .split("[、,,,]+"); - int totalKills = 0; - StringBuilder names = new StringBuilder(); - for (String name : mobNames) { - String trimmed = name.trim(); - if (trimmed.isEmpty()) continue; - String entityType = - chineseMobToEntity(trimmed); - if (!entityType.isEmpty()) { - totalKills += getCounter( - player, - "KILL:" + entityType); - if (names.length() > 0) - names.append("/"); - names.append(trimmed); - } - } - return "§7击杀" + names + ": §e" - + totalKills + "/" + needed; - } - -// 单一KILL:xxx:N 格式(原有逻辑保留) - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - int needed = parseInt(p[2]); - int cur = getCounter(player, - "KILL:" + p[1]); - return "§7击杀" + p[1] + ": §e" - + cur + "/" + needed; - } - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "KILL_PLAYER"); - return "§7击杀玩家: §e" - + cur + "/" + needed; - } - - if (key.startsWith("CRAFT")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "CRAFT"); - return "§7合成: §e" - + cur + "/" + needed; - } - - if (key.startsWith("FISH")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "FISH"); - return "§7钓鱼: §e" - + cur + "/" + needed; - } - - if (key.startsWith("PLAYTIME")) { - String[] parts = key.split(":"); - long needed = parts.length >= 2 - ? Long.parseLong(parts[1]) - : 0; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + formatMs(needed); - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7上报村庄: §e" - + cur + "/" + needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7探索村庄: §e" - + cur + "/1"; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return "§7近7天登录: §e" - + streak + "/" - + needed + "天"; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7新手任务: §e" - + done + "/" - + quests.size(); - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7主线任务: §e" - + done + "/" - + quests.size(); - } - // ===== 泛解析:击杀类进度 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < mobTypes.size(); i++) { - total += getCounter(player, - "KILL:" + mobTypes.get(i)); - if (i > 0) names.append("/"); - names.append(reverseMobName( - mobTypes.get(i))); - } - return "§7击杀" + names + ": §e" - + total + "/" + needed; - } - } - - // ===== 泛解析:村民交易进度 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - // ===== 泛解析:采集/挖矿进度 ===== - if (key.contains("采集") - || key.contains("挖")) { - int needed = - parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < matTypes.size(); i++) { - total += getCounter( - player, - "MINE:" + matTypes - .get(i)); - if (i > 0) - names.append("/"); - names.append( - translateMaterialName( - matTypes - .get(i))); - } - return "§7采集" + names - + ": §e" + total - + "/" + needed; - } - } - - // ===== 泛解析:合成进度 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "CRAFT"); - return "§7合成: §e" + cur + "/" + needed; - } - - // ===== 泛解析:钓鱼进度 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "FISH"); - return "§7钓鱼: §e" + cur + "/" + needed; - } - - return "§7" + key; - } - private String translateMaterialName( - String name) { - switch (name) { - case "COAL_ORE": - return "煤矿石"; - case "DEEPSLATE_COAL_ORE": - return "深层煤矿石"; - case "IRON_ORE": - return "铁矿石"; - case "DEEPSLATE_IRON_ORE": - return "深层铁矿石"; - case "COPPER_ORE": - return "铜矿石"; - case "DEEPSLATE_COPPER_ORE": - return "深层铜矿石"; - case "GOLD_ORE": - return "金矿石"; - case "DEEPSLATE_GOLD_ORE": - return "深层金矿石"; - case "DIAMOND_ORE": - return "钻石矿石"; - case "DEEPSLATE_DIAMOND_ORE": - return "深层钻石矿石"; - case "EMERALD_ORE": - return "绿宝石矿石"; - case "DEEPSLATE_EMERALD_ORE": - return "深层绿宝石矿石"; - case "LAPIS_ORE": - return "青金石矿石"; - case "DEEPSLATE_LAPIS_ORE": - return "深层青金石矿石"; - case "REDSTONE_ORE": - return "红石矿石"; - case "DEEPSLATE_REDSTONE_ORE": - return "深层红石矿石"; - case "NETHER_GOLD_ORE": - return "下界金矿石"; - case "NETHER_QUARTZ_ORE": - return "下界石英矿石"; - case "ANCIENT_DEBRIS": - return "远古残骸"; - case "COAL": - return "煤炭"; - case "RAW_IRON": - return "粗铁"; - case "RAW_GOLD": - return "粗金"; - case "RAW_COPPER": - return "粗铜"; - case "DIAMOND": - return "钻石"; - case "EMERALD": - return "绿宝石"; - case "LAPIS_LAZULI": - return "青金石"; - case "REDSTONE": - return "红石"; - case "QUARTZ": - return "石英"; - default: - return name; - } - } - - /** - * 中文物种名 → Bukkit EntityType 名 - * 支持击杀泛解析的模糊匹配 - */ - private String chineseMobToEntity(String cn) { - String n = cn.toLowerCase().trim(); - - // 直接别名表 - Map mobMap = new HashMap<>(); - mobMap.put("骷髅", "SKELETON"); - mobMap.put("僵尸", "ZOMBIE"); - mobMap.put("苦力怕", "CREEPER"); - mobMap.put("蜘蛛", "SPIDER"); - mobMap.put("洞穴蜘蛛", "CAVE_SPIDER"); - mobMap.put("小白", "SKELETON"); - mobMap.put("末影人", "ENDERMAN"); - mobMap.put("enderman", "ENDERMAN"); - mobMap.put("猪灵", "PIGLIN"); - mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE"); - mobMap.put("恶魂", "GHAST"); - mobMap.put("凋灵骷髅", "WITHER_SKELETON"); - mobMap.put("烈焰人", "BLAZE"); - mobMap.put("史莱姆", "SLIME"); - mobMap.put("岩浆怪", "MAGMA_CUBE"); - mobMap.put("女巫", "WITCH"); - mobMap.put("蠹虫", "SILVERFISH"); - mobMap.put("潜影贝", "SHULKER"); - mobMap.put("守卫者", "GUARDIAN"); - mobMap.put("远古守卫者", "ELDER_GUARDIAN"); - mobMap.put("溺尸", "DROWNED"); - mobMap.put("僵尸村民", "ZOMBIE_VILLAGER"); - mobMap.put("尸壳", "HUSK"); - mobMap.put("流浪者", "STRAY"); - mobMap.put("幻翼", "PHANTOM"); - mobMap.put("掠夺者", "PILLAGER"); - mobMap.put("卫道士", "VINDICATOR"); - mobMap.put("唤魔者", "EVOKER"); - mobMap.put("恼鬼", "VEX"); - mobMap.put("劫掠兽", "RAVAGER"); - mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY"); - mobMap.put("凋灵", "WITHER"); - mobMap.put("末影龙", "ENDER_DRAGON"); - - // 精确匹配 - if (mobMap.containsKey(n)) { - return mobMap.get(n); - } - - // 模糊匹配(包含关系) - for (Map.Entry en - : mobMap.entrySet()) { - if (n.contains(en.getKey()) - || en.getKey().contains(n)) { - return en.getValue(); - } - } - - // 尝试直接用英文名 - try { - return org.bukkit.entity.EntityType - .valueOf(n.toUpperCase() - .replace(" ", "_")) - .name(); - } catch (Exception e) { - return ""; - } - } - - public List checkInBiome( - String player, - String rawBiomeKey, - String worldName, - double x, double y, double z) { - List matched = - new ArrayList<>(); - - // 32格去重 - String posKey = - player + "_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - String lastPos = - lastCheckInPos.get(posKey); - if (curPos.equals(lastPos)) { - plugin.getLogger().info( - "[QuestTracker] 打卡忽略: " - + player - + " 32格内重复"); - return matched; - } - lastCheckInPos.put(posKey, curPos); - - String category = - mapBiomeCategory( - rawBiomeKey, worldName); - // 结构检测(覆盖纯生物群系分类) - // 地下检测:Y低于表面则归类为地下 -// 覆盖废弃矿井/试炼密室/要塞/远古城市等 - try { - org.bukkit.World uw = - Bukkit.getWorld(worldName); - if (uw != null) { - int surfaceY = uw - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - } - } catch (Exception ignored) {} - - // ===== 结构检测(覆盖纯生物群系分类)===== - // 使用 PaperMC API 检测玩家所在位置的结构 - String structureCat = - detectStructureAt( - worldName, x, y, z); - if (structureCat != null - && !structureCat.isEmpty()) { - // 结构检测成功,结构分类优先 - // (站在结构上就不算"地下") - category = structureCat; - plugin.getLogger().info( - "[QuestTracker] 结构覆盖: " - + category - + " (结构: " - + structureCat + ")"); - } else { - plugin.getLogger().info( - "[QuestTracker] 未检测到结构: world=" - + worldName + ", pos=" - + String.format("%.0f,%.0f,%.0f", x, y, z)); - } - - - // 记录总打卡 - int allCur = getCounter( - player, "biome_ALL"); - setCounter(player, "biome_ALL", - allCur + 1); - - // 记录分类打卡 - if (!category.isEmpty() - && !"主世界" - .equals(category)) { - String catKey = - "biomecat_" + category; - int catCur = getCounter( - player, catKey); - setCounter(player, catKey, - catCur + 1); - } - - // 记录具体群系 - String bioKey = - "biome_" + rawBiomeKey; - int bioCur = getCounter( - player, bioKey); - setCounter(player, bioKey, - bioCur + 1); - - // 检查所有任务条件是否匹配 - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - for (String cat : categories) { - List quests = - getQuests(cat); - for (QuestFile qf : quests) { - if (isStageCompleted( - player, qf)) - continue; - for (String cond - : qf.conditions) { - if (isOptional(cond)) - continue; - String condKey = - stripOptional(cond); - if (!condKey - .contains("打卡")) - continue; - // 支持群系和结构两种打卡条件 - boolean isBiomeCond = - condKey.contains("群系") - || condKey - .contains("风景点"); - boolean isStructureCond = - condKey.contains("结构") - || condKey - .contains("村庄") - || condKey - .contains("神殿") - || condKey - .contains("神庙") - || condKey - .contains("小屋") - || condKey - .contains("雪屋") - || condKey - .contains("府邸") - || condKey - .contains("前哨站") - || condKey - .contains("矿井") - || condKey - .contains("要塞") - || condKey - .contains("城市") - || condKey - .contains("废墟") - || condKey - .contains("密室") - || condKey - .contains("沉船") - || condKey - .contains("宝箱") - || condKey - .contains("传送门") - || condKey - .contains("化石") - || condKey - .contains("堡垒"); - if (!isBiomeCond - && !isStructureCond) - continue; - String target = - extractBiome(condKey); - plugin.getLogger().info( - "[QuestTracker] 条件匹配: condKey=\"" - + condKey + "\", target=\"" - + target + "\", category=\"" - + category + "\", isBiomeCond=" - + isBiomeCond + ", isStructureCond=" - + isStructureCond); - boolean hit = false; - if ("ALL".equals(target) - || "风景点" - .equals(target)) { - hit = true; - } else if (!target.isEmpty() - && target.equals( - category)) { - hit = true; - } - if (hit && !matched - .contains(cond)) { - matched.add(cond); - } - } - } - } - - return matched; - } - - /* - * 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录 - */ - public String checkInAtPosition( - String player, String worldName, - double x, double y, double z) { - - List history = - loadCheckInPositions(player); - for (double[] pos : history) { - double dist = Math.sqrt( - Math.pow(x - pos[0], 2) - + Math.pow(y - pos[1], 2) - + Math.pow(z - pos[2], 2)); - if (dist <= 32) { - return "§c距离上次打卡点太近(" - + String.format( - "%.0f", dist) - + "格)"; - } - } - - // ===== 获取群系和分类 ===== - String biome = "minecraft:plains"; - String category = "平原"; - String structureCat = null; - try { - org.bukkit.World w = - Bukkit.getWorld(worldName); - if (w != null) { - org.bukkit.Location loc = - new org.bukkit.Location( - w, - (int) Math.floor(x), - (int) Math.floor(y), - (int) Math.floor(z)); - biome = loc.getBlock() - .getBiome().getKey() - .toString(); - category = mapBiomeCategory( - biome, worldName); - - int surfaceY = w - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - - // ===== 结构检测(覆盖纯生物群系分类)===== - structureCat = - detectStructureAt( - worldName, x, y, z); - plugin.getLogger().info( - "[QuestTracker] checkInAtPosition 结构检测: " - + structureCat + " at " - + String.format("%.0f,%.0f,%.0f", x, y, z)); - if (structureCat != null - && !structureCat.isEmpty()) { - // 结构分类优先于"地下" - category = structureCat; - } - - if ("下界".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.NETHER) { - category = "下界"; - } else if ("末地".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.THE_END) { - category = "末地"; - } - } - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 群系读取失败: " - + ex.getMessage()); - } - - plugin.getLogger().info( - "[QuestTracker] 打卡: " - + player - + " world=" + worldName - + " biome=" + biome - + " cat=" + category - + " pos=" - + String.format( - "%.0f,%.0f,%.0f", - x, y, z)); - - saveCheckInPosition( - player, x, y, z); - recordCheckIn( - player, biome, category, - worldName, x, y, z); - List matched = - checkInBiome( - player, biome, - worldName, x, y, z); - - StringBuilder sb = new StringBuilder(); - sb.append("§a打卡成功!群系: §e") - .append(category); - if (structureCat != null && !structureCat.isEmpty()) { - sb.append("\n§a结构: §e").append(structureCat); - } - if (!matched.isEmpty()) { - sb.append("\n§a完成任务条件:"); - for (String m : matched) { - String qn = - findQuestName(m); - sb.append("\n§7• "); - if (qn != null) { - sb.append("§e[") - .append(qn) - .append("§7] §f"); - } - sb.append(m); - } - } - return sb.toString(); - } - - - /** - * 根据条件文本查找所属任务名 - */ - private String findQuestName( - String condition) { - for (String cat : Arrays.asList( - "新人任务", - "主线任务", - "支线任务")) { - for (QuestFile qf : - getQuests(cat)) { - if (qf.conditions.contains( - condition)) { - return qf.displayName; - } - } - } - return null; - } - - - /** - * 加载玩家所有历史打卡坐标 - * 格式: x1,y1,z1;x2,y2,z2;... - */ - private List loadCheckInPositions( - String player) { - List list = new ArrayList<>(); - Object val = storage.getCondition( - player, "checkin_all_pos"); - if (val == null) return list; - String data = val.toString(); - if (data.isEmpty()) return list; - String[] records = data.split(";"); - for (String r : records) { - String[] parts = r.split(","); - if (parts.length >= 3) { - try { - list.add(new double[]{ - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2])}); - } catch (NumberFormatException e) { - // 跳过损坏数据 - } - } - } - return list; - } - - /** - * 保存打卡坐标(追加到已有记录) - */ - private void saveCheckInPosition( - String player, double x, - double y, double z) { - List list = - loadCheckInPositions(player); - list.add(new double[]{x, y, z}); - StringBuilder sb = new StringBuilder(); - for (double[] pos : list) { - if (sb.length() > 0) sb.append(";"); - sb.append(pos[0]).append(",") - .append(pos[1]).append(",") - .append(pos[2]); - } - storage.setConditionRaw( - player, - "checkin_all_pos", - sb.toString()); - } - // ========== 中文生物名 → EntityType 映射 ========== - private static final Map - MOB_NAME_MAP = new HashMap<>(); - static { - MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER"); - MOB_NAME_MAP.put("骷髅", "SKELETON"); - MOB_NAME_MAP.put("僵尸", "ZOMBIE"); - MOB_NAME_MAP.put("苦力怕", "CREEPER"); - MOB_NAME_MAP.put("蜘蛛", "SPIDER"); - MOB_NAME_MAP.put("末影人", "ENDERMAN"); - MOB_NAME_MAP.put("女巫", "WITCH"); - MOB_NAME_MAP.put("烈焰人", "BLAZE"); - MOB_NAME_MAP.put("僵尸猪灵", - "ZOMBIFIED_PIGLIN"); - MOB_NAME_MAP.put("猪灵", "PIGLIN"); - MOB_NAME_MAP.put("恶魂", "GHAST"); - MOB_NAME_MAP.put("凋灵骷髅", - "WITHER_SKELETON"); - MOB_NAME_MAP.put("史莱姆", "SLIME"); - MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE"); - MOB_NAME_MAP.put("溺尸", "DROWNED"); - MOB_NAME_MAP.put("尸壳", "HUSK"); - MOB_NAME_MAP.put("流髑", "STRAY"); - MOB_NAME_MAP.put("幻翼", "PHANTOM"); - MOB_NAME_MAP.put("守卫者", "GUARDIAN"); - MOB_NAME_MAP.put("远古守卫者", - "ELDER_GUARDIAN"); - MOB_NAME_MAP.put("掠夺者", "PILLAGER"); - MOB_NAME_MAP.put("卫道士", "VINDICATOR"); - MOB_NAME_MAP.put("唤魔者", "EVOKER"); - MOB_NAME_MAP.put("恼鬼", "VEX"); - MOB_NAME_MAP.put("劫掠兽", "RAVAGER"); - MOB_NAME_MAP.put("潜影贝", "SHULKER"); - MOB_NAME_MAP.put("末影螨", "ENDERMITE"); - MOB_NAME_MAP.put("凋灵", "WITHER"); - MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON"); - MOB_NAME_MAP.put("监守者", "WARDEN"); - MOB_NAME_MAP.put("嗅探兽", "SNIFFER"); - MOB_NAME_MAP.put("僵尸村民", - "ZOMBIE_VILLAGER"); - } - - /** - * 从条件文本中提取所有匹配的生物类型 - * 按名称长度降序匹配,避免短名误匹配 - */ - private List extractMobTypes( - String text) { - List types = new ArrayList<>(); - List> sorted = - new ArrayList<>( - MOB_NAME_MAP.entrySet()); - sorted.sort((a, b) -> - b.getKey().length() - - a.getKey().length()); - String remaining = text; - for (Map.Entry entry - : sorted) { - if (remaining.contains( - entry.getKey())) { - if (!types.contains( - entry.getValue())) { - types.add(entry.getValue()); - } - remaining = remaining.replace( - entry.getKey(), ""); - } - } - return types; - } - - /** - * EntityType → 中文名 - */ - private String reverseMobName( - String entityType) { - for (Map.Entry entry - : MOB_NAME_MAP.entrySet()) { - if (entry.getValue() - .equals(entityType)) { - return entry.getKey(); - } - } - return entityType; - } - - /** - * 从条件文本中提取矿物名 - * "采集任意矿物(煤炭除外)1组" - * → [COAL_ORE, IRON_ORE, GOLD_ORE, ...] - */ - private List extractMaterialTypes( - String text) { - List types = new ArrayList<>(); - // 排除列表 - List excludes = - new ArrayList<>(); - if (text.contains("煤炭除外")) { - excludes.add("COAL"); - excludes.add("COAL_ORE"); - excludes.add("DEEPSLATE_COAL_ORE"); - } - // 通配符:"矿物" "矿石" - if (text.contains("矿物") - || text.contains("矿石")) { - String[] ores = { - "COAL_ORE", - "DEEPSLATE_COAL_ORE", - "IRON_ORE", - "DEEPSLATE_IRON_ORE", - "COPPER_ORE", - "DEEPSLATE_COPPER_ORE", - "GOLD_ORE", - "DEEPSLATE_GOLD_ORE", - "DIAMOND_ORE", - "DEEPSLATE_DIAMOND_ORE", - "EMERALD_ORE", - "DEEPSLATE_EMERALD_ORE", - "LAPIS_ORE", - "DEEPSLATE_LAPIS_ORE", - "REDSTONE_ORE", - "DEEPSLATE_REDSTONE_ORE", - "NETHER_GOLD_ORE", - "NETHER_QUARTZ_ORE", - "ANCIENT_DEBRIS" - }; - for (String ore : ores) { - if (!excludes.contains(ore)) { - types.add(ore); - } - } - return types; - } - // 单个矿物名 - if (text.contains("煤炭")) - types.add("COAL"); - if (text.contains("铁矿") - || text.contains("铁锭")) - types.add("RAW_IRON"); - if (text.contains("金矿") - || text.contains("金锭")) - types.add("RAW_GOLD"); - if (text.contains("钻石")) - types.add("DIAMOND"); - if (text.contains("绿宝石")) - types.add("EMERALD"); - if (text.contains("青金石")) - types.add("LAPIS_LAZULI"); - if (text.contains("红石")) - types.add("REDSTONE"); - if (text.contains("铜")) - types.add("RAW_COPPER"); - if (text.contains("下界石英") - || text.contains("石英")) - types.add("QUARTZ"); - return types; - } - - - /** - * 从条件文本中提取目标群系分类 - * "打卡上报地狱任意群系" → "下界" - * "打卡任意海洋群系" → "海洋" - * "打卡任意群系" → "ALL" - * "打卡任意风景点" → "" - * "打卡村庄" → "村庄" - * "打卡沙漠神殿" → "沙漠" - */ - private String extractBiome(String text) { - if (text == null) return ""; - - // 优先匹配结构名称(更具体) - String structureResult = - mapStructureCategory(text); - if (!structureResult.isEmpty()) { - return structureResult; - } - - // 逐个匹配分类关键词 - for (Map.Entry en - : CATEGORY_KEYWORDS.entrySet()) { - if (text.contains(en.getKey())) { - return en.getValue(); - } - } - if (text.contains("风景点") - || text.contains("风景")) { - return "ALL"; - } - - // 有"群系"但无具体分类 → ALL - if (text.contains("群系")) { - return "ALL"; - } - return ""; - } - - - // ========== 标记条件 ========== - - public void markConditionComplete( - String player, - String conditionName) { - if (player == null - || conditionName == null) - return; - storage.setCondition( - player, conditionName); - } - - // ========== 事件回调 ========== - - public void onBlockMined( - String player, String material) { - String key = "MINE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - // 总计数 - setCounter(player, "MINE:ALL", - getCounter(player, "MINE:ALL") - + 1); - } - public boolean onReportVillage( - String player, - String worldName, - double x, double y, double z) { - String dbKey = "village_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - Object lastObj = - storage.getCondition( - player, dbKey); - String lastPos = lastObj != null - ? lastObj.toString() : ""; - if (curPos.equals(lastPos)) { - return false; - } - storage.setConditionRaw( - player, dbKey, curPos); - - String key = "REPORT_VILLAGE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - return true; - } - - public void onMobKill( - String player, String mobType) { - String key = "KILL:" + mobType; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayerKill( - String player) { - String key = "KILL_PLAYER"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onItemCrafted( - String player) { - String key = "CRAFT"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onFishCaught( - String player) { - String key = "FISH"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayTime( - String player, long ms) { - String key = "PLAYTIME"; - long cur = getCounterLong( - player, key); - setCounter(player, key, cur + ms); - } - - // ========== 内部:条件判定 ========== - - private boolean isStageComplete( - String player, - List conditions) { - if (conditions == null - || conditions.isEmpty()) - return true; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) - return false; - } - return true; - } - - public boolean isConditionMet( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return false; - - String key = stripOptional(rawCond); - - // 无条件类文本直接通过 - if (key.equals("无前级条件") - || key.equals("无条件") - || key.equals("无") - || key.equals("none") - || key.equals("无需条件") - || key.equals("无解锁条件")) { - return true; - } - - if (key.contains("打卡") - && (key.contains("群系") - || key.contains("风景点") - || key.contains("结构") - || key.contains("村庄") - || key.contains("神殿") - || key.contains("神庙") - || key.contains("小屋") - || key.contains("雪屋") - || key.contains("府邸") - || key.contains("前哨站") - || key.contains("矿井") - || key.contains("要塞") - || key.contains("城市") - || key.contains("废墟") - || key.contains("密室") - || key.contains("废墟") - || key.contains("沉船") - || key.contains("宝箱") - || key.contains("传送门") - || key.contains("化石") - || key.contains("堡垒"))) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return total >= 1; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return catCount >= 1; - } - return getCounter( - player, "biome_ALL") >= 1; - } - - if (key.contains("完成") - && key.contains("阶段")) { - return checkStageDependency( - player, key); - } - - if ("完成注册登录".equals(key)) { - return getDb() != null - && getDb().userExists( - player); - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - return val != null - && !val.toString().isEmpty(); - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return cur >= needed * 3600000L; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - return getCounter( - player, "CHAT") >= 1; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return total >= needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return streak >= needed; - } - - if (key.startsWith("MINE:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "MINE:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "KILL:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "VILLAGER_TRADE") - >= needed; - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter(player, - "KILL_PLAYER") >= n; - } - - if (key.startsWith("CRAFT")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "CRAFT") >= n; - } - - if (key.startsWith("FISH")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "FISH") >= n; - } - - if (key.startsWith("PLAYTIME")) { - String[] p = key.split(":"); - long n = p.length >= 2 - ? Long.parseLong(p[1]) : 0; - return getCounterLong(player, - "PLAYTIME") >= n; - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "REPORT_VILLAGE") - >= needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - return getCounter( - player, "REPORT_VILLAGE") - >= 1; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return streak >= needed; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - // ===== 泛解析:击杀类中文条件 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - for (String mobType : mobTypes) { - total += getCounter(player, - "KILL:" + mobType); - } - return total >= needed; - } - } - - // ===== 泛解析:村民交易条件 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "TRADE") - >= needed; - } - - // ===== 泛解析:采集/挖矿类条件 ===== - if (key.contains("采集") || key.contains("挖")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - for (String mat : matTypes) { - total += getCounter(player, - "MINE:" + mat); - } - return total >= needed; - } - } - - // ===== 泛解析:合成类条件 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "CRAFT") - >= needed; - } - - // ===== 泛解析:钓鱼类条件 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "FISH") - >= needed; - } - - return false; - } - - public void debugLogin(String player) { - try { - var user = getDb().getUser(player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "=== " + player - + " 数据库全部字段 ==="); - for (var entry - : user.entrySet()) { - plugin.getLogger().info( - "[QuestTracker] " - + entry.getKey() - + " = " - + entry.getValue() - + " (" - + (entry.getValue() - != null - ? entry.getValue() - .getClass() - .getSimpleName() - : "null") - + ")"); - } - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "getUser返回null: " - + player); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "debug异常: " - + e.getMessage()); - } - } - - // ========== 内部:计数器 ========== - - private int getCounter( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - return 0; - } - private int getIntField( - String player, String field) { - try { - Object val = getDb().getField( - player, field); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - if (val != null) { - return Integer.parseInt( - val.toString().trim()); - } - } catch (Exception e) { - // 忽略 - } - return 0; - } - - public void onVillagerTrade(String player) { - String key = "VILLAGER_TRADE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - private long getCounterLong( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).longValue(); - } - return 0; - } - - private void setCounter( - String player, String key, - int value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - private void setCounter( - String player, String key, - long value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - // ========== 工具 ========== - - private boolean toBool(Object val) { - if (val == null) return false; - if (val instanceof Boolean) - return (Boolean) val; - if (val instanceof Number) - return ((Number) val) - .intValue() != 0; - String s = String.valueOf(val); - return "1".equals(s) - || "true" - .equalsIgnoreCase(s); - } - - public boolean isOptional(String cond) { - if (cond == null) return false; - String c = cond; - return c.contains("(进阶)") - || c.contains("(可选)") - || c.contains(",可选)") - || c.contains(",可选)") - || c.contains("【可选】") - || c.contains("【进阶】"); - } - - - private String stripOptional(String cond) { - if (cond == null) return ""; - return cond.replace("(进阶)", "") - .replace("(可选)", "") - .replace(",可选)", "") - .replace(",可选)", "") - .replace("【可选】", "") - .replace("【进阶】", "") - .trim(); - } - - private int parseInt(String s) { - - try { - return Integer.parseInt(s.trim()); - } catch (Exception e) { - return 0; - } - } - private Object getDbField( - String player, String field) { - Main main = (Main) plugin; - return main.getDb().getField( - player, field); - } - - /** - * 从文本中提取第一个数字 - * "在线时间≥1小时" → 1 - * "破坏/放置方块100个" → 100 - */ - private int parseNumberFromText( - String text) { - java.util.regex.Matcher m = - java.util.regex.Pattern - .compile("(\\d+)") - .matcher(text); - if (m.find()) { - try { - return Integer.parseInt( - m.group(1)); - } catch (Exception e) { - return 0; - } - } - return 0; - } - public void onBlockPlaced( - String player, String material) { - String key = "PLACE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - setCounter(player, "PLACE:ALL", - getCounter(player, "PLACE:ALL") - + 1); - } - - public void onPlayerChat( - String player) { - String key = "CHAT"; - setCounter(player, key, - getCounter(player, key) + 1); - } - - private String formatMs(long ms) { - long sec = ms / 1000; - long min = sec / 60; - long hr = min / 60; - if (hr > 0) - return hr + "小时" - + (min % 60) + "分"; - if (min > 0) - return min + "分" - + (sec % 60) + "秒"; - return sec + "秒"; - } - - // ========== 数据结构 ========== - - public static class QuestFile { - public String displayName = ""; - public String fileName = ""; - public List details = - new ArrayList<>(); - public List rewards = - new ArrayList<>(); - public List conditions = - new ArrayList<>(); - - public boolean hasConditions() { - return conditions != null - && !conditions.isEmpty(); - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/RadioDownloadListener.java b/backup/20260605_152529/src/main/java/Sdf1_login/RadioDownloadListener.java deleted file mode 100644 index d3e744f..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/RadioDownloadListener.java +++ /dev/null @@ -1,144 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player - .PlayerResourcePackStatusEvent; - -import java.util.UUID; -import java.util.concurrent - .ConcurrentHashMap; - -public class RadioDownloadListener - implements Listener { - - private final Main plugin; - private final java.util.logging.Logger log; - - private final ConcurrentHashMap - failCount = new ConcurrentHashMap<>(); - private final ConcurrentHashMap - blocked = new ConcurrentHashMap<>(); - - private static final int MAX_FAILED = 3; - - public RadioDownloadListener(Main plugin) { - this.plugin = plugin; - this.log = plugin.getLogger(); - } - - public void onPlayerQuit(UUID uuid) { - failCount.remove(uuid); - blocked.remove(uuid); - } - - @EventHandler - public void onStatus( - PlayerResourcePackStatusEvent e) { - Player p = e.getPlayer(); - UUID uuid = p.getUniqueId(); - String status = - e.getStatus().name(); - - log.info("[Radio] === 资源包状态 ==="); - log.info("[Radio] 玩家: " - + p.getName()); - log.info("[Radio] UUID: " + uuid); - log.info("[Radio] 状态: " - + status); - log.info("[Radio] 原始状态枚举: " - + e.getStatus().getClass() - .getSimpleName() - + "." - + e.getStatus().name()); - - if (blocked.containsKey(uuid)) { - log.info("[Radio] 已熔断,跳过: " - + p.getName()); - return; - } - - switch (status) { - case "SUCCESS": - log.info("[Radio] " - + p.getName() - + " ✓ 资源包加载成功"); - failCount.remove(uuid); - blocked.remove(uuid); - break; - - case "ACCEPTED": - log.info("[Radio] " - + p.getName() - + " 客户端已接受," - + "等待下载..."); - break; - - case "DECLINED": - log.warning("[Radio] " - + p.getName() - + " ✗ 拒绝了资源包"); - log.warning("[Radio] " - + "可能原因: " - + "1.客户端设置禁用资源包" - + " 2.已缓存拒绝记录" - + " 3.SSL证书不匹配"); - handleFailure(p, uuid); - break; - - case "FAILED_DOWNLOAD": - log.warning("[Radio] " - + p.getName() - + " ✗ 下载失败"); - log.warning("[Radio] " - + "可能原因: " - + "1.URL无法访问" - + " 2.SSL证书错误" - + " 3.网络超时" - + " 4.文件不存在(404)"); - handleFailure(p, uuid); - break; - - default: - log.info("[Radio] " - + p.getName() - + " 未知状态: " - + status); - break; - } - } - - private void handleFailure(Player p, UUID uuid) { - int failNum = failCount.getOrDefault(uuid, 0) + 1; - failCount.put(uuid, failNum); - - log.warning("[Radio] " + p.getName() - + " 失败次数: " + failNum + "/" + MAX_FAILED); - - if (failNum >= MAX_FAILED) { - blocked.put(uuid, true); - log.severe("[Radio] " + p.getName() - + " 已达最大失败次数,停止重试," - + "玩家继续留在服务器"); - // 不踢出,只记录熔断,玩家正常游玩 - p.sendMessage("§e§l[Radio] 资源包加载失败" - + ",已取消下载资源包,部分画面音频可能出现问题" - + "\n§d§l若想继续下载,请退出重进。若持续失败,请上报管理员"); - - return; - } -// 打了包没测试 - p.sendMessage("§e§l[Radio] 下载失败," - + "重试... (" + failNum + "/" + MAX_FAILED + ")"); - - // 5秒后重试 - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline() && plugin.radio != null) { - plugin.radio.sendResourcePack(p); - } - }, 100L); - } -} \ No newline at end of file diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/RadioManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/RadioManager.java deleted file mode 100644 index 6fe5eda..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/RadioManager.java +++ /dev/null @@ -1,1040 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; - -import java.io.*; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.zip.*; -import java.util.Collections; -import org.bukkit.NamespacedKey; -import java.util.Set; - - -public class RadioManager { - - private final Main plugin; - private File radioDir; - private File oggDir; - private File packDir; - private BukkitTask mainRadioTask; - private final Map loginPlayers = - new ConcurrentHashMap<>(); - private String externalUrl = ""; - private int httpPort = 443; - private final Set pendingPack = - Collections.newSetFromMap( - new ConcurrentHashMap<>()); - private String lastPlayedSound = ""; - - private int radioIndex = 0; - - // ========== 登录阶段推送(管理员手动触发) ========== - public void startLoginRadio(Player p) { - loginPlayers.put(p.getName(), true); - } - - public void stopLoginRadio(Player p) { - loginPlayers.remove(p.getName()); - } - - public void stopAllLoginRadio() { - loginPlayers.clear(); - } - - private final List mainFiles = new ArrayList<>(); - private final List loginFiles = new ArrayList<>(); - - public RadioManager(Main plugin) { - this.plugin = plugin; - this.radioDir = new File( - plugin.getDataFolder(), "radio"); - this.oggDir = new File( - plugin.getDataFolder(), "radio/ogg"); - this.packDir = new File( - plugin.getDataFolder(), "radiopack"); - radioDir.mkdirs(); - oggDir.mkdirs(); - packDir.mkdirs(); - loadRadioConfig(); - } - // ========== 读取 radio_config.txt ========== - private void loadRadioConfig() { - File cfg = new File( - plugin.getDataFolder(), - "radio_config.txt"); - - // 不存在则创建默认配置 - if (!cfg.exists()) { - createRadioConfig(cfg); - plugin.getLogger().info( - "[Radio] 已生成 radio_config.txt"); - } - - // 读取 - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(cfg), - java.nio.charset - .StandardCharsets - .UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("#") - || line.trim().isEmpty()) - continue; - int eq = line.indexOf('='); - if (eq < 0) continue; - String key = line.substring(0, eq) - .trim(); - String val = line.substring(eq + 1) - .trim(); - if (key.equals("resource-pack-url")) { - externalUrl = val; - } else if (key.equals("http-port")) { - try { - httpPort = Integer.parseInt( - val); - } catch (NumberFormatException ignored) { - } - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 读取radio_config.txt失败: " - + e.getMessage()); - } - - // 443是HTTPS端口,内嵌HTTP不能用 - if (httpPort == 443) httpPort = 8080; - - plugin.getLogger().info( - "[Radio] 配置: externalUrl=[" - + externalUrl + "] httpPort=" - + httpPort); - } - - private void createRadioConfig(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - java.nio.charset - .StandardCharsets - .UTF_8))) { - pw.println("# ============ Sdf1 Radio Config ============"); - pw.println("#"); - pw.println("# resource-pack-url"); - pw.println("# 填外部地址(HTTP或HTTPS均可)"); - pw.println("# 例: https://example.com/radiopack.zip"); - pw.println("# 留空 = 使用内置HTTP服务"); - pw.println("#"); - pw.println("# http-port"); - pw.println("# 内置HTTP端口(仅resource-pack-url为空时生效)"); - pw.println("#"); - pw.println("# ============ Settings ==============="); - pw.println(); - pw.println("resource-pack-url="); - pw.println("http-port=8080"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 创建radio_config.txt失败: " - + e.getMessage()); - } - } - - // ========== 初始化 ========== - public void init() { - loadRadioConfig(); - convertNonOgg(); - loadAllOggs(); - buildResourcePack(); - startHttpServer(); // 启动HTTP - plugin.getLogger().info( - "[Radio] 初始化完成: 全服=" - + mainFiles.size() - + " 登录=" + loginFiles.size()); - } - - - // ========== 转换非ogg文件 ========== - private void convertNonOgg() { - convertDir(radioDir); - convertDir(new File(radioDir, "login")); - convertDir(new File(radioDir, "reg")); - } - - private void convertDir(File src) { - if (!src.exists() || !src.isDirectory()) return; - File[] files = src.listFiles((d, n) -> { - String l = n.toLowerCase(); - return l.endsWith(".mp3") - || l.endsWith(".wav") - || l.endsWith(".flac"); - }); - if (files == null) return; - - for (File f : files) { - String oggName = f.getName() - .replaceAll("\\.(mp3|wav|flac)$", - ".ogg"); - File oggFile = new File(src, oggName); - if (oggFile.exists() - && oggFile.lastModified() - >= f.lastModified()) { - continue; - } - // 调用ffmpeg转换 - if (convertWithFfmpeg(f, oggFile)) { - plugin.getLogger().info( - "[Radio] 转换成功: " - + f.getName()); - } else { - plugin.getLogger().warning( - "[Radio] 转换失败: " - + f.getName() - + " (需要安装ffmpeg)"); - } - } - } - - private boolean convertWithFfmpeg( - File input, File output) { - try { - ProcessBuilder pb = new ProcessBuilder( - "ffmpeg", "-y", - "-i", input.getAbsolutePath(), - "-c:a", "libvorbis", - "-q:a", "5", - output.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - boolean finished = - p.waitFor(30, TimeUnit.SECONDS); - if (!finished) { - p.destroyForcibly(); - return false; - } - return output.exists() - && output.length() > 0; - } catch (Exception e) { - return false; - } - } - - // ========== 加载ogg文件列表 ========== - private void loadAllOggs() { - mainFiles.clear(); - loginFiles.clear(); - loadOggsFlat(radioDir, mainFiles); - loadOggsFlat( - new File(radioDir, "login"), - loginFiles); - loadOggsFlat( - new File(radioDir, "reg"), - loginFiles); - } - - private void loadOggsFlat( - File dir, List list) { - if (!dir.exists() || !dir.isDirectory()) - return; - File[] files = dir.listFiles( - (d, n) -> n.toLowerCase() - .endsWith(".ogg")); - if (files == null) return; - Arrays.sort(files); - for (File f : files) list.add(f); - } - /** - * 解析 pack.png:优先读取插件目录, - * 没有则搜索图片并用ffmpeg转码 - */ - private File resolvePackIcon() { - File dataDir = plugin.getDataFolder(); - File packPng = new File(dataDir, "pack.png"); - - if (packPng.exists() - && packPng.length() > 0) { - plugin.getLogger().info( - "[Radio] 找到 pack.png (" - + packPng.length() - + " bytes)"); - return packPng; - } - - // 搜索可转换的图片文件 - String[] exts = {"png", "jpg", "jpeg", - "bmp", "webp", "tiff", "gif"}; - File[] found = dataDir.listFiles( - (dir, name) -> { - String low = name.toLowerCase(); - for (String ext : exts) { - if (low.endsWith("." + ext) - && !name.equals( - "pack.png")) { - return true; - } - } - return false; - }); - - if (found != null && found.length > 0) { - File src = found[0]; - plugin.getLogger().info( - "[Radio] 找到图片: " - + src.getName() - + " → 转码为 pack.png"); - if (convertToPackPng(src, packPng)) { - return packPng; - } - plugin.getLogger().warning( - "[Radio] 转码失败"); - return null; - } - - plugin.getLogger().warning( - "[Radio] 未找到 pack.png 或可转换图片"); - return null; - } - - /** - * ffmpeg 将任意图片转为 256x256 PNG - */ - private boolean convertToPackPng( - File input, File output) { - try { - ProcessBuilder pb = new ProcessBuilder( - "ffmpeg", "-y", - "-i", input.getAbsolutePath(), - "-vf", - "scale='min(256,iw)'" - + ":'min(256,ih)':" - + "force_original_aspect_ratio" - + "=decrease," - + "pad=256:256:" - + "(ow-iw)/2:" - + "(oh-ih)/2:" - + "color=0x00000000", - "-c:v", "png", - output.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - boolean ok = p.waitFor( - 30, TimeUnit.SECONDS); - if (!ok) { - p.destroyForcibly(); - return false; - } - return output.exists() - && output.length() > 0; - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] ffmpeg转码失败: " - + e.getMessage()); - return false; - } - } - - // ========== 资源包构建 ========== - private void buildResourcePack() { - deleteDir(packDir); - packDir.mkdirs(); - - List allOggs = new ArrayList<>(); - collectOggs(radioDir, allOggs); - - if (allOggs.isEmpty()) { - plugin.getLogger().warning( - "[Radio] radio/ 目录下" - + "无 .ogg 文件,跳过资源包"); - return; - } - - // 正确路径:assets/minecraft/sounds/custom/ - File soundsDir = new File(packDir, - "assets/minecraft/sounds/custom"); - soundsDir.mkdirs(); - - // 生成 sounds.json - StringBuilder json = - new StringBuilder("{\n"); - for (int i = 0; i < allOggs.size(); i++) { - File ogg = allOggs.get(i); - String simpleName = - ogg.getName() - .replace(".ogg", ""); - File dest = new File(soundsDir, - simpleName + ".ogg"); - try { - Files.copy(ogg.toPath(), - dest.toPath(), - StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - // ignore - } - // 音频事件名: custom.xxx - // 文件路径: custom/xxx - json.append(" \"custom.") - .append(simpleName) - .append("\": {\n"); - json.append(" \"sounds\": [{\n"); - json.append(" \"name\":" - + " \"custom/") - .append(simpleName) - .append("\",\n"); - json.append( - " \"stream\": true\n"); - json.append(" }]\n"); - json.append(" }"); - if (i < allOggs.size() - 1) - json.append(","); - json.append("\n"); - } - json.append("}"); - - // sounds.json 放在 assets/minecraft/ 下 - try { - Files.write(new File(packDir, - "assets/minecraft/sounds.json") - .toPath(), - json.toString() - .getBytes("UTF-8")); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] sounds.json写入失败"); - } -// sounds.json 写入后... - -// ===== 新增:打包 pack.png 作为资源包图标 ===== - File iconPng = resolvePackIcon(); - if (iconPng != null && iconPng.exists()) { - try { - Files.copy(iconPng.toPath(), - new File(packDir, "pack.png") - .toPath(), - StandardCopyOption - .REPLACE_EXISTING); - plugin.getLogger().info( - "[Radio] pack.png 已加入资源包"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] pack.png复制失败: " - + e.getMessage()); - } - } - - // pack.mcmeta(pack_format=54) - try { - Files.write(new File(packDir, - "pack.mcmeta").toPath(), - ("{\n \"pack\": {\n" - + " \"pack_format\": 54,\n" - + " \"description\":" - + " \"§d§l草原探险服务器资源包,请勿拒绝资源包\"\n }\n}") - .getBytes("UTF-8")); - } catch (Exception e) { - // ignore - } - - try { - File zipFile = new File( - plugin.getDataFolder(), - "radiopack.zip"); - packZip(packDir, zipFile); - plugin.getLogger().info( - "[Radio] 资源包生成: " - + allOggs.size() + " 个音频"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] zip打包失败: " - + e.getMessage()); - } - } - - private void deleteDir(File dir) { - if (!dir.exists()) return; - File[] files = dir.listFiles(); - if (files != null) { - for (File f : files) { - if (f.isDirectory()) deleteDir(f); - else f.delete(); - } - } - dir.delete(); - } - - private void collectOggs( - File dir, List list) { - File[] files = dir.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) - collectOggs(f, list); - else if (f.getName().endsWith(".ogg")) - list.add(f); - } - } - - private void packZip(File root, - File zipFile) throws Exception { - try (ZipOutputStream zos = - new ZipOutputStream( - new FileOutputStream( - zipFile))) { - zipDir(root, root, zos); - } - } - - private void zipDir(File root, - File current, - ZipOutputStream zos) - throws Exception { - File[] files = current.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) { - zipDir(root, f, zos); - } else { - String path = root.toURI() - .relativize(f.toURI()) - .getPath(); - zos.putNextEntry(new ZipEntry(path)); - zos.write(Files.readAllBytes( - f.toPath())); - zos.closeEntry(); - } - } - } - - // ========== 资源包发送 ========== - - /** - * 发送资源包(带限流,不禁重试) - */ - private final Map lastSendTime = - new ConcurrentHashMap<>(); - - public void sendResourcePack(Player p) { - UUID uuid = p.getUniqueId(); - - // 最短间隔 5秒(不是10秒的全锁死) - Long last = lastSendTime.get(uuid); - if (last != null - && System.currentTimeMillis() - last - < 5000L) { - plugin.getLogger().info( - "[Radio] 跳过(5秒内): " - + p.getName()); - return; - } - lastSendTime.put(uuid, - System.currentTimeMillis()); - - String url = getResourcePackUrl(); - if (url == null || url.isEmpty()) { - plugin.getLogger().warning( - "[Radio] URL为空,跳过: " - + p.getName()); - return; - } - - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) { - plugin.getLogger().warning( - "[Radio] radiopack.zip不存在," - + "跳过: " + p.getName()); - return; - } - - try { - byte[] data = - Files.readAllBytes(zip.toPath()); - String hash = sha1(data); - long fileSize = zip.length(); - - plugin.getLogger().info( - "[Radio] 发送: " + p.getName() - + " url=" + url - + " size=" + fileSize - + " hash=" + hash); - - sendPackWithHash(p, url, hash); - - } catch (Exception e) { - plugin.getLogger().severe( - "[Radio] 发送异常: " - + p.getName()); - e.printStackTrace(); - } - } - - - - private void sendPackWithHash( - Player p, - String url, - String hash) { - - UUID uuid = UUID.fromString( - "a1b2c3d4-e5f6-7890-abcd-" - + "ef1234567890"); - - // 尝试5参 + Component - try { - Object component = - net.kyori.adventure.text - .Component.text( - "§d§l请下载自定义资源包"); - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class, - net.kyori.adventure - .text.Component - .class, - boolean.class) - .invoke( - p, uuid, url, - hash, component, - true); - plugin.getLogger().info( - "[Radio] 成功(5参): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // 尝试5参 + String描述 - try { - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class, - String.class, - boolean.class) - .invoke( - p, uuid, url, - hash, - "Sdf1 Radio", - true); - plugin.getLogger().info( - "[Radio] 成功(5参String): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // 尝试3参 - try { - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class) - .invoke( - p, uuid, url, - hash); - plugin.getLogger().info( - "[Radio] 成功(3参): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // ★ 全部失败 → 通知玩家 + 熔断 - plugin.getLogger().severe( - "[Radio] 所有签名失败: " - + p.getName() - + " 客户端版本: " - + p.getProtocolVersion()); - p.sendMessage( - "§e§l[Radio] §f资源包发送失败," - + "§7部分功能可能异常"); - p.sendMessage( - "§7可尝试: §e/rejoin §7或重启客户端"); - } - - - - private void sendPackFromLocal( - Player p, String url) { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) return; - try { - byte[] data = - Files.readAllBytes( - zip.toPath()); - String hash = sha1(data); - sendPackWithHash(p, url, hash); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 降级发送失败"); - } - } - - // 从URL下载字节 - private byte[] downloadBytes(String urlStr) - throws Exception { - java.net.URL url = - new java.net.URL(urlStr); - java.net.HttpURLConnection conn = - (java.net.HttpURLConnection) - url.openConnection(); - conn.setConnectTimeout(10000); - conn.setReadTimeout(30000); - try (java.io.InputStream is = - conn.getInputStream()) { - return is.readAllBytes(); - } - } - - // SHA-1计算 - private String sha1(byte[] data) - throws Exception { - java.security.MessageDigest md = - java.security.MessageDigest - .getInstance("SHA-1"); - md.update(data); - byte[] hash = md.digest(); - StringBuilder sb = - new StringBuilder(); - for (byte b : hash) - sb.append(String.format( - "%02x", b)); - return sb.toString(); - } - - public void clearPending(UUID uuid) { - lastSendTime.remove(uuid); - } - - - - // ========== 内嵌HTTP服务 ========== - private com.sun.net.httpserver.HttpServer - httpServer; - - private void startHttpServer() { - // 如果配置了外部地址则不启动 - if (plugin.getConfig2() != null - && !plugin.getConfig2() - .broadcastServerUrl - .isEmpty()) { - return; - } - try { - int port = 8080; - if (plugin.getConfig2() != null) { - port = plugin.getConfig2().httpPort; - } - httpServer = - com.sun.net.httpserver - .HttpServer.create( - new java.net - .InetSocketAddress( - port), 0); - httpServer.createContext( - "/radiopack.zip", - exchange -> { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (zip.exists()) { - byte[] data = - Files.readAllBytes( - zip.toPath()); - exchange.getResponseHeaders() - .set("Content-Type", - "application/zip"); - exchange.getResponseHeaders() - .set("Access-Control" - + "-Allow-Origin", - "*"); - exchange.sendResponseHeaders( - 200, data.length); - exchange.getResponseBody() - .write(data); - exchange.getResponseBody() - .close(); - } else { - byte[] empty = - "not found".getBytes(); - exchange.sendResponseHeaders( - 404, empty.length); - exchange.getResponseBody() - .write(empty); - exchange.getResponseBody() - .close(); - } - }); - httpServer.setExecutor(null); - httpServer.start(); - plugin.getLogger().info( - "[Radio] HTTP服务: http://0.0.0.0:" - + port + "/radiopack.zip"); - } catch (Exception e) { - plugin.getLogger().severe( - "[Radio] HTTP启动失败: " - + e.getMessage()); - } - } - - private void stopHttpServer() { - if (httpServer != null) { - httpServer.stop(0); - httpServer = null; - } - } - - public String getResourcePackUrl() { - // 有外部配置 → 随机生成URL - if (externalUrl != null - && !externalUrl.trim().isEmpty()) { - return generateRandomUrl(externalUrl); - } - // 无配置 → 用内置HTTP - int port = 8080; - if (plugin.getConfig2() != null) { - port = plugin.getConfig2().httpPort; - } - return "http://127.0.0.1:" - + port + "/radiopack.zip"; - } - - - /** - * 从配置的域名列表中随机选一个 - * 生成随机、带时间戳的完整资源包URL - * - * 规则: - * *.example.com - * → 随机子域+时间戳 - * 例: a3f7k2.example.com/pack.zip?t=xxx&v=b9c1e4 - * - * cdn.example.com (无*) - * → 直接使用+时间戳 - * 例: cdn.example.com/pack.zip?t=xxx - */ - private String generateRandomUrl( - String raw) { - String[] parts = raw.split("\\|"); - String pick = - parts[(int)(Math.random() - * parts.length)].trim(); - - // 去掉协议 https:// http:// - String cleaned = pick - .replaceFirst("^https?://", ""); - // 去掉路径 /path/a?v=1 - if (cleaned.contains("/")) { - cleaned = cleaned.substring( - 0, cleaned.indexOf('/')); - } - - // 随机子域 - String hex = Long.toHexString( - Double.doubleToLongBits( - Math.random())); - String rand = hex.length() > 6 - ? hex.substring(2, 8) : hex; - rand = rand.replaceAll( - "[^a-z0-9]", "x"); - - String host; - if (cleaned.startsWith("*")) { - host = rand - + cleaned.substring(1); - } else { - host = cleaned; - } - - String ts = String.valueOf( - System.currentTimeMillis()); - String token = Integer.toHexString( - (int)(Math.random() - * 0xFFFFFF)); - - String url = "https://" + host - + "/radiopack.zip" - + "?t=" + ts - + "&v=" + token; - - plugin.getLogger().info( - "[Radio] 生成URL: " - + pick + " → " + url); - return url; - } - - - - // ========== 全服广播 ========== - public void startMainRadio() { - stopMainRadio(); - if (mainFiles.isEmpty()) { - plugin.getLogger().warning( - "[Radio] 无 .ogg 文件"); - return; - } - plugin.getLogger().info( - "[Radio] 启动全服广播 (" - + mainFiles.size() + " 个文件)"); - mainRadioTask = new BukkitRunnable() { - @Override - public void run() { - playNext(); - } - }.runTaskTimer(plugin, 100L, 2400L); - } - - private void playNext() { - if (mainFiles.isEmpty()) return; - File file = mainFiles.get( - radioIndex % mainFiles.size()); - String name = file.getName() - .replace(".ogg", ""); - lastPlayedSound = "custom." + name; - String key = "minecraft:" + lastPlayedSound; - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" + name); - radioIndex++; - } - - // ========== 按名称播放 ========== - public void playToAll(String name) { - String key = findSongKey(name); - if (key == null) return; - lastPlayedSound = "custom." + name; - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" + name); - } - - - public void playToPlayer( - Player p, String name) { - String key = findSongKey(name); - if (key == null) { - p.sendMessage("§c未找到: " + name); - return; - } - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - - public void playToLoginPlayers(String name) { - String key = findSongKey(name); - if (key == null) return; - for (String pName - : loginPlayers.keySet()) { - Player lp = Bukkit.getPlayer(pName); - if (lp != null && lp.isOnline()) { - lp.playSound(lp.getLocation(), - key, - SoundCategory.RECORDS, - 1.5f, 1.0f); - } - } - } - - - private String findSongKey(String name) { - List all = new ArrayList<>(); - collectOggs(radioDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - return "minecraft:custom." + name; - } - } - plugin.getLogger().warning( - "[Radio] 未找到歌曲: " + name); - return null; - } - - - public boolean hasSong(String name) { - return findSongKey(name) != null; - } - - // ========== 手动播放 ========== - public boolean playSpecific( - Player sender, String name) { - List all = new ArrayList<>(); - collectOggs(radioDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - String key = "sdf1radio:" + name; - for (Player p - : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), - key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" - + name); - return true; - } - } - if (sender != null) - sender.sendMessage("§c未找到: " + name); - return false; - } - public void stopMainRadio() { - if (mainRadioTask != null) { - mainRadioTask.cancel(); - mainRadioTask = null; - } - if (lastPlayedSound != null - && !lastPlayedSound.isEmpty()) { - for (Player p : Bukkit.getOnlinePlayers()) { - p.stopSound(lastPlayedSound, - SoundCategory.RECORDS); - } - lastPlayedSound = ""; - } - } - -// 在 init() 或 registerEvents 中保存引用: -// this.listener = new RadioDownloadListener(plugin); -private RadioDownloadListener radioListener; - - - - // ========== 重载 ========== - public void reload() { - stopMainRadio(); - stopAllLoginRadio(); - stopHttpServer(); - loadAllOggs(); - buildResourcePack(); - startHttpServer(); - loadRadioConfig(); - } -} \ No newline at end of file diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/RefundRecord.java b/backup/20260605_152529/src/main/java/Sdf1_login/RefundRecord.java deleted file mode 100644 index c0a3070..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/RefundRecord.java +++ /dev/null @@ -1,62 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; -import java.util.UUID; - -public class RefundRecord { - public final UUID playerUUID; - public final String playerName; - public final String itemId; - public final String itemName; - public final Material material; - public final Material shulkerMaterial; - public int amount; - public int totalPaid; - public final long timestamp; - - // 普通购买 - public RefundRecord(UUID uuid, String name, - String itemId, String itemName, - Material material, int amount, - int totalPaid, long timestamp) { - this.playerUUID = uuid; - this.playerName = name; - this.itemId = itemId; - this.itemName = itemName; - this.material = material; - this.shulkerMaterial = null; - this.amount = amount; - this.totalPaid = totalPaid; - this.timestamp = timestamp; - } - - // 盒装购买 - public RefundRecord(UUID uuid, String name, - String itemId, String itemName, - Material material, - Material shulkerMat, - int amount, int totalPaid, - long timestamp) { - this.playerUUID = uuid; - this.playerName = name; - this.itemId = itemId; - this.itemName = itemName; - this.material = material; - this.shulkerMaterial = shulkerMat; - this.amount = amount; - this.totalPaid = totalPaid; - this.timestamp = timestamp; - } - - public boolean isExpired() { - return System.currentTimeMillis() - - timestamp > 300000L; - } - - public int getSecondsLeft() { - long left = 300000L - - (System.currentTimeMillis() - - timestamp); - return (int) Math.max(0, left / 1000); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/RiskControlManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/RiskControlManager.java deleted file mode 100644 index 4ddedcc..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/RiskControlManager.java +++ /dev/null @@ -1,248 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 风控管理器:检测异常登录行为,记录尝试次数,触发安全报警 - */ -public class RiskControlManager { - - private final Main plugin; - - // 每个玩家名 -> 连续失败次数 - private final Map failAttempts = - new ConcurrentHashMap<>(); - - // 每个IP -> 关联的可疑玩家列表 - private final Map> ipSuspicious = - new ConcurrentHashMap<>(); - - // 每个玩家 -> 上次验证时间(用于冷却) - private final Map verifyCooldown = - new ConcurrentHashMap<>(); - - // 配置项 - private static final int MAX_FAIL_ATTEMPTS = 5; - private static final int MAX_ACCOUNTS_PER_IP = 5; - private static final long COOLDOWN_MS = 60000L; - - public RiskControlManager(Main plugin) { - this.plugin = plugin; - } - - /** - * 记录一次失败的登录尝试 - * - * @param playerName 玩家名 - * @return 剩余尝试次数 - */ - public int recordFailAttempt(String playerName) { - int current = failAttempts - .getOrDefault(playerName, 0) + 1; - failAttempts.put(playerName, current); - return Math.max(0, MAX_FAIL_ATTEMPTS - current); - } - - /** - * 重置指定玩家的失败计数(登录成功时调用) - */ - public void resetFailAttempts(String playerName) { - failAttempts.remove(playerName); - } - - /** - * 获取指定玩家的失败次数 - */ - public int getFailAttempts(String playerName) { - return failAttempts - .getOrDefault(playerName, 0); - } - - /** - * 判断玩家是否已被锁定(失败次数达到上限) - */ - public boolean isLocked(String playerName) { - return failAttempts - .getOrDefault(playerName, 0) - >= MAX_FAIL_ATTEMPTS; - } - - /** - * 检查IP是否达到注册上限 - * 直接查数据库,不依赖 DatabaseManager 的方法 - */ - public boolean canRegisterFromIP(String ip) { - if (ip == null || ip.isEmpty()) return true; - try { - java.sql.Connection conn = - plugin.getDb().getDb(); - if (conn == null) return true; - java.sql.PreparedStatement ps = - conn.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address = ?"); - ps.setString(1, ip); - java.sql.ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count < 5; - } catch (Exception e) { - e.printStackTrace(); - return true; - } - } - - - /** - * 记录IP关联的可疑行为 - */ - public void recordSuspiciousIP(String ip, - String playerName) { - if (ip == null || ip.isEmpty()) return; - ipSuspicious - .computeIfAbsent(ip, - k -> ConcurrentHashMap.newKeySet()) - .add(playerName); - } - - /** - * 获取某IP关联的所有可疑玩家 - */ - public Set getSuspiciousAccounts(String ip) { - return ipSuspicious - .getOrDefault(ip, Collections.emptySet()); - } - - /** - * 检查是否在验证冷却期内 - */ - public boolean isOnCooldown(String playerName) { - Long last = verifyCooldown.get(playerName); - if (last == null) return false; - return System.currentTimeMillis() - last - < COOLDOWN_MS; - } - - /** - * 设置验证冷却时间 - */ - public void setCooldown(String playerName) { - verifyCooldown.put(playerName, - System.currentTimeMillis()); - } - - /** - * 当检测到可疑登录时触发报警 - * - 全服广播 - * - 有邮箱则发邮件 - * - 记录到数据库 - */ - public void triggerAlert(Player player, - String suspiciousIP) { - String name = player.getName(); - - // 记录到数据库 - plugin.getDb().recordSecurityAlert( - name, suspiciousIP); - - // 记录IP可疑行为 - recordSuspiciousIP(suspiciousIP, name); - - // 全服广播 - String serverMsg = "§c§l[安全警告] §e" + name - + " §f的账号疑似遭到 §c" + suspiciousIP - + " §f的盗号尝试, 请各位玩家注意账号安全!"; - Bukkit.broadcastMessage(serverMsg); - - // 有邮箱则发邮件 - String emailAddr = (String) plugin.getDb() - .getField(name, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String subject = - "[安全警告] 您的账号疑似遭到盗号尝试"; - String body = "尊敬的玩家 " + name - + ":\n\n" - + "您的账号在 " - + new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()) - + " 疑似遭到 IP: " + suspiciousIP - + " 的盗号尝试。\n\n" - + "建议您:\n" - + "1. 立即修改密码" - + "(使用 /sdf1_login pw)\n" - + "2. 如非本人操作," - + "请联系管理员处理\n\n" - + "--Sdf1_login 安全系统"; - plugin.getEmail().sendTempPassword( - emailAddr, name, body); - } - } - - /** - * 处理登录失败时的风控逻辑 - * - * @param player 玩家 - * @param suspiciousIP 可疑来源IP - * @return true=已被锁定(应踢出), false=仍可继续尝试 - */ - public boolean handleLoginFailure(Player player, - String suspiciousIP) { - String name = player.getName(); - int remaining = recordFailAttempt(name); - - if (remaining <= 0) { - // 达到上限,触发报警 - triggerAlert(player, suspiciousIP); - player.kickPlayer( - "§c§l[风控] §f登录失败次数过多," - + " 账号已被临时锁定, 请联系管理员"); - return true; - } - - // 未达上限,提示剩余次数 - player.sendMessage( - "§c§l[风控] §f密码错误! " - + "剩余" + remaining + "次机会"); - return false; - } - - /** - * 登录成功后重置风控状态 - */ - public void onLoginSuccess(String playerName) { - resetFailAttempts(playerName); - } - - /** - * 清理过期的冷却记录(可由定时任务调用) - */ - public void cleanup() { - long now = System.currentTimeMillis(); - verifyCooldown.entrySet().removeIf( - e -> now - e.getValue() > COOLDOWN_MS * 10); - } - - /** - * 获取所有玩家的失败尝试记录(调试用) - */ - public Map getAllFailAttempts() { - return Collections.unmodifiableMap(failAttempts); - } - - /** - * 清除所有风控数据(管理员调试用) - */ - public void clearAll() { - failAttempts.clear(); - ipSuspicious.clear(); - verifyCooldown.clear(); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/SalesStatsManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/SalesStatsManager.java deleted file mode 100644 index 9d06a22..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/SalesStatsManager.java +++ /dev/null @@ -1,170 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class SalesStatsManager { - - private final Main plugin; - - private final Map sessionSales = - new ConcurrentHashMap<>(); - private int sessionRevenue = 0; - private int sessionOrders = 0; - private int sessionItems = 0; - private int sessionRefunds = 0; - private int sessionRefundAmount = 0; - private long sessionStart = System.currentTimeMillis(); - - // 状态 - private boolean waitingForEmpty = false; - private boolean hasReported = false; - private BukkitRunnable waitTask = null; - - public SalesStatsManager(Main plugin) { - this.plugin = plugin; - startMonitor(); - } - - // ★ 每10秒检测一次 - private void startMonitor() { - new BukkitRunnable() { - @Override - public void run() { - int online = Bukkit.getOnlinePlayers().size(); - - if (online > 0) { - // 有玩家在线 → 重置等待状态 - if (waitingForEmpty) { - // 刚有人进来了,清除已汇报标记 - // 下次没人时会重新汇报 - } - waitingForEmpty = false; - hasReported = false; - cancelWaitTask(); - } else if (!waitingForEmpty && !hasReported) { - // 没人了,且还没汇报过 → 开始1分钟倒计时 - waitingForEmpty = true; - startWaitCountdown(); - } - } - }.runTaskTimer(plugin, 200L, 200L); - } - - // ★ 1分钟等待 - private void startWaitCountdown() { - cancelWaitTask(); - waitTask = new BukkitRunnable() { - @Override - public void run() { - int online = Bukkit.getOnlinePlayers().size(); - - if (online > 0) { - // 等待期间有人进来 → 取消,等下次没人 - waitingForEmpty = false; - hasReported = false; - waitTask = null; - cancel(); - return; - } - - // 1分钟没人 → 汇报一次 - if (!hasReported) { - hasReported = true; - report(); - } - waitingForEmpty = false; - waitTask = null; - cancel(); - } - }; - waitTask.runTaskLater(plugin, 1200L); // 60秒 - } - - private void cancelWaitTask() { - if (waitTask != null) { - waitTask.cancel(); - waitTask = null; - } - } - - // ===== 记录 ===== - - public void recordSale(String itemName, int price) { - sessionSales.merge(itemName, 1, Integer::sum); - sessionRevenue += price; - sessionOrders++; - sessionItems++; - } - - public void recordRefund(int amount) { - sessionRefunds++; - sessionRefundAmount += amount; - } - - // ===== 汇报 ===== - - public void report() { - if (sessionItems == 0 && sessionRefunds == 0) { - // plugin.getLogger().info("[Sales] 无数据,跳过"); - return; - } - - long minutes = - (System.currentTimeMillis() - sessionStart) - / 60000; - if (minutes < 1) minutes = 1; - - StringBuilder sb = new StringBuilder(); - sb.append("§6§l========== 销量汇总 =========="); - sb.append("\n§7时长: §f").append(minutes).append("分钟"); - sb.append("\n§7订单: §f").append(sessionOrders).append("笔"); - sb.append("\n§7商品: §f").append(sessionItems).append("件"); - sb.append("\n§7成交额: §6").append(sessionRevenue).append("枚"); - sb.append("\n§7退款: §c").append(sessionRefunds) - .append("次(§c").append(sessionRefundAmount) - .append("枚§7)"); - sb.append("\n§6§l------------------------------"); - - if (!sessionSales.isEmpty()) { - sb.append("\n§7销量排行:"); - List> sorted = - new ArrayList<>(sessionSales.entrySet()); - sorted.sort((a, b) -> - b.getValue() - a.getValue()); - int rank = 1; - for (Map.Entry e : sorted) { - if (rank > 10) break; - sb.append("\n§e").append(rank).append(". ") - .append(e.getKey()) - .append(" §7x").append(e.getValue()); - rank++; - } - } - sb.append("\n§6§l=============================="); - - String msg = sb.toString(); - for (Player p : Bukkit.getOnlinePlayers()) { - if (p.isOp()) { - for (String line : msg.split("\n")) - p.sendMessage(line); - } - } - plugin.getLogger().info("[Sales] 汇报完成"); - resetSession(); - } - - public void resetSession() { - sessionSales.clear(); - sessionRevenue = 0; - sessionOrders = 0; - sessionItems = 0; - sessionRefunds = 0; - sessionRefundAmount = 0; - sessionStart = System.currentTimeMillis(); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/ShopCategory.java b/backup/20260605_152529/src/main/java/Sdf1_login/ShopCategory.java deleted file mode 100644 index 715e9ad..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/ShopCategory.java +++ /dev/null @@ -1,32 +0,0 @@ -package Sdf1_login; - -import java.util.ArrayList; -import java.util.List; - -public class ShopCategory { - private final String name; - private final String fileName; - private final List items; - - public ShopCategory(String name, String fileName) { - this.name = name; - this.fileName = fileName; - this.items = new ArrayList<>(); - } - - public String getName() { return name; } - public String getFileName() { return fileName; } - public List getItems() { return items; } - - public ShopItem getItem(String id) { - for (ShopItem item : items) { - if (item.getId().equalsIgnoreCase(id)) - return item; - } - return null; - } - - public void addItem(ShopItem item) { - items.add(item); - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/ShopItem.java b/backup/20260605_152529/src/main/java/Sdf1_login/ShopItem.java deleted file mode 100644 index d336c7c..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/ShopItem.java +++ /dev/null @@ -1,49 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; - -public class ShopItem { - private final String id; - private final String displayName; - private final Material material; - private final int buyPrice; - private final int sellPrice; - private int stock; - private int hourlySales; - private int totalSales; - public ShopItem(String id, String displayName, - Material material, int buyPrice, - int sellPrice, int stock, - int hourlySales, int totalSales) { - this.id = id; - this.displayName = displayName; - this.material = material; - this.buyPrice = buyPrice; - this.sellPrice = sellPrice; - this.stock = stock; - this.hourlySales = hourlySales; - this.totalSales = totalSales; - } - - public int getEffectiveSellPrice() { - if (sellPrice <= 0) return 0; - return sellPrice; - } - - public String getId() { return id; } - public String getDisplayName() { return displayName; } - public Material getMaterial() { return material; } - public int getBuyPrice() { return buyPrice; } - public int getSellPrice() { return sellPrice; } - public int getStock() { return stock; } - public int getHourlySales() { return hourlySales; } - public int getTotalSales() { return totalSales; } - - public void setStock(int s) { this.stock = s; } - public void setHourlySales(int s) { this.hourlySales = s; } - public void setTotalSales(int s) { this.totalSales = s; } - public void addSales(int n) { - this.hourlySales += n; - this.totalSales += n; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/ShopManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/ShopManager.java deleted file mode 100644 index cdcc045..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/ShopManager.java +++ /dev/null @@ -1,2610 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.Base64; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.inventory.meta.PotionMeta; - - - - -public class ShopManager implements Listener { - - private final Main plugin; - private final List categories = - new ArrayList<>(); - private final List refundRecords = - new ArrayList<>(); - private final Map activeDiscount = - new HashMap<>(); - private final Map viewCat = - new HashMap<>(); - private final Map viewPage = - new HashMap<>(); - private final Map> shoppingCarts = - new HashMap<>(); - private final Set cartModeEnabled = - new HashSet<>(); - private final java.util.Random rng = - new java.util.Random(); - private final Map> pendingCheckout = - new HashMap<>(); - private final Set couponListening = - new HashSet<>(); - private final Map pendingCouponCode = - new HashMap<>(); - // ===== 物品NBT数据 ===== - private final Map itemNbtData = - new HashMap<>(); - private final Map itemStackCache = - new HashMap<>(); - // 替换字段 - private final Map couponApplyTime = new HashMap<>(); - - - - private static final Material[] SHULKER_COLORS = { - Material.SHULKER_BOX, - Material.WHITE_SHULKER_BOX, - Material.ORANGE_SHULKER_BOX, - Material.MAGENTA_SHULKER_BOX, - Material.LIGHT_BLUE_SHULKER_BOX, - Material.YELLOW_SHULKER_BOX, - Material.LIME_SHULKER_BOX, - Material.PINK_SHULKER_BOX, - Material.GRAY_SHULKER_BOX, - Material.LIGHT_GRAY_SHULKER_BOX, - Material.CYAN_SHULKER_BOX, - Material.PURPLE_SHULKER_BOX, - Material.BLUE_SHULKER_BOX, - Material.BROWN_SHULKER_BOX, - Material.GREEN_SHULKER_BOX, - Material.RED_SHULKER_BOX, - Material.BLACK_SHULKER_BOX - }; - private static final Map EFFECT_MAP = new HashMap<>(); - static { - // ===== 基础效果 ===== - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("速度", PotionEffectType.SPEED); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); // 1.21+ 改名 - EFFECT_MAP.put("治疗", PotionEffectType.INSTANT_HEALTH); // 1.21+ 改名 - EFFECT_MAP.put("治愈", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("伤害", PotionEffectType.INSTANT_DAMAGE); // 1.21+ 改名 - EFFECT_MAP.put("剧毒", PotionEffectType.POISON); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("回复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("再生", PotionEffectType.REGENERATION); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性", PotionEffectType.RESISTANCE); // 1.21+ 改名 - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); // 1.21+ 改名 - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); // 1.21+ 改名 - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); // ★ 补充 - EFFECT_MAP.put("迟缓", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("不祥", PotionEffectType.UNLUCK); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("生命加成", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("海豚关怀", PotionEffectType.DOLPHINS_GRACE); - EFFECT_MAP.put("跳跃", PotionEffectType.JUMP_BOOST); - // ★ 神龟药水是双效果,特殊处理,这里只做名称匹配入口 - EFFECT_MAP.put("神龟", PotionEffectType.RESISTANCE); - } - - public static class CartItem { - String itemId; - String displayName; - Material material; - int unitPrice; - int quantity; - } - - - private static final int PAGE_SIZE = 28; - private static final int[] ITEM_SLOTS = { - 10,11,12,13,14,15,16, - 19,20,21,22,23,24,25, - 28,29,30,31,32,33,34, - 37,38,39,40,41,42,43 - }; - - public ShopManager(Main plugin) { - this.plugin = plugin; - loadCategories(); - startHourlyTask(); - loadItemNbtData(); - startCleanupTask(); - } - // ===== 切换购物车模式 ===== - // ===== 切换购物车模式 ===== - public boolean toggleCartMode(Player p) { - UUID uuid = p.getUniqueId(); - if (cartModeEnabled.contains(uuid)) { - cartModeEnabled.remove(uuid); - p.sendMessage("§7购物车模式已关闭,恢复正常购买"); - return false; - } else { - cartModeEnabled.add(uuid); - p.sendMessage("§a购物车模式已开启,点击商品加入购物车"); - return true; - } - } - - public boolean isCartMode(Player p) { - return cartModeEnabled.contains(p.getUniqueId()); - } - private String itemToBase64(ItemStack item) { - try { - ByteArrayOutputStream os = - new ByteArrayOutputStream(); - BukkitObjectOutputStream bos = - new BukkitObjectOutputStream(os); - bos.writeObject(item); - bos.close(); - return Base64.getEncoder() - .encodeToString(os.toByteArray()); - } catch (Exception e) { - return ""; - } - } - - private ItemStack base64ToItem(String b64) { - try { - byte[] data = - Base64.getDecoder().decode(b64); - ByteArrayInputStream is = - new ByteArrayInputStream(data); - BukkitObjectInputStream bis = - new BukkitObjectInputStream(is); - ItemStack item = - (ItemStack) bis.readObject(); - bis.close(); - return item; - } catch (Exception e) { - return null; - } - } - - private void loadItemNbtData() { - itemNbtData.clear(); - itemStackCache.clear(); - File f = new File(plugin.getDataFolder(), - "shop/item_nbt.properties"); - if (!f.exists()) return; - try { - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() - || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq < 0) continue; - String id = - line.substring(0, eq).trim(); - String b64 = - line.substring(eq + 1).trim(); - itemNbtData.put(id, b64); - ItemStack stack = base64ToItem(b64); - if (stack != null) { - itemStackCache.put(id, stack); - } - } - br.close(); - // plugin.getLogger().info("[Shop] NBT数据: " - // + itemStackCache.size() + "条"); - } catch (Exception ignored) {} - } - - private void saveItemNbtData() { - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - File f = new File(dir, "item_nbt.properties"); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 商品完整数据(自动生成)"); - for (Map.Entry e - : itemNbtData.entrySet()) { - pw.println(e.getKey() + "=" - + e.getValue()); - } - pw.flush(); - pw.close(); - } catch (Exception ignored) {} - } - - // ===== 加入/移除购物车 ===== - public boolean addToCart(Player p, ShopItem item) { - UUID uuid = p.getUniqueId(); - List cart = shoppingCarts - .computeIfAbsent( - uuid, k -> new ArrayList<>()); - // ★ 始终用原价,折扣在结算时统一应用 - int price = item.getBuyPrice(); - for (CartItem ci : cart) { - if (ci.itemId.equals(item.getId())) { - ci.quantity += 1; - p.sendMessage("§a+1 §e" - + item.getDisplayName() - + " §7购物车共§f" - + getCartCount(p) - + "件 §6需" - + getCartTotal(p) + "枚"); - return true; - } - } - CartItem ci = new CartItem(); - ci.itemId = item.getId(); - ci.displayName = item.getDisplayName(); - ci.material = item.getMaterial(); - ci.unitPrice = price; - ci.quantity = 1; - cart.add(ci); - p.sendMessage("§a+1 §e" - + item.getDisplayName() - + " §7购物车共§f" - + getCartCount(p) - + "件 §6需" - + getCartTotal(p) + "枚"); - return true; - } - - - public int getCartCount(Player p) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null) return 0; - int c = 0; - for (CartItem ci : cart) c += ci.quantity; - return c; - } - - public int getCartTotal(Player p) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null) return 0; - int t = 0; - for (CartItem ci : cart) - t += ci.unitPrice * ci.quantity; - return t; - } - - public void cartAddOne(Player p, int index) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null || index < 0 - || index >= cart.size()) return; - cart.get(index).quantity += 1; - } - - public void cartRemoveOne(Player p, int index) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null || index < 0 - || index >= cart.size()) return; - CartItem ci = cart.get(index); - ci.quantity -= 1; - if (ci.quantity <= 0) cart.remove(index); - } - - public void clearCart(Player p) { - shoppingCarts.remove(p.getUniqueId()); - } - - // ===== 打开购物车页面 ===== - public void openCart(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§a§l购物车"); - List cart = shoppingCarts - .getOrDefault(p.getUniqueId(), - new ArrayList<>()); - for (int i = 0; i < cart.size() && i < 25; i++) { - CartItem ci = cart.get(i); - ItemStack is = new ItemStack(ci.material); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e" + ci.displayName - + " §7x" + ci.quantity); - im.setLore(java.util.Arrays.asList( - "§7单价: §f" + ci.unitPrice + "枚", - "§7小计: §6" - + (ci.unitPrice * ci.quantity) - + "枚", - "", - "§a左键+1 §c右键-1")); - is.setItemMeta(im); - } - g.setItem(10 + i, is); - } - if (cart.isEmpty()) { - g.setItem(22, mkItem(Material.BARRIER, - "§7购物车为空")); - } - int total = getCartTotal(p); - int bal = plugin.getBonds().getBonds(p.getName()); - g.setItem(49, mkItem(Material.EMERALD, - "§6§l结算: §e" + total + " §6枚债券", - "§7余额: §f" + bal + "枚", - bal >= total ? "§a余额充足" : "§c余额不足", - "", "§a点击结算")); - g.setItem(48, mkItem( - Material.LIME_STAINED_GLASS_PANE, - "§7清空购物车")); - g.setItem(53, mkItem(Material.ARROW, "§7返回商店")); - - p.openInventory(g); - } - - // ===== 购物车点击处理 ===== - public boolean handleCartClick(Player p, int raw, - boolean left, - boolean right) { - if (raw == 53) { openShopMain(p); return true; } - if (raw == 48) { - clearCart(p); - p.sendMessage("§7购物车已清空"); - openCart(p); - return true; - } - if (raw == 49) { - checkoutCartInit(p); - return true; - } - if (raw >= 10 && raw < 35) { - int idx = raw - 10; - List cart = shoppingCarts - .getOrDefault(p.getUniqueId(), - new ArrayList<>()); - if (idx < cart.size()) { - if (right) cartRemoveOne(p, idx); - else cartAddOne(p, idx); - openCart(p); - } - return true; - } - return true; - } - - // ===== 结算入口:判断打包方式 ===== - private void checkoutCartInit(Player p) { - UUID uuid = p.getUniqueId(); - List cart = shoppingCarts.get(uuid); - if (cart == null || cart.isEmpty()) { - p.sendMessage("§c购物车为空"); - return; - } - int total = getCartTotal(p); - int bal = plugin.getBonds() - .getBonds(p.getName()); - if (bal < total) { - p.sendMessage("§c余额不足,需要 §e" - + total + "§c枚"); - return; - } - int totalQty = 0; - for (CartItem ci : cart) totalQty += ci.quantity; - if (cart.size() == 1 && totalQty <= 64) { - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - double factor = 1.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - deductAndGive(p, cart, - (int) Math.round(total * factor), - false, null, "", 0, - ecoPct, couponPct); - return; - } - pendingCheckout.put(uuid, - new ArrayList<>(cart)); - openPackingConfirm(p, total); - } - - - - // ===== 执行扣款+发货 ===== - private boolean deductAndGive(Player p, - List cart, int baseTotal, - boolean useShulker, Material color, - String packNote, int packFee, - int ecoPct, int couponPct) { - - UUID uuid = p.getUniqueId(); - int originalTotal = 0; - for (CartItem ci : cart) - originalTotal += ci.unitPrice * ci.quantity; - - String[] names = new String[cart.size()]; - int[] subs = new int[cart.size()]; - for (int i = 0; i < cart.size(); i++) { - CartItem ci = cart.get(i); - names[i] = ci.displayName; - subs[i] = ci.unitPrice * ci.quantity; - } - - if (baseTotal < originalTotal) { - double ratio = - (double) baseTotal / originalTotal; - int sum = 0; - for (int i = 0; i < subs.length; i++) { - if (i == subs.length - 1) { - subs[i] = baseTotal - sum; - } else { - subs[i] = (int) (subs[i] * ratio); - sum += subs[i]; - } - } - } - - boolean ok = plugin.getBonds().checkoutCart( - p.getName(), names, subs); - if (!ok) { - p.sendMessage("§c扣款失败"); - return false; - } - - if (packFee > 0) { - plugin.getBonds().deductBonds( - p.getName(), packFee, - "shop_packing", "", - "商店系统", "打包费"); - } - - for (CartItem ci : cart) { - ShopItem si = findItemById(ci.itemId); - if (si != null) { - if (si.getStock() >= 0) - si.setStock(si.getStock() - - ci.quantity); - si.addSales(ci.quantity); - } - } - - int newBal = plugin.getBonds() - .getBonds(p.getName()); - - - // ===== 优惠券码 ===== - String usedCouponCode = - pendingCouponCode.get(uuid); - - // ===== 记录订单 ===== - OrderManager.OrderRecord latestOrder = null; - try { - List orderItems = - new ArrayList<>(); - for (CartItem ci : cart) { - orderItems.add( - new OrderManager.OrderItem( - ci.displayName, - ci.material.name(), - ci.unitPrice, - ci.unitPrice, - ci.quantity)); - } - String dType = "none"; - String cCode = ""; - if (usedCouponCode != null) { - dType = "coupon"; - cCode = usedCouponCode; - } else if (ecoPct > 0) { - dType = "eco"; - } - plugin.getOrderManager().recordOrder( - p, orderItems, originalTotal, - baseTotal + packFee, - originalTotal - baseTotal, - dType, cCode, packFee, - useShulker ? (packFee > 0 - ? "custom" : "default") - : "none", - color != null ? color.name() : ""); - // 获取最新订单 - synchronized (plugin.getOrderManager() - .getOrders()) { - List all = - plugin.getOrderManager() - .getOrders(); - if (!all.isEmpty()) - latestOrder = - all.get(all.size() - 1); - } - // 散装打印小票 - if (!useShulker && latestOrder != null) { - plugin.getOrderManager() - .sendReceiptChat(p, latestOrder); - } - } catch (Exception ex) { - // plugin.getLogger().warning( - // "[Shop] 记录订单失败: " - // + ex.getMessage()); - } - -// ===== 发货 ===== - if (!useShulker) { - for (CartItem ci : cart) { - ShopItem si = findItemById(ci.itemId); - ItemStack give = (si != null) - ? getShopStack(si, ci.quantity) - : new ItemStack(ci.material, ci.quantity); - HashMap left2 = - p.getInventory().addItem(give); - for (ItemStack drop : left2.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - } - } else { - List shulkers = - packShulkers(cart, color); - - // ★ 给潜影盒打订单ID标签 - if (latestOrder != null) { - for (ItemStack sh : shulkers) { - ItemMeta shm = sh.getItemMeta(); - if (shm != null) { - List lore = shm.getLore() != null - ? new ArrayList<>(shm.getLore()) - : new ArrayList<>(); - lore.add("§8#" + latestOrder.orderId); - shm.setLore(lore); - sh.setItemMeta(shm); - } - } - } - - // 小票书塞入 - if (!shulkers.isEmpty() - && latestOrder != null) { - ItemStack book = plugin.getOrderManager() - .createReceiptBook(latestOrder); - plugin.getOrderManager() - .addBookToShulker(shulkers.get(0), book); - } - // 发货 - for (ItemStack sh : shulkers) { - HashMap left2 = - p.getInventory().addItem(sh); - for (ItemStack drop : left2.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - } - } - - - // ===== 退款记录 ===== - long now = System.currentTimeMillis(); - if (!useShulker) { - for (CartItem ci : cart) { - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - ci.itemId, ci.displayName, - ci.material, null, - ci.quantity, - ci.unitPrice * ci.quantity, - now)); - } - } else { - String summary = ""; - for (CartItem ci : cart) { - if (!summary.isEmpty()) summary += "+"; - summary += ci.displayName - + "x" + ci.quantity; - } - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - "cart_" + now, summary, - Material.SHULKER_BOX, color, - 1, baseTotal, now)); - } - - // ===== 核销优惠券 ===== - if (usedCouponCode != null) { - activeDiscount.remove(uuid); - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - m.invoke(sdf1, usedCouponCode); - } - } catch (Exception ignored) {} - p.sendMessage("§7优惠券 §e" + usedCouponCode - + " §7已核销"); - } - - shoppingCarts.remove(uuid); - pendingCheckout.remove(uuid); - return true; - } - - @EventHandler - public void onBlockPlace(org.bukkit.event.block.BlockPlaceEvent e) { - ItemStack item = e.getItemInHand(); - if (item == null) return; - if (!item.getType().name().contains("SHULKER")) return; - markShulkerUsed(item); - } - - // ★ 公共方法:标记潜影盒为已使用 - private void markShulkerUsed(ItemStack item) { - if (item == null) return; - ItemMeta meta = item.getItemMeta(); - if (meta == null || meta.getLore() == null) return; - for (String line : meta.getLore()) { - if (line.startsWith("§8#")) { - try { - long orderId = Long.parseLong( - line.replace("§8#", "").trim()); - // shulkerUsedOrders.add(orderId); - } catch (NumberFormatException ignored) {} - } - } - } - - @EventHandler - public void onPlayerDrop(org.bukkit.event.player.PlayerDropItemEvent e) { - ItemStack item = e.getItemDrop().getItemStack(); - if (item != null && item.getType().name().contains("SHULKER")) { - markShulkerUsed(item); - } - } - - - // ===== 潠影盒打包(指定颜色)===== - private List packShulkers( - List items, Material color) { - List result = new ArrayList<>(); - List all = new ArrayList<>(); - for (CartItem ci : items) { - int rem = ci.quantity; - while (rem > 0) { - int sz = Math.min(rem, 64); - all.add(new ItemStack(ci.material, sz)); - rem -= sz; - } - } - while (!all.isEmpty()) { - ItemStack shulker = new ItemStack(color); - org.bukkit.inventory.meta.BlockStateMeta meta = - (org.bukkit.inventory.meta.BlockStateMeta) - shulker.getItemMeta(); - if (meta != null) { - org.bukkit.block.BlockState state = - meta.getBlockState(); - if (state instanceof org.bukkit.block.Container) { - org.bukkit.block.Container c = - (org.bukkit.block.Container) state; - List remain = new ArrayList<>(); - for (ItemStack stack : all) { - HashMap left = - c.getInventory().addItem(stack); - for (ItemStack l : left.values()) - remain.add(l); - } - all = remain; - meta.setBlockState(c); - shulker.setItemMeta(meta); - } - } - result.add(shulker); - } - - return result; - } - - // ===== 打包确认GUI ===== - public void openPackingConfirm(Player p, int baseTotal) { - Inventory g = Bukkit.createInventory( - null, 27, "§6§l选择包装方式"); - g.setItem(10, mkItem(Material.CHEST, - "§a§l不打包", - "§7直接放入背包", - "§7适合少量物品")); - g.setItem(13, mkItem(Material.SHULKER_BOX, - "§6§l默认打包", - "§7随机颜色潜影盒", - "§7加收 §e5 §6枚债券")); - g.setItem(16, mkItem(Material.ENDER_CHEST, - "§b§l自选颜色", - "§7选择你喜欢的潜影盒颜色", - "§7加收 §e5 §6枚债券")); - g.setItem(22, mkItem(Material.PAPER, - "§7消费金额: §e" + baseTotal + "枚")); - - p.openInventory(g); - } - - public boolean handlePackingConfirm( - Player p, int raw) { - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.remove(uuid); - if (cart == null) return false; - - int originalTotal = 0; - for (CartItem ci : cart) - originalTotal += ci.unitPrice * ci.quantity; - - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - - if (raw == 10) { - ecoPct = 2; - } - - double factor = 1.0; - if (ecoPct > 0) - factor *= (100 - ecoPct) / 100.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - int discounted = (int) Math.round( - originalTotal * factor); - - int totalQty = 0; - for (CartItem ci : cart) - totalQty += ci.quantity; - boolean multiAndBig = (cart.size() > 1 - && originalTotal >= 100); - - if (raw == 10) { - deductAndGive(p, cart, discounted, - false, null, "", 0, - ecoPct, couponPct); - return true; - } - if (raw == 13) { - Material c = Material.SHULKER_BOX; - if (multiAndBig) { - deductAndGive(p, cart, discounted, - true, c, "", 0, - ecoPct, couponPct); - } else { - deductAndGive(p, cart, discounted, - true, c, "默认打包", 5, - ecoPct, couponPct); - } - return true; - } - if (raw == 16) { - pendingCheckout.put(uuid, cart); - openColorSelect(p, discounted); - return true; - } - return true; - } - - - // ===== 自选颜色GUI ===== - public void openColorSelect(Player p, int baseTotal) { - Inventory g = Bukkit.createInventory( - null, 54, "§b§l选择潜影盒颜色"); - String[] colorNames = { - "白色","橙色","品红","淡蓝", - "黄色","淡绿","粉色","灰色", - "淡灰","青色","紫色","蓝色", - "棕色","绿色","红色","黑色","原色" - }; - for (int i = 0; i < SHULKER_COLORS.length; i++) { - ItemStack is = new ItemStack(SHULKER_COLORS[i]); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e" + colorNames[i] - + "§6潜影盒"); - im.setLore(java.util.Arrays.asList( - "§7加收 §e5 §6枚债券", - "", - "§a点击选择")); - is.setItemMeta(im); - } - g.setItem(i, is); - } - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - g.setItem(45, mkItem(Material.PAPER, - "§7消费: §e" + baseTotal - + "枚 + §e5§6打包费")); - - p.openInventory(g); - } - - public boolean handleColorSelect( - Player p, int raw) { - if (raw == 49) { - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.get(uuid); - if (cart != null) { - int total = 0; - for (CartItem ci : cart) - total += ci.unitPrice * ci.quantity; - openPackingConfirm(p, total); - } - return true; - } - if (raw < 0 || raw >= SHULKER_COLORS.length) - return false; - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.remove(uuid); - if (cart == null) return false; - int total = 0; - for (CartItem ci : cart) - total += ci.unitPrice * ci.quantity; - - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - double factor = 1.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - int discounted = (int) Math.round( - total * factor); - - Material selectedColor = SHULKER_COLORS[raw]; - if (selectedColor == Material.SHULKER_BOX) { - deductAndGive(p, cart, discounted, true, - Material.SHULKER_BOX, - "原色打包", 0, - ecoPct, couponPct); - } else { - deductAndGive(p, cart, discounted, true, - selectedColor, "自选颜色", 5, - ecoPct, couponPct); - } - return true; - } - - - - // ===== 加载分类 ===== - - public void loadCategories() { - // plugin.getLogger().info("[Shop] === loadCategories 被调用 ==="); // 过滤调试日志 - categories.clear(); - viewCat.clear(); // ★ 清缓存 - viewPage.clear(); - - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - - convertTxtToMd(dir); - - File[] allMd = dir.listFiles( - (d, n) -> n.endsWith(".md") - && !n.equals("coupons.md")); - if (allMd == null || allMd.length == 0) { - createDefaultCategories(dir); - allMd = dir.listFiles( - (d, n) -> n.endsWith(".md") - && !n.equals("coupons.md")); - } - if (allMd == null) { - // plugin.getLogger().warning( - // "[Shop] shop目录为空"); - return; - } - - /* plugin.getLogger().info("[Shop] === 开始加载 ==="); - plugin.getLogger().info("[Shop] 扫描到 " - + allMd.length + " 个md文件"); -*/ - for (File f : allMd) { - /* plugin.getLogger().info("[Shop] 读取: " - + f.getName());*/ - ShopCategory cat = parseFile(f); - if (cat != null) { - /* plugin.getLogger().info("[Shop] 分类: " - + cat.getName() - + " 商品数: " - + cat.getItems().size());*/ - if (!cat.getItems().isEmpty()) { - categories.add(cat); - } - } - } - - int totalItems = 0; - for (ShopCategory c : categories) - totalItems += c.getItems().size(); - - plugin.getLogger().info("[Shop] === 加载完成 ===" - + " 分类=" + categories.size() - + " 商品=" + totalItems); - } - - -// ===== txt → md 自动迁移 ===== - - private void convertTxtToMd(File dir) { - File[] txtFiles = dir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("coupons.txt")); - if (txtFiles == null) return; - for (File txt : txtFiles) { - String mdName = txt.getName() - .replace(".txt", ".md"); - File mdFile = new File(dir, mdName); - if (mdFile.exists()) continue; - - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(txt), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) - lines.add(line); - br.close(); - - String catName = txt.getName() - .replace(".txt", ""); - List out = new ArrayList<>(); - out.add("# " + catName); - out.add(""); - out.add("| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |"); - out.add("| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |"); - - for (String l : lines) { - l = l.trim(); - if (l.isEmpty() || l.startsWith("#")) - continue; - // 旧格式: ID | 名称 | 材质 | ... - // 保持原样转换为 md 表格行 - out.add("| " + l.replace("|", "|") - + " |"); - } - - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(mdFile), - StandardCharsets.UTF_8)); - for (String o : out) - pw.println(o); - pw.flush(); - pw.close(); - - txt.delete(); - // plugin.getLogger().info( - // "[Shop] 已迁移: " - // + txt.getName() - // + " → " + mdName); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 迁移失败: " - // + txt.getName()); - } - } - } - -// ===== 解析 md 文件 ===== - - private ShopCategory parseFile(File f) { - String name = f.getName() - .replace(".md", ""); - ShopCategory cat = - new ShopCategory(name, f.getName()); - try { - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean headerPassed = false; - boolean separatorPassed = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - - // 空行跳过 - if (line.isEmpty()) continue; - - // 标题行:# 分类名 - if (line.startsWith("#")) { - String t = line.replaceFirst( - "^#+\\s*", "").trim(); - if (!t.isEmpty()) { - // 更新分类名(用文件内标题) - // cat.name 是 final,不改 - } - continue; - } - - // 表格分隔行:| --- | --- | - if (line.contains("---")) { - separatorPassed = true; - continue; - } - - // 表头行:| ID | 品名 | ... - if (line.startsWith("|")) { - if (!separatorPassed) { - headerPassed = true; - continue; - } - // 数据行 - ShopItem item = - parseTableRow(line); - if (item != null) - cat.addItem(item); - } - } - br.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 解析失败: " + f.getName() - // + " 原因: " + e.getMessage()); - return null; - } - return cat; - } - -// ===== 解析表格行 ===== - - private ShopItem parseTableRow(String row) { - // plugin.getLogger().info("[Shop] 行: " + row); // 过滤调试日志 - - String trimmed = row.trim(); - if (trimmed.startsWith("|")) - trimmed = trimmed.substring(1); - if (trimmed.endsWith("|")) - trimmed = trimmed.substring(0, - trimmed.length() - 1); - - String[] p = trimmed.split("\\|"); - // plugin.getLogger().info("[Shop] 列数=" + p.length); // 过滤调试日志 - - if (p.length < 6) { - // plugin.getLogger().warning( - // "[Shop] ✗ 列数不足, 跳过"); - return null; - } - - String rawId = p[0].trim(); - String rawName = p[1].trim(); - String rawMat = p[2].trim().toUpperCase(); - String rawBuy = p[3].trim(); - String rawSell = p[4].trim(); - String rawStock = p[5].trim(); - - // plugin.getLogger().info("[Shop] ID=" - // + rawId + " 名=" + rawName - // + " 材质=" + rawMat - // + " 购=" + rawBuy + " 售=" + rawSell - // + " 库=" + rawStock); // 过滤调试日志 - - // 检查材质是否存在 - Material mat = Material.matchMaterial(rawMat); - if (mat == null) { - // plugin.getLogger().warning( - // "[Shop] ✗ 无效材质: " + rawMat - // + " (行: " + row + ")"); - return null; - } - - try { - ShopItem item = new ShopItem( - rawId, rawName, mat, - Integer.parseInt(rawBuy), - Integer.parseInt(rawSell), - Integer.parseInt(rawStock), - p.length > 6 - ? Integer.parseInt( - p[6].trim()) : 0, - p.length > 7 - ? Integer.parseInt( - p[7].trim()) : 0); - // plugin.getLogger().info("[Shop] ✓ 解析成功: " + rawId); // 过滤调试日志 - return item; - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] ✗ 数字解析失败: " - // + e.getMessage()); - return null; - } - } - - -// ===== 保存为 md ===== - - public void saveCategory(ShopCategory cat) { - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - File f = new File(dir, cat.getFileName()); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# " + cat.getName()); - pw.println(""); - pw.println("| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |"); - pw.println("| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |"); - for (ShopItem item : cat.getItems()) { - pw.println("| " - + item.getId() - + " | " + item.getDisplayName() - + " | " + item.getMaterial().name() - + " | " + item.getBuyPrice() - + " | " + item.getSellPrice() - + " | " + item.getStock() - + " | " + item.getHourlySales() - + " | " + item.getTotalSales() - + " |"); - } - pw.flush(); - pw.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 保存失败: " - // + cat.getFileName()); - } - } - -// ===== 创建示例分类 ===== - - private void createDefaultCategories(File dir) { - writeMd(dir, "建筑材料.md", - "# 建筑材料\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| STONE | 石头 | STONE" - + " | 10 | -1 | -1 | 0 | 0 |\n" - + "| COBBLESTONE | 圆石 | COBBLESTONE" - + " | 5 | -1 | -1 | 0 | 0 |\n" - + "| OAK_PLANKS | 橡木木板 | OAK_PLANKS" - + " | 8 | 3 | -1 | 0 | 0 |\n" - + "| BRICKS | 砖块 | BRICKS" - + " | 12 | -1 | -1 | 0 | 0 |\n" - + "| GLASS | 玻璃 | GLASS" - + " | 15 | 6 | -1 | 0 | 0 |\n" - + "| QUARTZ_BLOCK | 石英块 | QUARTZ_BLOCK" - + " | 20 | 8 | -1 | 0 | 0 |\n"); - - writeMd(dir, "装备工具.md", - "# 装备工具\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| IRON_PICKAXE | 铁镐 | IRON_PICKAXE" - + " | 50 | 20 | -1 | 0 | 0 |\n" - + "| DIAMOND_PICKAXE | 钻石镐 | DIAMOND_PICKAXE" - + " | 500 | 200 | -1 | 0 | 0 |\n" - + "| IRON_SWORD | 铁剑 | IRON_SWORD" - + " | 40 | 16 | -1 | 0 | 0 |\n" - + "| DIAMOND_SWORD | 钻石剑 | DIAMOND_SWORD" - + " | 400 | 160 | -1 | 0 | 0 |\n" - + "| ELYTRA | 鞘翅 | ELYTRA" - + " | 2000 | 800 | -1 | 0 | 0 |\n"); - - writeMd(dir, "食物补给.md", - "# 食物补给\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| COOKED_BEEF | 熟牛排 | COOKED_BEEF" - + " | 15 | 6 | -1 | 0 | 0 |\n" - + "| GOLDEN_APPLE | 金苹果 | GOLDEN_APPLE" - + " | 500 | 200 | -1 | 0 | 0 |\n" - + "| BREAD | 面包 | BREAD" - + " | 8 | 3 | -1 | 0 | 0 |\n"); - - // plugin.getLogger().info( - // "[Shop] 已创建3个示例分类(md)"); - } - - private void writeMd(File dir, String name, - String content) { - File f = new File(dir, name); - if (f.exists()) return; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.print(content); - pw.flush(); - pw.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 创建失败: " + name); - } - } - - /** - * 从商品名称+材质智能推断完整物品 - * - * "夜视药水" + POTION - * → PotionEffect(NIGHT_VISION, 600, 0) - * - * "喷溅型治疗药水" + SPLASH_POTION - * → PotionEffect(HEALING, 600, 0) - * - * "长效夜视之箭" + TIPPED_ARROW - * → PotionEffect(NIGHT_VISION, 1800, 0) - * - * "强效力量喷溅药水" + SPLASH_POTION - * → PotionEffect(STRENGTH, 600, 1) - */ - private ItemStack inferFromName( - String name, Material mat) { - // 只处理药水和箭 - if (mat != Material.POTION - && mat != Material.SPLASH_POTION - && mat != Material.LINGERING_POTION - && mat != Material.TIPPED_ARROW) { - return null; - } - - // 1. 找效果类型 - PotionEffectType effectType = null; - for (Map.Entry e - : EFFECT_MAP.entrySet()) { - if (name.contains(e.getKey())) { - effectType = e.getValue(); - break; - } - } - if (effectType == null) return null; - - // 2. 找时长 - int duration = 600; - if (name.contains("长效") || name.contains("长时") - || name.contains("持久") || name.contains("长命")) { - duration = 1800; - } -// ★ 瞬间效果药水:duration = 1 - if (name.contains("治疗") || name.contains("治愈") - || name.contains("伤害")) { - duration = 1; - } - - // 3. 找等级 - int amplifier = 0; - if (name.contains("强效") - || name.contains("强化") - || name.contains("加强")) { - amplifier = 1; - } - - // 4. 构造完整物品 - try { - ItemStack item = new ItemStack(mat); - PotionMeta meta = - (PotionMeta) item.getItemMeta(); - - // ★ 神龟药水:双效果 - if (name.contains("神龟")) { - meta.addCustomEffect( - new PotionEffect( - PotionEffectType.RESISTANCE, - duration, amplifier + 2), - true); - meta.addCustomEffect( - new PotionEffect( - PotionEffectType.SLOWNESS, - duration, amplifier + 3), - true); - } else { - // 普通药水:单效果 - meta.addCustomEffect( - new PotionEffect(effectType, - duration, amplifier), - true); - } - - item.setItemMeta(meta); - return item; - } catch (Exception e) { - return null; - } - } - - public void saveAll() { - for (ShopCategory cat : categories) - saveCategory(cat); - } - private ItemStack getShopStack( - ShopItem item, int amount) { - // 优先级1: 手动捕获的NBT数据 - ItemStack cached = - itemStackCache.get(item.getId()); - if (cached != null) { - ItemStack copy = cached.clone(); - copy.setAmount(amount); - return copy; - } - // 优先级2: 根据名称智能推断 - ItemStack inferred = inferFromName( - item.getDisplayName(), - item.getMaterial()); - if (inferred != null) { - ItemStack copy = inferred.clone(); - copy.setAmount(amount); - return copy; - } - // 优先级3: 纯材质(默认) - return new ItemStack( - item.getMaterial(), amount); - } - - // ===== 购买 ===== - - public boolean buyItem(Player p, ShopItem item, - int amount) { - // plugin.getLogger().info("[Shop] ★ buyItem item=" + (item != null ? item.getId() : "null") + " amount=" + amount); - // plugin.getLogger().info("buyItem被调用"); - if (item == null) return false; - if (item.getStock() >= 0 - && item.getStock() < amount) { - p.sendMessage("§c库存不足,剩余: " - + item.getStock()); - return false; - } - if (p.getInventory().firstEmpty() == -1 - && !p.getInventory().contains( - item.getMaterial(), 1)) { - p.sendMessage("§c背包已满"); - return false; - } - - int unit = item.getBuyPrice(); - int disc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int total = Math.max(0, - unit * amount * (100 - disc) / 100); - - int balance = plugin.getBonds() - .getBonds(p.getName()); - if (balance < total) { - p.sendMessage("§c债券不足,需要 §e" - + total + "§c枚,当前 §e" - + balance + "§c枚"); - return false; - } - - boolean ok = plugin.getBonds().deductBonds( - p.getName(), total, - "shop_buy", item.getId(), - "商店系统", "购买" - + item.getDisplayName()); - if (!ok) { - p.sendMessage("§c扣款失败"); - return false; - } - - ItemStack give = getShopStack(item, amount); - HashMap left = - p.getInventory().addItem(give); - for (ItemStack drop : left.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - - if (item.getStock() >= 0) - item.setStock( - item.getStock() - amount); - item.addSales(amount); - - mergeOrAddRefund(p, item, amount, total); - Long applyTime = couponApplyTime.get(p.getUniqueId()); - if (applyTime != null - && System.currentTimeMillis() - applyTime > 300000L) { - // 超过5分钟,自动失效 - pendingCouponCode.remove(p.getUniqueId()); - activeDiscount.remove(p.getUniqueId()); - couponApplyTime.remove(p.getUniqueId()); - p.sendMessage("§7优惠券已过期,请重新输入"); - disc = 0; // 重置折扣 - } - - - String discMsg = disc > 0 - ? " §7(优惠" + disc + "%)" : ""; - p.sendMessage("§a购买成功: §e" - + item.getDisplayName() - + " x" + amount - + " §a-" + total + "枚债券" - + discMsg); - if (disc > 0) - activeDiscount.remove( - p.getUniqueId()); - // ★ 直购成功后核销优惠券 - String usedCode = pendingCouponCode - .remove(p.getUniqueId()); - if (usedCode != null) { - activeDiscount.remove(p.getUniqueId()); - couponApplyTime.remove(p.getUniqueId()); - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - m.invoke(sdf1, usedCode); - } - } catch (Exception ignored) {} - p.sendMessage("§7优惠券 §e" + usedCode - + " §7已核销"); - couponApplyTime.remove(p.getUniqueId()); - } - // ★ 记录订单(包裹try-catch,失败不影响购买) - try { - String usedCouponCode = - pendingCouponCode.get(p.getUniqueId()); - List orderItems = - new ArrayList<>(); - orderItems.add(new OrderManager.OrderItem( - item.getDisplayName(), - item.getMaterial().name(), - item.getBuyPrice(), - item.getBuyPrice(), amount)); - plugin.getOrderManager().recordOrder( - p, orderItems, - item.getBuyPrice() * amount, total, - item.getBuyPrice() * amount - total, - usedCouponCode != null ? "coupon" : "none", - usedCouponCode != null ? usedCouponCode : "", - 0, "none", ""); - // 打印小票 - List all = - plugin.getOrderManager().getOrders(); - if (!all.isEmpty()) { - plugin.getOrderManager().sendReceiptChat( - p, all.get(all.size() - 1)); - } - } catch (Exception ex) { - // plugin.getLogger().warning( - // "[Shop] 记录订单失败: " + ex.getMessage()); - } - - return true; - } - - - public void handleRefundItemClick(Player p, int raw) { - if (raw < 10 || raw >= 45) return; - int target = raw - 10; - int cnt = 0; - synchronized (refundRecords) { - Iterator it = - refundRecords.iterator(); - while (it.hasNext()) { - RefundRecord r = it.next(); - if (!r.playerUUID.equals(p.getUniqueId())) - continue; - if (r.isExpired()) { it.remove(); continue; } - if (cnt == target) { - requestRefund(p, r); - return; - } - cnt++; - } - } - } - - - // ===== 出售 ===== - - public boolean sellItem(Player p, ShopItem item, - int amount) { - if (item == null) return false; - - // ★ 先判断售价,禁止出售的不收物品 - if (item.getEffectiveSellPrice() <= 0) { - p.sendMessage("§c该商品不可出售"); - return false; - } - - if (!p.getInventory().containsAtLeast( - getShopStack(item, 1), amount)) { - p.sendMessage("§c你没有足够的 §e" - + item.getDisplayName()); - return false; - } - - int total = item.getEffectiveSellPrice() * amount; - - p.getInventory().removeItem( - getShopStack(item, amount)); - plugin.getBonds().addBonds( - p.getName(), total, "shop_sell", - item.getId(), "商店系统", - "出售" + item.getDisplayName()); - if (item.getStock() >= 0) - item.setStock(item.getStock() + amount); - p.sendMessage("§a出售成功: §e" - + item.getDisplayName() - + " x" + amount - + " §a+" + total + "枚债券"); - return true; - } - - // ===== 退款合并:30秒内同商品合并为一个订单 ===== - private boolean mergeOrAddRefund(Player p, ShopItem item, - int amount, int total) { - long now = System.currentTimeMillis(); - synchronized (refundRecords) { - for (int i = refundRecords.size() - 1; i >= 0; i--) { - RefundRecord r = refundRecords.get(i); - boolean same = r.playerUUID.equals(p.getUniqueId()) - && r.itemId.equals(item.getId()) - && (now - r.timestamp) < 30000L; - if (same) { - // final 字段不能改,只能删旧建新 - int newAmount = r.amount + amount; - int newTotal = r.totalPaid + total; - refundRecords.remove(i); - refundRecords.add(new RefundRecord( - r.playerUUID, r.playerName, - r.itemId, r.itemName, - r.material, newAmount, - newTotal, now)); - return true; - } - } - } - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - item.getId(), item.getDisplayName(), - item.getMaterial(), amount, total, now)); - return false; - } - - - - // ===== 退款 ===== - - public boolean requestRefund(Player p, RefundRecord rec) { - if (rec == null) return false; - if (!rec.playerUUID.equals(p.getUniqueId())) { - p.sendMessage("§c这不是你的购买记录"); - return false; - } - if (rec.isExpired()) { - p.sendMessage("§c已超过5分钟退款期限"); - synchronized (refundRecords) { - refundRecords.remove(rec); - } - return false; - } - - // 检测物品/潜影盒是否还在 - synchronized (refundRecords) { - if (rec.shulkerMaterial != null) { - int boxCount = countShulkers(p, rec.shulkerMaterial); - if (boxCount < 1) { - p.sendMessage("§c潜影盒已移出背包,无法退款"); - refundRecords.remove(rec); - return false; - } - } else { - int have = countDirect(p, rec.material); - if (have < rec.amount) { - p.sendMessage("§c物品已转移或消耗,无法退款"); - refundRecords.remove(rec); - return false; - } - } - } - - // 退款:扣物品/潜影盒 - if (rec.shulkerMaterial != null) { - // 盒装:扣一个潜影盒 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == rec.shulkerMaterial) { - p.getInventory().clear(i); - break; - } - } - } else { - // 普通:扣商品 - p.getInventory().removeItem( - new ItemStack(rec.material, rec.amount)); - } - - // 退款:加债券 - plugin.getBonds().addBonds( - p.getName(), rec.totalPaid, - "refund", rec.itemId, - "商店系统", "退款" + rec.itemName); - - // 恢复库存 - ShopItem si = findItemById(rec.itemId); - if (si != null && si.getStock() >= 0) - si.setStock(si.getStock() + rec.amount); - - synchronized (refundRecords) { - refundRecords.remove(rec); - } - - p.sendMessage("§a退款成功: §e" + rec.itemName - + " §a+" + rec.totalPaid + "枚债券"); - return true; - } - - - - - public void setPlayerDiscount(Player p, - int pct) { - activeDiscount.put( - p.getUniqueId(), pct); - p.sendMessage("§a已设置 " + pct - + "% 折扣,下次购买生效"); - } - - // ===== 管理员操作 ===== - - public boolean addItemToCategory( - String catName, String id, - String displayName, Material mat, - int buyPrice, int sellPrice, - int stock) { - ShopCategory cat = - findCategory(catName); - if (cat == null) return false; - if (cat.getItem(id) != null) return false; - cat.addItem(new ShopItem(id, displayName, - mat, buyPrice, sellPrice, - stock, 0, 0)); - saveCategory(cat); - return true; - } - - public boolean removeItem(String id) { - for (ShopCategory cat : categories) { - ShopItem item = cat.getItem(id); - if (item != null) { - cat.getItems().remove(item); - saveCategory(cat); - return true; - } - } - return false; - } - - public boolean setStock(String id, int stock) { - ShopItem item = findItemById(id); - if (item == null) return false; - item.setStock(stock); - for (ShopCategory cat : categories) { - if (cat.getItem(id) != null) { - saveCategory(cat); - return true; - } - } - return false; - } - - public ShopCategory findCategory(String n) { - for (ShopCategory c : categories) - if (c.getName().equals(n)) return c; - return null; - } - - public ShopItem findItemById(String id) { - for (ShopCategory c : categories) { - ShopItem item = c.getItem(id); - if (item != null) return item; - } - return null; - } - - public List getCategories() { - return categories; - } - - // ===== 命令处理 ===== - - public boolean handleCommand( - CommandSender s, String[] a) { - if (a.length <= 1) { - if (s instanceof Player) { - openShopMain((Player) s); - } else { - s.sendMessage("§c仅玩家可使用"); - } - return true; - } - - String sub = a[1].toLowerCase(); - - if (sub.equals("reload")) { - loadCategories(); - s.sendMessage("§a商店已重载,共 " - + categories.size() - + " 个分类"); - return true; - } - - if (sub.equals("add")) { - if (a.length < 7) { - s.sendMessage( - "§e用法: shop add <分类>" - + " <名称> <材质>" - + " <购入价>" - + " [售出价-1自动]" - + " [库存-1无限]"); - return true; - } - Material mat; - try { - mat = Material.valueOf( - a[5].toUpperCase()); - } catch (Exception e) { - s.sendMessage( - "§c无效材质: " + a[5]); - return true; - } - int price = Integer.parseInt(a[6]); - int sell = a.length > 7 - ? Integer.parseInt(a[7]) : -1; - int stock = a.length > 8 - ? Integer.parseInt(a[8]) : -1; - if (addItemToCategory(a[2], a[3], - a[4], mat, price, sell, stock)) { - s.sendMessage("§a已添加: " - + a[4] + " → " + a[2]); - } else { - s.sendMessage("§c添加失败" - + "(分类不存在或ID重复)"); - } - return true; - } - - if (sub.equals("remove")) { - if (a.length < 3) { - s.sendMessage( - "§e用法: shop remove "); - return true; - } - if (removeItem(a[2])) { - s.sendMessage( - "§a已移除: " + a[2]); - } else { - s.sendMessage( - "§c未找到: " + a[2]); - } - return true; - } - - if (sub.equals("set")) { - if (a.length < 4) { - s.sendMessage( - "§e用法: shop set " - + " <库存数(-1无限)>"); - return true; - } - int stock = Integer.parseInt(a[3]); - if (setStock(a[2], stock)) { - s.sendMessage("§a已设置 " - + a[2] + " 库存为 " - + stock); - } else { - s.sendMessage( - "§c未找到: " + a[2]); - } - return true; - } - - if (sub.equals("pro")) { - if (a.length < 4) { - s.sendMessage( - "§e用法: shop pro" - + " <玩家名> <折扣%>"); - return true; - } - Player t = - Bukkit.getPlayerExact(a[2]); - if (t == null) { - s.sendMessage("§c玩家不在线"); - return true; - } - int pct = Integer.parseInt(a[3]); - setPlayerDiscount(t, pct); - s.sendMessage("§a已对 " + a[2] - + " 设置 " + pct + "% 折扣"); - return true; - } - - - s.sendMessage("§e商店子命令:" - + " reload / add / remove" - + " / set / pro "); - return true; - } - @EventHandler(priority = EventPriority.LOW) - public void onCouponChat( - org.bukkit.event.player.AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); - UUID uuid = p.getUniqueId(); - if (!couponListening.contains(uuid)) return; - e.setCancelled(true); - couponListening.remove(uuid); - String code = e.getMessage().trim(); - if (code.isEmpty()) { - p.sendMessage("§c已取消输入"); - return; - } - // ★ 预处理:标准化输入 - String normalized = normalizeCouponInput(code); - if (normalized.isEmpty()) { - p.sendMessage("§c优惠券码无效(输入为空或仅含特殊字符)"); - couponListening.remove(uuid); - return; - } - - p.sendMessage("§7正在验证优惠券: §e" + code); - - int discount = -1; - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "validateCoupon", - String.class); - Object result = m.invoke(sdf1, normalized); - discount = (result != null) - ? (Integer) result : -1; - } - } catch (Exception ex) { - p.sendMessage("§c验证异常: " - + ex.getMessage()); - return; - } - if (discount <= 0) { - p.sendMessage("§c优惠券无效或已使用"); - return; - } - int offPct = 100 - discount; - int current = activeDiscount - .getOrDefault(uuid, 0); - if (offPct > current) { - pendingCouponCode.put(uuid, normalized); - activeDiscount.put(uuid, offPct); - couponApplyTime.put(uuid, System.currentTimeMillis()); - String zhe; - if (discount % 10 == 0) - zhe = (discount / 10) + "折"; - else - zhe = (discount / 10.0) + "折"; - p.sendMessage("§6§l[优惠券] §a验证通过!"); - p.sendMessage("§7折扣: §e" + zhe - + " §7(减" + offPct + "%)"); - p.sendMessage("§7结算时自动核销"); - final UUID u = uuid; - Bukkit.getScheduler().runTask(plugin, () -> { - Player pl = Bukkit.getPlayer(u); - if (pl != null && pl.isOnline()) { - openShopMain(pl); - } - }); - } else { - String curZhe; - if ((100 - current) % 10 == 0) - curZhe = ((100 - current) / 10) + "折"; - else - curZhe = ((100 - current) / 10.0) + "折"; - p.sendMessage("§c当前已有更优折扣: " - + curZhe + ",本次不生效"); - } - } - - /** - * 优惠券输入标准化 - * 全角→半角、去分隔符、转小写 - * "AAA-BBB-CCCC" → "aaaabbbbcccc" - * "AAA-BBB" → "aaabbb" - * "aaa_bbb_cccc" → "aaabbbcccc" - */ - private String normalizeCouponInput(String s) { - if (s == null) return ""; - StringBuilder sb = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - // 全角数字→半角 - if (c >= '\uFF10' && c <= '\uFF19') { - sb.append((char)(c - 0xFF10 + '0')); - } - // 全角字母→半角 - else if (c >= '\uFF21' && c <= '\uFF3A') { - sb.append((char)(c - 0xFF21 + 'A')); - } - else if (c >= '\uFF41' && c <= '\uFF5A') { - sb.append((char)(c - 0xFF41 + 'a')); - } - // ★ 保留中文字符(Unicode CJK统一汉字范围) - else if ((c >= '\u4E00' && c <= '\u9FFF') - || (c >= '\u3400' && c <= '\u4DBF')) { - sb.append(c); - } - // 保留半角字母和数字 - else if ((c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9')) { - sb.append(c); - } - // 分隔符(- _ 空格等)丢弃 - } - return sb.toString().toLowerCase(); - } - - - - - - private String formatZhe(int discount) { - if (discount % 10 == 0) { - return (discount / 10) + "折"; - } - return (discount / 10.0) + "折"; - } - - public boolean redeemCoupon(Player p, String code) { - int discount = -1; - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - Object result = m.invoke(sdf1, code); - discount = (result != null) - ? (Integer) result : -1; - } - } catch (Exception e) { - return false; - } - if (discount <= 0) return false; - int offPct = 100 - discount; - int cur = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (offPct > cur) { - activeDiscount.put(p.getUniqueId(), offPct); - } - return true; - } - - /** - * 商店主界面点击"优惠券"按钮后调用 - * 开启聊天监听,等待玩家输入优惠券码 - */ - public void openCouponInput(Player p) { - UUID uuid = p.getUniqueId(); - // 先取消已有的待核销优惠券 - pendingCouponCode.remove(uuid); - couponListening.add(uuid); - // 关闭UI - Bukkit.getScheduler().runTask(plugin, () -> { - if (p.isOnline()) p.closeInventory(); - }); - p.sendMessage("§6§l[优惠券] §e请在聊天栏输入优惠券码"); - p.sendMessage("§715秒内输入,过期自动取消"); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (couponListening.remove(uuid)) { - Player pl = Bukkit.getPlayer(uuid); - if (pl != null && pl.isOnline()) { - pl.sendMessage("§c优惠券输入已超时取消"); - } - } - }, 300L); - } - - - // ===== GUI: 主界面 ===== - - public void openShopMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l商店"); - int si = 0; - for (ShopCategory cat : categories) { - if (si >= ITEM_SLOTS.length) break; - ItemStack is = new ItemStack( - Material.CHEST); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName( - "§e§l" + cat.getName()); - im.setLore(Arrays.asList( - "§7商品: §f" - + cat.getItems().size() - + " 种", - "", - "§a点击进入")); - is.setItemMeta(im); - } - g.setItem(ITEM_SLOTS[si], is); - si++; - } - // ★ 改为跳转订单中心 - g.setItem(49, mkItem(Material.CLOCK, - "§6§l订单中心", - "§7查看订单记录、申请退款", - "", "§a点击进入")); - - // 在 openShopMain 方法中,原来的 slot 49/53 附近加: - - g.setItem(48, mkItem( - isCartMode(p) - ? Material.LIME_WOOL - : Material.GRAY_WOOL, - isCartMode(p) - ? "§a§l购物车模式 §7[开]" - : "§7§l购物车模式 §7[关]", - isCartMode(p) - ? "§a当前: 点击商品加入购物车" - : "§7当前: 直接购买", - "§7点击切换")); - g.setItem(50, mkItem(Material.CHEST, - "§6§l购物车 §7[" + getCartCount(p) + "件]", - "§7总价: §6" + getCartTotal(p) + "枚", - "", - "§a点击打开购物车")); - - // 在现有按钮之后,p.openInventory(g) 之前加: - g.setItem(49, mkItem(Material.CLOCK, - "§6§l订单中心", - "§7查看购物订单记录", - "", "§a点击进入")); - - // 优惠券按钮 - slot 47 - String discStatus = "§7"; - int curDisc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (curDisc > 0) { - discStatus = "§a当前: " + formatZhe(100 - curDisc); - } - g.setItem(47, mkItem(Material.NAME_TAG, - "§6§l优惠券", - discStatus, - "", - "§a点击输入优惠券码", - "§7一次性使用,核销后失效")); - - g.setItem(53, mkItem(Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - // ===== GUI: 分类 ===== - public void openCategory(Player p, - ShopCategory cat, - int page) { - // ★ 每次重新获取最新对象 ★ - ShopCategory fresh = null; - for (ShopCategory c : categories) { - if (c.getName().equals(cat.getName())) { - fresh = c; - break; - } - } - if (fresh == null) { - openShopMain(p); - return; - } - cat = fresh; - viewCat.put(p.getUniqueId(), cat); - - int total = (int) Math.ceil( - (double) cat.getItems().size() - / PAGE_SIZE); - if (total < 1) total = 1; - if (page < 0) page = 0; - if (page >= total) page = total - 1; - viewPage.put(p.getUniqueId(), page); - - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l" + cat.getName()); - - - int start = page * PAGE_SIZE; - int end = Math.min(start + PAGE_SIZE, - cat.getItems().size()); - - for (int i = start; i < end; i++) { - int idx = i - start; - if (idx >= ITEM_SLOTS.length) break; - - // ★ item 保留原名,代表 ShopItem 数据 - ShopItem item = cat.getItems().get(i); - - // ★ is 是带完整元数据的 ItemStack(用于GUI图标) - ItemStack is = getShopStack(item, 1); - ItemMeta im = is.getItemMeta(); - - if (im != null) { - im.setDisplayName("§e§l" - + item.getDisplayName()); - List lore = new ArrayList<>(); - lore.add("§7购入: §a" - + item.getBuyPrice() - + "§7枚债券"); - lore.add("§7售出: §e" - + item.getEffectiveSellPrice() - + "§7枚债券"); - if (item.getStock() >= 0) - lore.add("§7库存: §f" - + item.getStock()); - else - lore.add("§7库存: §a无限"); - lore.add(""); - int disc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (disc > 0) { - int dis = item.getBuyPrice() - * (100 - disc) / 100; - lore.add("§7优惠价: §6" - + dis + "枚" - + " §7(-" + disc + "%)"); - } - lore.add("§a左键买1 §6右键卖1"); - lore.add("§aShift+左键买64" - + " §6Shift+右键卖64"); - im.setLore(lore); - is.setItemMeta(im); - } - - // ★ 用 is(ItemStack)放入GUI格子 - g.setItem(ITEM_SLOTS[idx], is); - } - - - g.setItem(4, mkItem(Material.BOOK, - "§e§l" + cat.getName(), - "§7共 " + cat.getItems().size() - + " 种商品")); - g.setItem(48, page > 0 - ? mkItem(Material.ARROW, - "§7上一页") - : mkItem(Material - .BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(49, mkItem(Material.PAPER, - "§e" + (page + 1) + "/" + total)); - g.setItem(50, page < total - 1 - ? mkItem(Material.ARROW, - "§7下一页") - : mkItem(Material - .BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(53, mkItem(Material.ARROW, - "§7返回商店")); - - p.openInventory(g); - } - - // ===== GUI: 退款 ===== - - public void openRefundPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§c§l退款中心"); - - // 位移检测:比对上次记录的背包内容 - validateRefunds(p); - - // 过期清理 - synchronized (refundRecords) { - refundRecords.removeIf(RefundRecord::isExpired); - } - - int idx = 0; - synchronized (refundRecords) { - for (RefundRecord r : refundRecords) { - if (idx >= 45) break; - if (!r.playerUUID.equals(p.getUniqueId())) continue; - if (r.isExpired()) continue; - - // 盒装用潜影盒图标,普通用商品图标 - Material iconMat = (r.shulkerMaterial != null) - ? r.shulkerMaterial : r.material; - - ItemStack is = new ItemStack(iconMat); - ItemMeta im = is.getItemMeta(); - if (im != null) { - int secs = r.getSecondsLeft(); - im.setDisplayName("§e" + r.itemName - + (r.shulkerMaterial != null - ? " §7[盒装]" : " x" + r.amount)); - - List lore = new ArrayList<>(); - lore.add("§7订单: §f#" + r.timestamp); - lore.add("§7支付: §a" + r.totalPaid - + "§7枚债券"); - - if (r.shulkerMaterial != null) { - int boxCount = countShulkers(p, - r.shulkerMaterial); - lore.add("§7潜影盒: §f" - + (boxCount >= 1 - ? "§a在背包中" - : "§c已移出")); - } else { - int have = countDirect(p, r.material); - lore.add("§7物品: §f" + have - + "/" + r.amount); - } - - lore.add("§7剩余: §e" + secs + "秒"); - lore.add(""); - lore.add("§c点击退款"); - im.setLore(lore); - } - is.setItemMeta(im); - g.setItem(10 + idx, is); - idx++; - } - } - - if (idx == 0) { - g.setItem(22, mkItem(Material.BARRIER, - "§7暂无可退款记录")); - } - - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - - p.openInventory(g); - } - // 统计背包中某物品数量 - private int countInInventory(Player p, Material mat) { - int count = 0; - for (ItemStack is : p.getInventory().getContents()) { - if (is != null && is.getType() == mat) { - count += is.getAmount(); - } - } - // 潜影盒也要检查里面 - for (ItemStack is : p.getInventory().getContents()) { - if (is != null - && is.getType().name() - .endsWith("_SHULKER_BOX")) { - count += is.getAmount(); - } - } - return count; - } - - // 只统计背包中直接可见的数量(不含盒内) - // 新代码:增加按完整物品匹配的方法 - private int countDirect(Player p, Material mat) { - int count = 0; - for (ItemStack is : - p.getInventory().getContents()) { - if (is != null && is.getType() == mat) - count += is.getAmount(); - } - return count; - } - - private int countDirectByStack(Player p, - ItemStack target) { - int count = 0; - for (ItemStack is : - p.getInventory().getContents()) { - if (is != null && is.isSimilar(target)) - count += is.getAmount(); - } - return count; - } - // 统计背包中潜影盒数量 - private int countShulkers(Player p, Material shulker) { - if (shulker == null) return Integer.MAX_VALUE; - int count = 0; - for (ItemStack is : p.getInventory().getContents()) { - if (is != null && is.getType() == shulker) - count += is.getAmount(); - } - return count; - } - - // 退款检测:位移 OR 消耗 → 不能退 - private void validateRefunds(Player p) { - synchronized (refundRecords) { - Iterator it = - refundRecords.iterator(); - while (it.hasNext()) { - RefundRecord r = it.next(); - if (!r.playerUUID.equals(p.getUniqueId())) - continue; - if (r.isExpired()) { - it.remove(); - continue; - } - - if (r.shulkerMaterial != null) { - // 盒装:检测潜影盒是否还在背包 - int now = countShulkers( - p, r.shulkerMaterial); - if (now < 1) { - it.remove(); - p.sendMessage("§c[退款] " + r.itemName - + " 潜影盒已移出背包,无法退款"); - } - } else { - // 普通:检测商品是否还在背包 - int now = countDirect(p, r.material); - if (now < r.amount) { - it.remove(); - p.sendMessage("§c[退款] " + r.itemName - + " 物品已被使用或移出,无法退款"); - } - } - } - } - } - - - public int getPlayerPage(java.util.UUID uuid) { - return viewPage.getOrDefault(uuid, 0); - } - - public ShopCategory getPlayerCategory(java.util.UUID uuid) { - return viewCat.get(uuid); - } - - // ===== 供GUIManager调用:商店主界面点击 ===== - public boolean handleMainShopClick(Player p, int raw) { - if (raw == 49) { plugin.getOrderManager().openOrderCenter(p); return true; } - - if (raw == 48) { - toggleCartMode(p); - openShopMain(p); - return true; - } - if (raw == 50) { - openCart(p); - return true; - } - if (raw == 53) { - plugin.getGui().openMain(p); - return true; - } - - // ★ 新增:优惠券按钮 - if (raw == 47) { - openCouponInput(p); - return true; - } - if (raw == 49) { plugin.getOrderManager().openOrderCenter(p); return true;} - int idx = slotToIndex(raw); - if (idx < 0 || idx >= categories.size()) - return true; - openCategory(p, categories.get(idx), 0); - return true; - } - - // ===== 供GUIManager调用:退款中心点击 ===== - public boolean handleRefundClick(Player p, int raw) { - if (raw == 49) { openShopMain(p); return true; } - return true; - } - - // ===== 供GUIManager调用:分类页点击 ===== - public boolean handleCategoryClick(Player p, String title, - int raw, boolean left, - boolean shift) { - // plugin.getLogger().info("[Shop] ★ handleCategoryClick raw=" + raw); - - ShopManager sm = this; - ShopCategory match = null; - for (ShopCategory c : categories) { - if (title.equals("§6§l" + c.getName())) { - match = c; - break; - } - } - if (match == null) { - // plugin.getLogger().info("[Shop] ★ 匹配失败, title=[" + title + "]"); - return false; - } - // plugin.getLogger().info("[Shop] ★ 匹配到: " + match.getName()); - - viewCat.put(p.getUniqueId(), match); - - if (raw == 53) { openShopMain(p); return true; } - int pg = getPlayerPage(p.getUniqueId()); - if (raw == 48) { openCategory(p, match, pg - 1); return true; } - if (raw == 50) { openCategory(p, match, pg + 1); return true; } - - int itemIdx = slotToIndex(raw); - if (itemIdx < 0) { - // plugin.getLogger().info("[Shop] ★ slot=" + raw + " 不是商品格"); - return true; - } - int actual = itemIdx + pg * PAGE_SIZE; - if (actual < 0 || actual >= match.getItems().size()) { - // plugin.getLogger().info("[Shop] ★ 超出范围 actual=" + actual + " size=" + match.getItems().size()); - return true; - } - ShopItem si = match.getItems().get(actual); - // plugin.getLogger().info("[Shop] ★ 准备购买: " + si.getId() + " left=" + left); - - int amount = shift ? 64 : 1; - if (left) { - if (shift && isCartMode(p)) { addToCart(p, si); } - else if (shift) { buyItem(p, si, 64); } - else if (isCartMode(p)) { addToCart(p, si); } - else { buyItem(p, si, 1); } - } else { - if (shift) { sellAllOf(p, si); } - else { sellItem(p, si, 1); } - } - openCategory(p, match, pg); - return true; - } - - // ===== 批量出售某商品全部数量 ===== - private void sellAllOf(Player p, ShopItem item) { - if (item == null) return; - // ★ 补上禁止出售检查 - if (item.getEffectiveSellPrice() <= 0) { - p.sendMessage("§c该商品不可出售"); - return; - } - if (item == null) return; - Material mat = item.getMaterial(); - int total = 0; - int count = 0; - // 遍历背包清点 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - total += is.getAmount(); - count++; - } - } - if (total <= 0) { - p.sendMessage("§c你没有可出售的 §e" - + item.getDisplayName()); - return; - } - // 清除背包中该物品 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - p.getInventory().clear(i); - } - } - int money = item.getEffectiveSellPrice() * total; - plugin.getBonds().addBonds( - p.getName(), money, "shop_sell", - item.getId(), "商店系统", - "批量出售" + item.getDisplayName() - + " x" + total); - if (item.getStock() >= 0) - item.setStock(item.getStock() + total); - p.sendMessage("§a批量出售: §e" - + item.getDisplayName() - + " x" + total - + " §a+" + money + "枚债券"); - } - - - // ===== 定时 ===== - - private void startHourlyTask() { - new BukkitRunnable() { - @Override - public void run() { - // ★ 汇报交给 SalesStatsManager - if (plugin.getSalesStats() != null) { - plugin.getSalesStats().report(); - } - // 重置小时销量 - for (ShopCategory cat : categories) - for (ShopItem item : cat.getItems()) - item.setHourlySales(0); - saveAll(); - } - }.runTaskTimer(plugin, 72000L, 72000L); - } - - - private void startCleanupTask() { - new BukkitRunnable() { - @Override - public void run() { - synchronized (refundRecords) { - refundRecords.removeIf( - RefundRecord::isExpired); - } - } - }.runTaskTimer(plugin, 1200L, 1200L); - } - - // ===== 工具 ===== - - private int slotToIndex(int slot) { - for (int i = 0; i < ITEM_SLOTS.length; i++) - if (ITEM_SLOTS[i] == slot) return i; - return -1; - } - - private ItemStack mkItem(Material mat, - String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/TicketManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/TicketManager.java deleted file mode 100644 index c21edc2..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/TicketManager.java +++ /dev/null @@ -1,1644 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class TicketManager { - - private final Main plugin; - - public static final String T_TICKET_MAIN = - "§6§l工单中心"; - public static final String T_TICKET_CREATE = - "§e§l创建工单"; - public static final String T_TICKET_MY = - "§b§l我的工单"; - public static final String T_TICKET_ADMIN = - "§c§l工单管理"; - public static final String T_TICKET_DETAIL = - "§a§l工单详情"; - - public static final String TYPE_BUG = - "bug反馈"; - public static final String TYPE_HELP = - "求助"; - public static final String TYPE_REPORT = - "举报"; - public static final String TYPE_APPLY = - "申请"; - public static final String TYPE_OTHER = - "其他"; - public static final String TYPE_DELETE = - "删号"; - public static final String TYPE_PASSWORD = - "修改密码"; - - - - private static final Map - PRICES = new LinkedHashMap<>(); - static { - PRICES.put(TYPE_BUG, 10); - PRICES.put(TYPE_HELP, 5); - PRICES.put(TYPE_REPORT, 8); - PRICES.put(TYPE_APPLY, 15); - PRICES.put(TYPE_OTHER, 5); - PRICES.put(TYPE_DELETE, 0); - PRICES.put(TYPE_PASSWORD, 0); - } - - - public TicketManager(Main plugin) { - this.plugin = plugin; - } - - // ========== GUI ========== - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_MAIN); - fillBg(g); - g.setItem(10, mk(Material.PAPER, - "§e创建工单", - "§7提交bug、求助、举报等")); - g.setItem(12, mk(Material.BOOK, - "§b我的工单", - "§7查看已提交的工单")); - g.setItem(14, mk(Material.CHEST, - "§6工单记录", - "§7查看历史工单")); - if (adm(p)) { - g.setItem(16, mk(Material.REDSTONE, - "§c工单管理", - "§7管理员专用")); - } - if (plugin.getDb() - .isServiceProvider( - p.getName())) { - g.setItem(18, mk(Material.EMERALD, - "§a服务商面板", - "§7接单、处理工单")); - } - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openCreate(Player p) { - Inventory g = Bukkit.createInventory( - null, 36, T_TICKET_CREATE); - fillBg(g); - g.setItem(10, mk(Material.BOOK, - "§cbug反馈")); - g.setItem(11, mk(Material.EMERALD, - "§a求助")); - g.setItem(12, mk(Material.BLAZE_POWDER, - "§6举报")); - g.setItem(13, mk(Material.NAME_TAG, - "§e申请")); - g.setItem(14, mk(Material.PAPER, - "§7其他")); - g.setItem(20, mk(Material.BARRIER, - "§c申请删号", - "§7申请删除账号")); - g.setItem(21, mk(Material.NETHER_WART, - "§d申请改密", - "§7请求管理员协助改密")); - g.setItem(31, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyTickets(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_MY); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByPlayer( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openDetail(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_DETAIL); - fillBg(g); - - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = sv(t, "requester"); - String title = sv(t, "title"); - String desc = sv(t, "description"); - String assigned = sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - int confirmed = nv(t, "admin_confirmed"); - - g.setItem(10, mk(Material.PAPER, - "§e§l" + (title.isEmpty() - ? type : title), - "§7类型: §f" + type, - "§7状态: " + stxt(status), - "§7提交者: §f" + requester, - "§7奖励: §e" + reward + " 积分")); - - g.setItem(12, mk(Material.BOOK, - "§7详细描述", - desc.isEmpty() ? "§7无描述" - : "§7" + (desc.length() > 40 - ? desc.substring(0, 40) - + "..." : desc))); - - if (!assigned.isEmpty()) { - g.setItem(14, mk(Material.PLAYER_HEAD, - "§7处理人: §f" + assigned, - confirmed == 1 - ? "§7评分: §e" + score - : "§7处理中")); - } else { - g.setItem(14, mk(Material.BARRIER, - "§7未分配")); - } - - // 驳回原因 - if ("closed".equals(status)) { - String reason = sv(t, "reject_reason"); - if (!reason.isEmpty()) { - g.setItem(13, mk(Material.BARRIER, - "§c驳回原因", - "§7" + reason)); - } - } - - // 回复按钮(非关闭/已完结状态) - if (!"closed".equals(status) - && !"withdrawn".equals(status) - && !"resolved".equals(status)) { - g.setItem(15, mk( - Material.WRITABLE_BOOK, - "§b回复工单", - "§7向对方发送消息")); - g.setItem(16, mk(Material.BOOK, - "§7查看回复记录")); - } - - // ===== 管理员按钮 ===== - if (adm(p)) { - if ("pending".equals(status)) { - // 待分配:可分配给服务商 - g.setItem(19, mk(Material.EMERALD, - "§a分配工单", - "§7指定服务商处理")); - // 也可自己处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && !assigned.equals("admin") - && !assigned.isEmpty()) { - // 服务商在处理:可取消指派收回 - g.setItem(19, mk(Material.BARRIER, - "§c取消指派", - "§7取消服务商指派,收回工单")); - // 也可自己接手处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && assigned.equals("admin")) { - // 管理员自己在处理:可标记完结 - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - if ("replied".equals(status) - && assigned.equals("admin")) { - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - // 驳回(非已关闭/已完结状态) - if (!"closed".equals(status) - && !"resolved".equals(status) - && !"withdrawn".equals(status)) { - g.setItem(21, mk(Material.RED_DYE, - "§c驳回工单", - "§7驳回并关闭此工单")); - } - } - - // ===== 服务商按钮 ===== - if (plugin.getDb() - .isServiceProvider(p.getName()) - && !TYPE_DELETE.equals(type) - && !TYPE_PASSWORD.equals(type)) { - if ("pending".equals(status) - && assigned.isEmpty()) { - // 待分配无人:抢单 - g.setItem(19, mk( - Material.GOLD_INGOT, - "§6抢单", - "§7接取此工单")); - } - if (assigned.equals(p.getName()) - && ("processing".equals(status) - || "replied".equals(status))) { - // 我在处理:标记完结/上报异常 - g.setItem(19, mk( - Material.LIME_DYE, - "§a标记完结", - "§7上报处理完成")); - g.setItem(20, mk( - Material.REDSTONE, - "§c上报异常", - "§7报告处理中的问题")); - } - } - - g.setItem(22, mk(Material.ARROW, - "§7返回")); - - ItemStack info = g.getItem(10); - if (info != null) { - ItemMeta im = info.getItemMeta(); - if (im != null) { - im.setCustomModelData(tid); - info.setItemMeta(im); - } - } - p.openInventory(g); - } - - - public void openAdminList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_ADMIN); - fillBg(g); - g.setItem(45, mk(Material.EMERALD, - "§a邀请服务商")); - g.setItem(46, mk(Material.BARRIER, - "§c移除服务商")); - g.setItem(47, mk(Material.CLOCK, - "§e服务商列表")); - g.setItem(48, mk(Material.NETHER_STAR, - "§e直接派单", - "§7管理员直接创建并派单")); - List> all = - new ArrayList<>(); - all.addAll(plugin.getDb() - .getTicketsByStatus("pending")); - all.addAll(plugin.getDb() - .getTicketsByStatus("processing")); - all.addAll(plugin.getDb() - .getTicketsByStatus("replied")); - all.addAll(plugin.getDb() - .getTicketsByStatus("completed")); - all.addAll(plugin.getDb() - .getTicketsByStatus("resolved")); - all.addAll(plugin.getDb() - .getTicketsByStatus("closed")); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - - public void openProviderPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, "§d§l服务商面板"); - fillBg(g); - g.setItem(10, mk(Material.CHEST, - "§e抢单大厅", - "§7查看可接工单")); - g.setItem(12, mk(Material.CLOCK, - "§b我的工单", - "§7我正在处理的工单")); - g.setItem(14, mk(Material.PAPER, - "§6历史工单", - "§7已完成的工单")); - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openGrabList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l抢单大厅"); - fillBg(g); - List> pending = - plugin.getDb() - .getTicketsByStatus( - "pending"); - List> list = - new ArrayList<>(); - for (Map t : pending) { - if (sv(t, "assigned_to").isEmpty()) { - list.add(t); - } - } - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyAssigned(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§b§l我处理中的工单"); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByProvider( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - // ========== 业务 ========== - - public int submitTicket(Player p, - String type, String title, - String desc) { - int price = PRICES - .getOrDefault(type, 5); - int id = plugin.getDb().createTicket( - type, "pending", "normal", - p.getName(), title, desc, - price, ""); - if (id > 0) { - if (TYPE_DELETE.equals(type) - || TYPE_PASSWORD.equals(type)) { - plugin.getDb().updateTicketField( - id, "assigned_to", "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage( - "§a§l[工单] §f工单 #" - + id + " 已提交," - + "等待管理员处理"); - } else { - p.sendMessage( - "§a§l[工单] §f创建成功!" - + " #§e" + id - + " 奖励: §e" - + price + "积分"); - } - notifyAdmins(p.getName(), id); - } else { - p.sendMessage("§c创建失败"); - } - return id; - } - - - public void adminCreateAndAssign( - Player p, String type, - String title, String desc, - String provider, int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - int id = plugin.getDb().createTicket( - type, "processing", "normal", - p.getName(), title, desc, - price, ""); - if (id <= 0) { - p.sendMessage("§c创建失败"); - return; - } - plugin.getDb().assignTicket( - id, provider); - if (price > 0) { - plugin.getDb().updateTicketField( - id, "reward_amount", price); - } - p.sendMessage("§a工单 #" + id - + " 已创建并分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f管理员指派工单 #" - + id + " 给您"); - playAssignedSound(tp); - } - } - - public void grabTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!"pending".equals( - sv(t, "status"))) { - p.sendMessage("§c该工单已被接单"); - return; - } - if (!sv(t, "assigned_to").isEmpty()) { - p.sendMessage("§c该工单已被分配"); - return; - } - plugin.getDb().assignTicket( - tid, p.getName()); - p.sendMessage("§a抢单成功!工单 #" + tid); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f您的工单 #" - + tid + " 已被 " - + p.getName() - + " 接单"); - } - } - - public void assignToProvider(Player p, - int tid, String provider, - int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - plugin.getDb().assignTicket( - tid, provider); - if (price > 0) { - plugin.getDb().updateTicketStatus( - tid, "processing"); - plugin.getDb().updateTicketField( - tid, "reward_amount", price); - } - p.sendMessage("§a工单 #" + tid - + " 已分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f您有新工单: #" - + tid); - playAssignedSound(tp); - } - } - - public void confirmAndScore(Player p, - int tid, int score) { - if (score < 1 || score > 5) { - p.sendMessage("§c评分1-5"); - return; - } - plugin.getDb().confirmTicket( - tid, score); - p.sendMessage("§a工单 #" + tid - + " 已确认,评分: " + score); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已确认完结"); - } - } - settleTicket(tid); - } - - public void completeAsProvider(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "assigned_to") - .equals(p.getName())) { - p.sendMessage("§c这不是你的工单"); - return; - } - plugin.getDb().completeTicket(tid); - p.sendMessage("§a工单 #" + tid - + " 已标记完结,等待确认"); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已处理完成"); - } - } - - public void rejectTicket(Player p, - int tid, String reason) { - plugin.getDb().updateTicketStatus( - tid, "closed"); - plugin.getDb().updateTicketField( - tid, "reject_reason", reason); - p.sendMessage("§c工单 #" + tid - + " 已驳回并关闭"); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 被驳回: " - + reason); - } - } - } - - public void replyTicket(Player p, - int tid, String message) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - String status = sv(t, "status"); - if ("closed".equals(status) - || "withdrawn".equals(status) - || "resolved".equals(status)) { - p.sendMessage("§c该工单已关闭"); - return; - } - plugin.getDb().addTicketReply( - tid, p.getName(), message); - p.sendMessage("§a回复成功"); - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - String sender = p.getName(); - if (!requester.equals(sender)) { - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - if (!assigned.isEmpty() - && !assigned.equals(sender)) { - Player ap = Bukkit.getPlayer( - assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - } - - public void showReplies(Player p, - int tid) { - List> replies = - plugin.getDb() - .getTicketReplies(tid); - if (replies.isEmpty()) { - p.sendMessage("§7暂无回复"); - return; - } - p.sendMessage("§e§l=== 回复记录 ==="); - SimpleDateFormat sdf = - new SimpleDateFormat( - "MM-dd HH:mm"); - for (Map r : replies) { - String sender = sv(r, "sender"); - String msg2 = sv(r, "message"); - long time = ((Number) r.get( - "create_time")).longValue(); - p.sendMessage("§7[" + sdf.format( - new Date(time)) + "] §f" - + sender + "§7: " + msg2); - } - p.sendMessage( - "§e§l================="); - } - - public void withdrawTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "requester") - .equals(p.getName()) - && !adm(p)) { - p.sendMessage("§c无权操作"); - return; - } - plugin.getDb().updateTicketStatus( - tid, "withdrawn"); - p.sendMessage("§a工单 #" + tid - + " 已撤回"); - } - - public void showProviderList(Player p) { - List> list = - plugin.getDb() - .getAllServiceProviders(); - if (list.isEmpty()) { - p.sendMessage("§7暂无服务商"); - return; - } - p.sendMessage( - "§e§l=== 服务商列表 ==="); - for (Map pro : list) { - p.sendMessage("§7- §f" - + pro.get("player_name") - + " §7角色: §e" - + pro.get("role")); - } - p.sendMessage( - "§e§l==================="); - } - - public void autoProcessCompleted() { - long timeoutMs = 10 * 60 * 1000L; - - // 自动处理删号工单(5分钟自动通过) - List> deleteTickets = - plugin.getDb() - .getTicketsByStatus( - "processing"); - for (Map t : deleteTickets) { - String dtype = sv(t, "type"); - if (!TYPE_DELETE.equals(dtype)) continue; - int did = nv(t, "id"); - int dconf = nv(t, "admin_confirmed"); - // 已经处理过了跳过 - if (dconf != 0) continue; - String dasg = sv(t, "assigned_to"); - // 只处理分配给admin的 - if (!"admin".equals(dasg)) continue; - long createTime = ((Number) t.get( - "create_time")).longValue(); - if (System.currentTimeMillis() - - createTime >= 5 * 60 * 1000L) { - processDeleteAccount(did, t); - } - } - - - // 已完结未确认→10分钟自动完结 - List> list = - plugin.getDb() - .getUnconfirmedCompleted( - timeoutMs); - for (Map t : list) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 已自动完结"); - String assigned = - sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) - pp.sendMessage( - "§e§l[工单] §f工单 #" - + id - + " 已自动完结"); - } - settleTicket(id); - } - - // 已回复但10分钟无互动→自动完结 - List> stale = - plugin.getDb() - .getRepliedStaleTickets( - timeoutMs); - for (Map t : stale) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 超时自动完结"); - settleTicket(id); - } - } - - - - // ========== GUI点击 ========== - public boolean handleClick(Player p, - String title, int slot) { - - if (title.equals(T_TICKET_MAIN)) { - if (slot == 10) openCreate(p); - else if (slot == 12) - openMyTickets(p); - else if (slot == 14) - openMyTickets(p); - else if (slot == 16) { - if (adm(p)) openAdminList(p); - else p.sendMessage("§c权限不足"); - } - else if (slot == 18) { - if (plugin.getDb() - .isServiceProvider(p.getName())) { - openProviderPanel(p); - } else { - p.sendMessage("§c权限不足"); - } - } - else if (slot == 22) - plugin.getGui().openMain(p); - return true; - } - - if (title.equals(T_TICKET_CREATE)) { - if (slot == 31) { - openMain(p); - return true; - } - String type = null; - if (slot == 10) type = TYPE_BUG; - else if (slot == 11) type = TYPE_HELP; - else if (slot == 12) type = TYPE_REPORT; - else if (slot == 13) type = TYPE_APPLY; - else if (slot == 14) type = TYPE_OTHER; - else if (slot == 20) type = TYPE_DELETE; - else if (slot == 21) type = TYPE_PASSWORD; - if (type != null) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_TITLE; - plugin.getChatInput() - .getState(p) - .ticketType = type; - if (TYPE_DELETE.equals(type)) { - p.sendMessage("§e请描述删号原因:"); - } else if (TYPE_PASSWORD.equals(type)) { - p.sendMessage("§e请描述需要协助的内容:"); - } else { - p.sendMessage("§e请输入工单标题:"); - } - } - return true; - } - - - if (title.equals(T_TICKET_MY)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals(T_TICKET_DETAIL)) { - return handleClickDetail(p, slot); - } - - if (title.equals(T_TICKET_ADMIN)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot == 45) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_INVITE_PROVIDER; - p.sendMessage("§e输入服务商玩家名:"); - return true; - } - if (slot == 46) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REMOVE_PROVIDER; - p.sendMessage("§e输入要移除的服务商:"); - return true; - } - if (slot == 47) { - showProviderList(p); - return true; - } - if (slot == 48) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ADMIN_TYPE; - p.sendMessage("§e选择工单类型:"); - p.sendMessage("§7bug/求助/举报/申请/其他"); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§d§l服务商面板")) { - if (slot == 22) { - plugin.getGui().openMain(p); - return true; - } - if (slot == 10) openGrabList(p); - else if (slot == 12) openMyAssigned(p); - else if (slot == 14) openMyTickets(p); - return true; - } - - if (title.equals("§6§l抢单大厅")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§b§l我处理中的工单")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - return false; - } - - private boolean handleClickDetail(Player p, - int slot) { - if (slot == 22) { - openMyTickets(p); - return true; - } - int id = gtFromDetail(p); - if (id <= 0) return true; - Map t = - plugin.getDb().getTicket(id); - if (t == null) return true; - - String st = sv(t, "status"); - int conf = nv(t, "admin_confirmed"); - String asg = sv(t, "assigned_to"); - - // 回复按钮 - if (slot == 15) { - if (!"closed".equals(st) - && !"withdrawn".equals(st) - && !"resolved".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPLY; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入回复内容:"); - } - return true; - } - - // 查看回复记录 - if (slot == 16) { - showReplies(p, id); - return true; - } - - // slot 19 - if (slot == 19) { - // 管理员:分配工单 - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ASSIGN; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage( - "§e输入: 服务商名 价格"); - return true; - } - // 管理员:取消指派(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - cancelTicket(p, id); - openDetail(p, id); - return true; - } - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - Map tk = - plugin.getDb().getTicket(id); - String tkType = sv(tk, "type"); - if (TYPE_DELETE.equals(tkType)) { - approveDeleteTicket(p, id); - return true; - } - plugin.getDb().completeTicket(id); - p.sendMessage("§a工单 #" + id - + " 已标记完结"); - openDetail(p, id); - return true; - } - } - // 服务商:抢单 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && "pending".equals(st) - && asg.isEmpty()) { - grabTicket(p, id); - openDetail(p, id); - return true; - } - // 服务商:标记完结 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - completeAsProvider(p, id); - openDetail(p, id); - return true; - } - return true; - } - - // slot 20 - if (slot == 20) { - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:自己接手(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 服务商:上报异常 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPORT_ISSUE; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入异常描述:"); - return true; - } - return true; - } - - // slot 21:驳回 - if (slot == 21) { - if (adm(p) && !"closed".equals(st) - && !"resolved".equals(st) - && !"withdrawn".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REJECT; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入驳回原因:"); - } - return true; - } - - return true; - } - - // ========== 聊天输入 ========== - - public boolean handleChatInput(Player p, - String msg, - ChatInputManager.InputType type) { - switch (type) { - case TICKET_TITLE: { - plugin.getChatInput() - .getState(p) - .ticketTitle = msg; - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return true; - } - case TICKET_DESC: { - String tt = plugin.getChatInput() - .getState(p).ticketType; - String tl = plugin.getChatInput() - .getState(p).ticketTitle; - plugin.getChatInput().reset(p); - submitTicket(p, tt, tl, msg); - return true; - } - case TICKET_SCORE: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - Map tk = - plugin.getDb() - .getTicket(tid2); - String st = tk != null - ? sv(tk, "status") - : ""; - if ("completed".equals(st)) { - plugin.getChatInput() - .reset(p); - confirmAndScore( - p, tid2, sc); - } else { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c工单尚未处理完成"); - } - } catch ( - NumberFormatException e) { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return true; - } - case TICKET_REJECT: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - plugin.getChatInput().reset(p); - rejectTicket(p, tid2, msg); - return true; - } - case TICKET_ASSIGN: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - plugin.getChatInput().reset(p); - assignToProvider(p, tid2, - prov, price); - runSync(() -> - openDetail(p, tid2)); - return true; - } - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return true; - } - plugin.getDb() - .addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return true; - } - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .isServiceProvider( - tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return true; - } - plugin.getDb() - .removeServiceProvider( - tgt); - p.sendMessage( - "§a已移除 " + tgt); - return true; - } - default: - return false; - } - } - - // ========== 私有 ========== - - private void settleTicket(int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) return; - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - ConfigManager cfg = - plugin.getConfig2(); - if (reward > 0) { - plugin.getDb().addPoints( - requester, reward); - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f获得 " - + reward - + " 积分奖励"); - } - } - if (!assigned.isEmpty()) { - int pts = (int) (score - * cfg.providerPointsPerScore); - if (pts > 0) { - plugin.getDb().addPoints( - assigned, pts); - } - double money = - cfg.baseEconomyReward - + score - * cfg.providerEconomyPerScore; - boolean ok = false; - try { - if (plugin.getEconomy() != null) { - OfflinePlayer op = - Bukkit.getOfflinePlayer( - assigned); - plugin.getEconomy() - .depositPlayer( - op, money); - ok = true; - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 经济失败: " - + e.getMessage()); - } - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) { - pp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 确认完结"); - if (pts > 0) - pp.sendMessage("§7积分: §e" - + pts); - if (ok) - pp.sendMessage("§7经济: §a$" - + String.format( - "%.0f", money)); - } - } - } - - private void notifyAdmins(String creator, - int tid) { - String tag = - plugin.getConfig2().adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§e§l[工单] §f" + creator - + " 提交了新工单 #" - + tid); - } - } - } - - private void playAssignedSound(Player p) { - for (int i = 0; i < 3; i++) { - final int d = i * 15; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline()) { - 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); - } - }, d); - } - } - - private void runSync(Runnable task) { - Bukkit.getScheduler() - .runTask(plugin, task); - } - - private boolean adm(Player p) { - return p.getScoreboardTags().contains( - plugin.getConfig2().adminTag); - } - - private void fillBg(Inventory g) { - ItemStack glass = mk( - Material.GRAY_STAINED_GLASS_PANE, - " "); - for (int i = 0; i < g.getSize(); i++) { - if (g.getItem(i) == null - || g.getItem(i).getType() - == Material.AIR) { - g.setItem(i, glass); - } - } - } - - private void fillList(Inventory g, - List> list) { - if (list.isEmpty()) { - g.setItem(22, mk(Material.BARRIER, - "§7暂无工单")); - return; - } - int slot = 0; - for (Map t : list) { - if (slot >= 45) break; - g.setItem(slot, makeItem(t)); - slot++; - } - } - /** - * 执行删号流程 - */ - /** - * 执行删号流程 - */ - public void processDeleteAccount(int tid, - Map t) { - String requester = sv(t, "requester"); - plugin.getDb().updateTicketStatus( - tid, "resolved"); - plugin.getDb().updateTicketField(tid, - "admin_confirmed", 1); - Player target = - Bukkit.getPlayer(requester); - if (target != null && target.isOnline()) { - plugin.backupInventory(target); - target.sendMessage( - "§a§l[工单] §f您的删号请求已通过"); - target.sendMessage( - "§7物品已备份,下次登录可取回"); - target.kickPlayer( - "§a删号成功,物品已备份。\n" - + "请等待5分钟后重新注册取回物品。"); - } else { - // 离线:仅标记,不操作数据库 - plugin.getLogger().info( - "[Sdf1_login] " + requester - + " 删号工单已通过,等待上线执行"); - } - // 延迟执行删号 - final String name = requester; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - plugin.getDb().deleteUser(name); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 删号工单已执行"); - }, 100L); - } - - /** - * 管理员手动批准删号 - */ - public void approveDeleteTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!TYPE_DELETE.equals(sv(t, "type"))) { - p.sendMessage("§c这不是删号工单"); - return; - } - p.sendMessage("§a工单 #" + tid - + " 已批准,正在执行删号..."); - processDeleteAccount(tid, t); - } - - /** - * 管理员取消工单(服务商异常后) - */ - /** - * 管理员取消工单(解除服务商绑定,管理员可自行处理) - */ - public void cancelTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - // 解除服务商绑定,状态回退到pending - plugin.getDb().updateTicketField( - tid, "assigned_to", ""); - plugin.getDb().updateTicketStatus( - tid, "pending"); - p.sendMessage("§a工单 #" + tid - + " 已取消指派,回到待分配状态"); - // 通知原服务商 - String assigned = sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player ap = Bukkit.getPlayer(assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 已被管理员取消指派"); - } - } - } - - - /** - * 服务商查看历史工单 - */ - public void openProviderHistory(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l我的历史工单"); - fillBg(g); - List> all = - plugin.getDb() - .getTicketsByProviderAll( - p.getName()); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - private ItemStack makeItem( - Map t) { - int id = nv(t, "id"); - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = - sv(t, "requester"); - String title = sv(t, "title"); - int reward = nv(t, "reward_amount"); - Material mat; - switch (status) { - case "processing": - mat = Material.YELLOW_DYE; - break; - case "replied": - mat = Material.CYAN_DYE; - break; - case "completed": - mat = Material.LIME_DYE; - break; - case "resolved": - mat = Material.LIME_DYE; - break; - case "closed": - mat = Material.GRAY_DYE; - break; - case "rejected": - mat = Material.RED_DYE; - break; - case "withdrawn": - mat = Material.GRAY_DYE; - break; - default: - mat = Material.ORANGE_DYE; - break; - } - ItemStack item = new ItemStack(mat); - ItemMeta im = item.getItemMeta(); - if (im != null) { - im.setDisplayName("§e#" + id - + " " + (title.isEmpty() - ? type : title)); - List lore = - new ArrayList<>(); - lore.add("§7类型: §f" + type); - lore.add("§7状态: " - + stxt(status)); - lore.add("§7提交者: §f" - + requester); - lore.add("§7奖励: §e" + reward - + " 积分"); - lore.add(""); - lore.add("§7点击查看详情"); - im.setLore(lore); - im.setCustomModelData(id); - item.setItemMeta(im); - } - return item; - } - - private int gt(ItemStack item) { - if (item == null) return -1; - ItemMeta im = item.getItemMeta(); - if (im == null) return -1; - if (im.hasCustomModelData()) - return im.getCustomModelData(); - return -1; - } - - private int gtFromDetail(Player p) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(10); - return gt(it); - } - - private String stxt(String status) { - switch (status) { - case "pending": - return "§e待处理"; - case "processing": - return "§b处理中"; - case "replied": - return "§9已回复"; - case "completed": - return "§a待确认"; - case "resolved": - return "§a已完结"; - case "closed": - return "§7已关闭"; - case "rejected": - return "§c已驳回"; - case "withdrawn": - return "§7已撤回"; - default: - return "§7" + status; - } - } - - private String sv(Map m, - String key) { - Object v = m.get(key); - return v != null - ? String.valueOf(v) : ""; - } - - private int nv(Map m, - String key) { - Object v = m.get(key); - return v instanceof Number - ? ((Number) v).intValue() : 0; - } - - private ItemStack mk(Material mat, - String name, String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) { - List list = - new ArrayList<>(); - for (String l : lore) { - if (!l.isEmpty()) - list.add(l); - } - if (!list.isEmpty()) - im.setLore(list); - } - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/UpdateChecker.java b/backup/20260605_152529/src/main/java/Sdf1_login/UpdateChecker.java deleted file mode 100644 index c5c01bb..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/UpdateChecker.java +++ /dev/null @@ -1,523 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; - -public class UpdateChecker { - - private final Main plugin; - private final String currentVersion; - - // ===== 仓库信息 ===== - private static final String GITHUB_OWNER = - "ypsdf1"; - private static final String GITHUB_REPO = - "sdf1_login"; - private static final String GITEE_OWNER = - "nihaoshidifu"; - private static final String GITEE_REPO = - "sdf1_login"; - - - // API 地址 - private static final String GITHUB_API = - "https://api.github.com/repos/" - + GITHUB_OWNER + "/" - + GITHUB_REPO - + "/tags"; - private static final String GITEE_API = - "https://gitee.com/api/v5/repos/" - + GITEE_OWNER + "/" - + GITEE_REPO - + "/tags"; - - // 仓库页面链接(给用户看的) - private static final String GITHUB_LINK = - "https://github.com/" - + GITHUB_OWNER + "/" - + GITHUB_REPO + "/releases"; - private static final String GITEE_LINK = - "https://gitee.com/" - + GITEE_OWNER + "/" - + GITEE_REPO + "/releases"; - - - // 单次请求超时(毫秒) - private static final int TIMEOUT_MS = 3000; - - // 熔断阈值 - private static final int TRIP_THRESHOLD = 5; - - // ===== 熔断状态(内存态,重启自动清除) ===== - private volatile boolean circuitTripped = - false; - private volatile int failCount = 0; - - // ===== 自动检测禁用(持久化到文件) ===== - private volatile boolean autoCheckDisabled = - false; - private final File disabledFile; - - public UpdateChecker(Main plugin) { - this.plugin = plugin; - this.currentVersion = - plugin.getDescription() - .getVersion(); - this.disabledFile = new File( - plugin.getDataFolder(), - "update_disabled.dat"); - loadDisabledState(); - } - - // ========== 持久化禁用状态 ========== - - private void loadDisabledState() { - if (disabledFile.exists()) { - try (BufferedReader br = - new BufferedReader( - new FileReader( - disabledFile))) { - String line = br.readLine(); - if (line != null) { - autoCheckDisabled = - "true".equals( - line.trim()); - } - } catch (Exception ignored) { - } - } - } - - private void saveDisabledState() { - try (PrintWriter pw = - new PrintWriter( - new FileWriter( - disabledFile))) { - pw.println(autoCheckDisabled - ? "true" : "false"); - } catch (Exception ignored) { - } - } - - // ========== 公开方法 ========== - - /** - * onEnable 调用(异步静默) - * 熔断或禁用则直接跳过 - */ - public void checkOnEnable() { - if (autoCheckDisabled) return; - if (circuitTripped) return; - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doCheck(null, true)); - } - - /** - * /sdf1_login update 调用(异步) - * 忽略熔断和禁用状态,强制检测 - * 只有拿到版号才解除熔断 - */ - public void checkUpdate( - CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> - doCheck(sender, - false)); - } - - // ========== 核心检测逻辑 ========== - private void doCheck(CommandSender sender, - boolean isAuto) { - - if (isAuto) { - if (autoCheckDisabled - || circuitTripped) { - return; - } - } - - // 通道1:GitHub - CheckResult gh = - tryChannel(GITHUB_API, - "GitHub", GITHUB_LINK); - - // 通道2:Gitee - CheckResult ge = - tryChannel(GITEE_API, - "Gitee", GITEE_LINK); - - // 选择成功的结果 - CheckResult chosen = null; - String failMsg = null; - - if (gh.isSuccess()) { - chosen = gh; - } else if (ge.isSuccess()) { - chosen = ge; - } else { - // 双通道均失败 - failCount++; - // 拼接两个通道的失败原因 - StringBuilder sb = - new StringBuilder(); - if (gh.error != null) { - sb.append("§7GitHub: §c") - .append(gh.error); - } - if (ge.error != null) { - if (sb.length() > 0) - sb.append("\n"); - sb.append("§7Gitee: §c") - .append(ge.error); - } - if (failCount >= TRIP_THRESHOLD) { - circuitTripped = true; - plugin.getLogger().warning( - "[Sdf1_login] 更新检测" - + "已熔断,连续失败" - + failCount + "次"); - } - if (sender != null) { - final String msg = - sb.toString(); - final boolean tripped = - circuitTripped; - final int fc = failCount; - Bukkit.getScheduler() - .runTask(plugin, () -> { - sender.sendMessage( - "§c§l[更新] " - + "检测失败"); - sender.sendMessage(msg); - sender.sendMessage( - "§e失败次数: " - + fc + "/" - + TRIP_THRESHOLD); - if (tripped) { - sender.sendMessage( - "§c已熔断," - + "请重启服务器" - + "重置"); - } - }); - } - return; - } - - // 至少一个通道成功 - failCount = 0; - - // 检查禁用信号 - if (isDisableSignal(chosen.version)) { - autoCheckDisabled = true; - saveDisabledState(); - plugin.getLogger().info( - "[Sdf1_login] 远程版本信号" - + " \"" + chosen.version - + "\",自动更新检测已禁用"); - if (sender != null) { - Bukkit.getScheduler() - .runTask(plugin, () -> - sender.sendMessage( - "§e[更新] 远程已" - + "禁用自动" - + "更新检测")); - } - return; - } - - // 手动调用:成功才解除熔断和禁用 - if (!isAuto) { - if (circuitTripped) { - circuitTripped = false; - } - if (autoCheckDisabled) { - autoCheckDisabled = false; - saveDisabledState(); - } - } - - final String src = chosen.source; - final String lnk = chosen.link; - final String rv = chosen.version; - - Bukkit.getScheduler() - .runTask(plugin, () -> { - if (sender == null) return; - - sender.sendMessage( - "§e[更新] 检测成功" - + " via: §a" + src); - sender.sendMessage( - "§e[更新] 本地版本:" - + " §f" - + currentVersion); - sender.sendMessage( - "§e[更新] 远程版本:" - + " §f" + rv); - - if (isNewer(rv, - currentVersion)) { - sender.sendMessage( - "§c[更新] 发现新" - + "版本: " + rv); - sender.sendMessage( - "§a请前往仓库下载" - + "更新:"); - sender.sendMessage( - "§a" + lnk); - } else { - sender.sendMessage( - "§a[更新] 已是" - + "最新版本"); - } - - // 显示另一通道的失败原因 - // (仅信息,不影响结果) - CheckResult other = - (src.equals("GitHub")) - ? ge : gh; - if (!other.isSuccess() - && other.error != null) { - sender.sendMessage( - "§7[更新] " - + other.source - + "不可用: " - + other.error); - } - }); - } - - - // ========== 禁用信号判断 ========== - - private boolean isDisableSignal( - String version) { - if (version == null) return false; - String v = version.trim().toLowerCase(); - return "999".equals(v) - || "关闭".equals(v) - || "false".equals(v) - || "禁用".equals(v); - } - - // ========== HTTP 请求(带超时) ========== - - // ========== 检测结果 ========== - - private static class CheckResult { - final String version; - final String source; - final String link; - final String error; - - CheckResult(String version, - String source, - String link, - String error) { - this.version = version; - this.source = source; - this.link = link; - this.error = error; - } - - boolean isSuccess() { - return version != null; - } - } - -// ========== HTTP 请求 ========== - - private CheckResult tryChannel( - String apiUrl, - String sourceName, - String link) { - HttpURLConnection conn = null; - try { - URL url = new URL(apiUrl); - conn = (HttpURLConnection) - url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty( - "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; " - + "Win64; x64) AppleWebKit/537.36 " - + "(KHTML, like Gecko) " - + "Chrome/120.0.0.0 Safari/537.36"); - conn.setRequestProperty( - "Accept", - "*/*"); - conn.setRequestProperty( - "Accept-Language", - "zh-CN,zh;q=0.9"); - conn.setConnectTimeout(TIMEOUT_MS); - conn.setReadTimeout(TIMEOUT_MS); - conn.setInstanceFollowRedirects(true); - - int code = conn.getResponseCode(); - - if (code == 404) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 未找到 (HTTP 404)"); - } - if (code == 403) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 被限流 (HTTP 403)"); - } - if (code != 200) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": HTTP " + code); - } - - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - conn - .getInputStream())); - StringBuilder sb = - new StringBuilder(); - String line; - while ((line = reader.readLine()) - != null) { - sb.append(line); - } - reader.close(); - - String tag = - parseTagName(sb.toString()); - if (tag == null) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 无法解析版本号"); - } - - return new CheckResult(tag, - sourceName, link, null); - - } catch (java.net - .SocketTimeoutException e) { - return new CheckResult(null, - sourceName, link, - sourceName + ": 连接超时"); - } catch (java.net.ConnectException e) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 连接被拒绝"); - } catch (java.net - .UnknownHostException e) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": DNS解析失败"); - } catch (Exception e) { - return new CheckResult(null, - sourceName, link, - sourceName + ": " - + e.getMessage()); - } finally { - if (conn != null) { - conn.disconnect(); - } - } - } - - - - /** - * 从 JSON 中提取 tag_name - */ - private String parseTagName(String json) { - // tags API 返回数组: [{"name":"v1.1",...}] - // releases API 返回对象: {"tag_name":"v1.1"} - - // 尝试 tag_name(releases格式) - int idx = - json.indexOf("\"tag_name\""); - if (idx >= 0) { - int colon = - json.indexOf(":", idx); - int q1 = - json.indexOf("\"", colon + 1); - int q2 = - json.indexOf("\"", q1 + 1); - if (q2 > q1) { - return cleanTag( - json.substring( - q1 + 1, q2)); - } - } - - // 尝试 "name"(tags格式) - idx = json.indexOf("\"name\""); - if (idx >= 0) { - int colon = - json.indexOf(":", idx); - int q1 = - json.indexOf("\"", colon + 1); - int q2 = - json.indexOf("\"", q1 + 1); - if (q2 > q1) { - return cleanTag( - json.substring( - q1 + 1, q2)); - } - } - - return null; - } - - private String cleanTag(String tag) { - if (tag.startsWith("v") - || tag.startsWith("V")) { - tag = tag.substring(1); - } - return tag.isEmpty() ? null : tag; - } - - - // ========== 版本比较 ========== - - private boolean isNewer(String remote, - String local) { - String[] r = remote.split("\\."); - String[] l = local.split("\\."); - int len = Math.max(r.length, l.length); - for (int i = 0; i < len; i++) { - int ri = i < r.length - ? parseNum(r[i]) : 0; - int li = i < l.length - ? parseNum(l[i]) : 0; - if (ri > li) return true; - if (ri < li) return false; - } - return false; - } - - private int parseNum(String s) { - try { - return Integer.parseInt( - s.replaceAll("[^0-9]", "")); - } catch (Exception e) { - return 0; - } - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/UserGroupManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/UserGroupManager.java deleted file mode 100644 index adaf907..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/UserGroupManager.java +++ /dev/null @@ -1,370 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.sql.*; -import java.util.*; -import java.util.Base64; -import java.util.concurrent.ConcurrentHashMap; - -public class UserGroupManager { - - private final Main plugin; - private Connection db; - private final Map groupConfigs = new ConcurrentHashMap<>(); - - public UserGroupManager(Main plugin) { - this.plugin = plugin; - initDB(); - loadGroupConfigs(); - startExpiryChecker(); - } - - // ===== DB ===== - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(plugin.getDataFolder(), "usergroup.db"); - db = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS user_group_config (" - + "group_name TEXT PRIMARY KEY," - + "display_name TEXT DEFAULT ''," - + "display_color TEXT DEFAULT '§f'," - + "display_emoji TEXT DEFAULT ''," - + "priority INTEGER DEFAULT 0," - + "is_permanent INTEGER DEFAULT 1," - + "duration_minutes INTEGER DEFAULT 0," - + "pack_url TEXT DEFAULT ''," - + "pack_hash TEXT DEFAULT ''," - + "pack_prompt TEXT DEFAULT 'Custom Resource Pack'," - + "pack_size INTEGER DEFAULT 0)"); - st.execute("CREATE TABLE IF NOT EXISTS user_group_member (" - + "player_name TEXT NOT NULL," - + "group_name TEXT NOT NULL," - + "added_by TEXT DEFAULT 'system'," - + "added_time INTEGER DEFAULT 0," - + "expiry_time INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, group_name))"); - st.close(); - } catch (Exception e) { - throw new RuntimeException("[UserGroup] DB init failed: " + e.getMessage(), e); - } - } - - // ===== 配置加载 ===== - - public void loadGroupConfigs() { - groupConfigs.clear(); - File dir = new File(plugin.getDataFolder(), "用户组"); - if (!dir.exists()) dir.mkdirs(); - File[] files = dir.listFiles((d, n) -> n.endsWith(".txt")); - if (files == null) return; - - for (File f : files) { - try { - List lines = java.nio.file.Files.readAllLines( - f.toPath(), StandardCharsets.UTF_8); - UserGroupConfig cfg = parseGroupConfig(lines); - if (cfg != null) { - groupConfigs.put(cfg.name, cfg); - saveGroupConfigToDB(cfg); - } - } catch (Exception e) { - plugin.getLogger().warning("[UserGroup] Failed to load: " + f.getName()); - } - } - } - - private UserGroupConfig parseGroupConfig(List lines) { - UserGroupConfig cfg = new UserGroupConfig(); - boolean inBlock = false; - - for (String raw : lines) { - String l = raw; - - // 块注释 - if (l.contains("")) { inBlock = false; continue; } - if (l.contains("/*")) { inBlock = true; continue; } - if (l.contains("*/")) { inBlock = false; continue; } - if (inBlock) continue; - - // 行注释 - l = l.trim(); - if (l.isEmpty()) continue; - if (l.startsWith("#") || l.startsWith("//")) continue; - - // 行内注释 - int h = l.indexOf('#'); - int s = l.indexOf("//"); - if (h >= 0) l = l.substring(0, h).trim(); - if (s >= 0 && (h < 0 || s < h)) l = l.substring(0, s).trim(); - if (l.isEmpty()) continue; - - // 解析键值对 - String key = l; - String val = ""; - int idx = l.indexOf('\uff1a'); - if (idx < 0) idx = l.indexOf(':'); - if (idx >= 0) { - key = l.substring(0, idx).trim(); - val = l.substring(idx + 1).trim(); - if (val.startsWith("\"") && val.endsWith("\"")) - val = val.substring(1, val.length() - 1); - } - - switch (key) { - case "组名": - case "name": cfg.name = val; break; - case "显示名": - case "display": cfg.displayName = val; break; - case "颜色": - case "color": cfg.displayColor = val; break; - case "符号": - case "emoji": cfg.displayEmoji = val; break; - case "优先级": - case "priority": - try { cfg.priority = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "类型": - case "type": - cfg.permanent = val.equalsIgnoreCase("永久") || val.equalsIgnoreCase("permanent"); - break; - case "时长": - case "duration": - try { cfg.durationMinutes = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "资源包URL": - case "pack_url": cfg.packUrl = val; break; - case "资源包Hash": - case "pack_hash": cfg.packHash = val; break; - case "资源包提示": - case "pack_prompt": cfg.packPrompt = val; break; - case "资源包大小": - case "pack_size": - try { cfg.packSize = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "本地文件": - case "local_file": cfg.localFile = val; break; - } - } - - if (cfg.name == null || cfg.name.isEmpty()) return null; - return cfg; - } - - private void saveGroupConfigToDB(UserGroupConfig cfg) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO user_group_config " - + "(group_name, display_name, display_color, display_emoji," - + " priority, is_permanent, duration_minutes," - + " pack_url, pack_hash, pack_prompt, pack_size)" - + " VALUES (?,?,?,?,?,?,?,?,?,?,?)"); - ps.setString(1, cfg.name); - ps.setString(2, cfg.displayName); - ps.setString(3, cfg.displayColor); - ps.setString(4, cfg.displayEmoji); - ps.setInt(5, cfg.priority); - ps.setInt(6, cfg.permanent ? 1 : 0); - ps.setInt(7, cfg.durationMinutes); - ps.setString(8, cfg.packUrl); - ps.setString(9, cfg.packHash); - ps.setString(10, cfg.packPrompt); - ps.setInt(11, cfg.packSize); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ===== 玩家操作 ===== - - public boolean addPlayer(String player, String groupName, String addedBy, long durationMinutes) { - UserGroupConfig cfg = groupConfigs.get(groupName); - if (cfg == null) return false; - long now = System.currentTimeMillis(); - long expiry = cfg.permanent ? 0 : now + durationMinutes * 60 * 1000; - if (!cfg.permanent && durationMinutes > 0) expiry = now + durationMinutes * 60 * 1000; - else if (!cfg.permanent) expiry = now + cfg.durationMinutes * 60 * 1000; - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO user_group_member " - + "(player_name, group_name, added_by, added_time, expiry_time)" - + " VALUES (?,?,?,?,?)"); - ps.setString(1, player); - ps.setString(2, groupName); - ps.setString(3, addedBy); - ps.setLong(4, now); - ps.setLong(5, expiry); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - Player p = plugin.getServer().getPlayerExact(player); - if (p != null) sendResourcePack(p); - return true; - } - - public boolean removePlayer(String player, String groupName) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM user_group_member WHERE player_name=? AND group_name=?"); - ps.setString(1, player); - ps.setString(2, groupName); - int rows = ps.executeUpdate(); - ps.close(); - return rows > 0; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } - - public List> getPlayerGroups(String player) { - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM user_group_member WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - long now = System.currentTimeMillis(); - while (rs.next()) { - Map row = new LinkedHashMap<>(); - row.put("group_name", rs.getString("group_name")); - row.put("added_by", rs.getString("added_by")); - long expiry = rs.getLong("expiry_time"); - row.put("expiry_time", expiry); - row.put("expired", expiry > 0 && expiry <= now); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public UserGroupConfig getHighestGroup(String player) { - List> groups = getPlayerGroups(player); - long now = System.currentTimeMillis(); - UserGroupConfig best = null; - for (Map g : groups) { - long expiry = (long) g.get("expiry_time"); - if (expiry > 0 && expiry <= now) continue; - String name = (String) g.get("group_name"); - UserGroupConfig cfg = groupConfigs.get(name); - if (cfg != null && (best == null || cfg.priority > best.priority)) { - best = cfg; - } - } - return best; - } - - public boolean isPlayerInGroup(String player, String groupName) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM user_group_member WHERE player_name=? AND group_name=?"); - ps.setString(1, player); - ps.setString(2, groupName); - boolean exists = ps.executeQuery().next(); - ps.close(); - return exists; - } catch (SQLException e) { - return false; - } - } - - // ===== 过期自动移除 ===== - - private void startExpiryChecker() { - new BukkitRunnable() { - @Override - public void run() { - checkExpiry(); - } - }.runTaskTimerAsynchronously(plugin, 1200L, 1200L); // 每分钟检查 - } - - private void checkExpiry() { - try { - long now = System.currentTimeMillis(); - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, group_name FROM user_group_member " - + "WHERE expiry_time > 0 AND expiry_time <= ?"); - ps.setLong(1, now); - ResultSet rs = ps.executeQuery(); - List expired = new ArrayList<>(); - while (rs.next()) { - expired.add(new String[]{ - rs.getString("player_name"), - rs.getString("group_name")}); - } - rs.close(); ps.close(); - - for (String[] e : expired) { - PreparedStatement del = db.prepareStatement( - "DELETE FROM user_group_member WHERE player_name=? AND group_name=?"); - del.setString(1, e[0]); - del.setString(2, e[1]); - del.executeUpdate(); - del.close(); - plugin.getLogger().info("[UserGroup] 过期移除: " + e[0] + " -> " + e[1]); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ===== 资源包分发 ===== - - public void sendResourcePack(Player p) { - UserGroupConfig cfg = getHighestGroup(p.getName()); - if (cfg == null || cfg.packUrl == null || cfg.packUrl.isEmpty()) return; - - String url = cfg.packUrl; - String prompt = cfg.packPrompt != null - ? cfg.packPrompt : "Custom Resource Pack"; - - plugin.getServer().getScheduler().runTaskLater(plugin, () -> { - try { - p.setResourcePack(url, new byte[0], prompt, false); - plugin.getLogger().info("[UserGroup] 资源包已发送: " - + p.getName() + " -> " + cfg.displayName); - } catch (Exception e) { - plugin.getLogger().warning( - "[UserGroup] 资源包发送失败: " + e.getMessage()); - } - }, 40L); - } - - public Map getGroupConfigs() { - return groupConfigs; - } - - // ===== 配置类 ===== - - public static class UserGroupConfig { - public String name; - public String displayName = ""; - public String displayColor = "§f"; - public String displayEmoji = ""; - public int priority = 0; - public boolean permanent = true; - public int durationMinutes = 0; - public String packUrl = ""; - public String packHash = ""; - public String packPrompt = "Custom Resource Pack"; - public int packSize = 0; - public String localFile = ""; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/VerificationManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/VerificationManager.java deleted file mode 100644 index 3fc4ceb..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/VerificationManager.java +++ /dev/null @@ -1,158 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.UUID; -import java.util.function.Consumer; - -public class VerificationManager { - - private final Main plugin; - - public VerificationManager(Main plugin) { - this.plugin = plugin; - } - - public boolean isBedrockPlayer(Player p) { - String uuid = p.getUniqueId().toString(); - return uuid.contains("f851ba4") - || uuid.startsWith("00000000-0000-0000"); - } - - public boolean isOnlineMode() { - return Bukkit.getServer().getOnlineMode(); - } - - public void verifyPremiumAsync(Player player, - Consumer callback) { - Thread t = new Thread(() -> doVerify(player, callback), - "Sdf1_login-PremiumVerify"); - t.setDaemon(true); - t.start(); - } - - private void doVerify(Player player, - Consumer callback) { - String name = player.getName(); - String uuid = player.getUniqueId().toString(); - - plugin.getLogger().info( - "[Sdf1_login] ====== 验证开始 ======"); - plugin.getLogger().info( - "[Sdf1_login] 玩家: " + name); - plugin.getLogger().info( - "[Sdf1_login] 当前UUID: " + uuid); - - try { - UUID offlineUuid = UUID.nameUUIDFromBytes( - ("OfflinePlayer:" + name) - .getBytes(StandardCharsets.UTF_8)); - String offlineStr = offlineUuid.toString(); - - plugin.getLogger().info( - "[Sdf1_login] 离线UUID: " + offlineStr); - - boolean isOfflineUuid = - player.getUniqueId().equals(offlineUuid); - - plugin.getLogger().info( - "[Sdf1_login] UUID匹配离线格式: " - + isOfflineUuid); - - if (!isOfflineUuid) { - plugin.getLogger().info( - "[Sdf1_login] 结论: 正版玩家"); - plugin.getLogger().info( - "[Sdf1_login] ====== 验证结束 ======"); - callback.accept(true); - return; - } - - plugin.getLogger().info( - "[Sdf1_login] UUID为离线格式," - + "查询Mojang API..."); - UUID mojangUuid = fetchMojangUuid(name); - - if (mojangUuid != null) { - plugin.getLogger().info( - "[Sdf1_login] Mojang返回UUID: " - + mojangUuid); - plugin.getLogger().info( - "[Sdf1_login] 结论: 盗版客户端" - + "使用了正版名字"); - } else { - plugin.getLogger().info( - "[Sdf1_login] Mojang无此名字"); - plugin.getLogger().info( - "[Sdf1_login] 结论: 纯离线玩家"); - } - - plugin.getLogger().info( - "[Sdf1_login] ====== 验证结束 ======"); - callback.accept(false); - - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 验证异常: " - + e.getMessage()); - callback.accept(false); - } - } - - private UUID fetchMojangUuid(String name) { - HttpURLConnection conn = null; - try { - URL url = new URL( - "https://api.mojang.com/users/profiles/minecraft/" - + name); - conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - conn.setRequestMethod("GET"); - - int code = conn.getResponseCode(); - plugin.getLogger().info( - "[Sdf1_login] Mojang API响应: " + code); - - if (code == 200) { - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - conn.getInputStream(), - StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - reader.close(); - - String json = sb.toString(); - String uuidStr = json.replaceAll( - ".*\"id\"\\s*:\\s*\"([^\"]+)\".*", - "$1"); - plugin.getLogger().info( - "[Sdf1_login] Mojang原始UUID: " - + uuidStr); - - String formatted = uuidStr.replaceFirst( - "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", - "$1-$2-$3-$4-$5"); - return UUID.fromString(formatted); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] Mojang查询异常: " - + e.getMessage()); - } finally { - if (conn != null) conn.disconnect(); - } - return null; - } -} diff --git a/backup/20260605_152529/src/main/java/Sdf1_login/WelcomeManager.java b/backup/20260605_152529/src/main/java/Sdf1_login/WelcomeManager.java deleted file mode 100644 index 68bd3ef..0000000 --- a/backup/20260605_152529/src/main/java/Sdf1_login/WelcomeManager.java +++ /dev/null @@ -1,163 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -public class WelcomeManager { - - private final Main plugin; - private final Random random = new Random(); - private final List registerMsgs = - new ArrayList<>(); - private final List loginMsgs = - new ArrayList<>(); - - public WelcomeManager(Main plugin) { - this.plugin = plugin; - loadMessages(); - } - - public void loadMessages() { - registerMsgs.clear(); - loginMsgs.clear(); - File f = new File( - plugin.getDataFolder(), - "欢迎仪式.txt"); - if (!f.exists()) { - writeDefault(f); - } - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets - .UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("注册") - || t.equals("注册:")) { - section = "register"; - continue; - } - if (t.equals("登录") - || t.equals("登录:")) { - section = "login"; - continue; - } - // 支持 = 和 : 两种分隔 - if (section.isEmpty()) { - if (t.startsWith("注册") - && (t.contains("=") - || t.contains(":"))) { - int idx = t.indexOf('='); - int idx2 = t.indexOf(':'); - if (idx < 0) idx = idx2; - else if (idx2 >= 0) - idx = Math.min(idx, idx2); - String v = t.substring(idx + 1) - .trim(); - if (!v.isEmpty()) - registerMsgs.add( - colorize(v)); - continue; - } - if (t.startsWith("登录") - && (t.contains("=") - || t.contains(":"))) { - int idx = t.indexOf('='); - int idx2 = t.indexOf(':'); - if (idx < 0) idx = idx2; - else if (idx2 >= 0) - idx = Math.min(idx, idx2); - String v = t.substring(idx + 1) - .trim(); - if (!v.isEmpty()) - loginMsgs.add( - colorize(v)); - continue; - } - continue; - } - if (!t.isEmpty()) { - if (section.equals("register")) - registerMsgs.add( - colorize(t)); - else - loginMsgs.add( - colorize(t)); - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[欢迎] 加载失败: " - + e.getMessage()); - } - plugin.getLogger().info( - "[欢迎] 加载完成: 注册=" - + registerMsgs.size() - + " 登录=" + loginMsgs.size()); - } - - public void onRegister(Player p) { - sendRandom(p, registerMsgs, "注册"); - } - - public void onLogin(Player p) { - sendRandom(p, loginMsgs, "登录"); - } - - private void sendRandom(Player p, - List msgs, String tag) { - if (msgs.isEmpty()) return; - String msg = msgs.get( - random.nextInt(msgs.size())); - msg = msg.replace("{player}", - p.getName()); - final String finalMsg = msg; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> Bukkit - .broadcastMessage( - finalMsg), 10L); - } - - private String colorize(String msg) { - return msg.replace("&", "\u00a7"); - } - - private void writeDefault(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets - .UTF_8))) { - pw.println("# 欢迎仪式配置"); - pw.println("# &为颜色代码"); - pw.println("# {player} = 玩家名"); - pw.println(); - pw.println("注册:"); - pw.println("&a&l欢迎 &f{player} &a加入服务器!&7[注册]"); - pw.println("&6&l{player} &e已完成注册,欢迎入驻!&7[注册]"); - pw.println("&b&l新成员 &f{player} &b已就位!&7[注册]"); - pw.println(); - pw.println("登录:"); - pw.println("&a&l欢迎回来 &f{player}&a!&7[登录]"); - pw.println("&e&l{player} &e已登录,继续冒险吧!&7[登录]"); - pw.println("&b&l{player} &b回来了!&7[登录]"); - } catch (IOException ignored) { - } - } -} diff --git a/backup/20260605_152529/src/main/resources/plugin.yml b/backup/20260605_152529/src/main/resources/plugin.yml deleted file mode 100644 index f6868d9..0000000 --- a/backup/20260605_152529/src/main/resources/plugin.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Sdf1_login -version: 1.0 -main: Sdf1_login.Main -api-version: '1.21' -commands: - sdf1_login: - description: Sdf1_login主命令 - usage: /sdf1_login - reg: - description: 注册 - usage: /reg <密码> - login: - description: 登录 - usage: /login <密码> - l: - description: 登录快捷版 - usage: /l <密码> - 注册: - description: 注册(中文) - usage: /注册 <密码> - 登录: - description: 登录(中文) - usage: /登录 <密码> - 玩家信息: - description: 打开面板(中文) - usage: /玩家信息 - 改密码: - description: 修改密码(中文) - usage: /改密码 <旧密码> <新密码> <确认新密码> - 设置邮箱: - description: 设置邮箱(中文) - usage: /设置邮箱 <邮箱地址> - 撤销: - description: 撤销操作(中文) - usage: /撤销 - 删除: - description: 删除玩家(中文) - usage: /删除 <玩家> <密码> - 找回密码: - description: 找回密码(中文) - usage: /找回密码 - 签到: - description: 每日签到(中文) - usage: /签到 - #oa: - # description: 申请删除账号 - # usage: /oa [玩家名] - 删除账号: - description: 申请删除账号(中文) - usage: /删除账号 [玩家名] - 通过: - description: 批准OA请求(中文) - usage: /通过 <编号> - 拒绝: - description: 拒绝OA请求(中文) - usage: /拒绝 <编号> [理由] - accept: - description: 批准OA请求 - usage: /accept <编号> - deny: - description: 拒绝OA请求 - usage: /deny <编号> [理由] - email: - description: 绑定邮箱 - usage: /email <邮箱> - 绑定: - description: 绑定邮箱(中文) - usage: /绑定 <邮箱> - 绑定邮箱: - description: 绑定邮箱 - usage: /绑定邮箱 - chat: - description: 聊天过滤管理 - usage: /chat - recycle: - description: 打开垃圾回收站 - usage: /recycle - 垃圾清理: - description: 手动清理掉落物 - usage: /垃圾清理 - menu: - description: 雪球菜单开关控制 - usage: /menu <开/关> - pvp : - description: PVP榜单 - usage: /pvp - cypay : - description: 草原探险债券 - usage: /cypay - printer : - description: 打印债券流水 - usage: /printer - shop : - description: 游戏商人 - usage: /shop - 商店: - description: 打开商店 - usage: /商店 - #testorder: - # description: 测试订单中心 - # usage: /testorder - protect: - description: 区域保护 - usage: /protect \ No newline at end of file diff --git a/backup/20260605_185500/CheckInManager.java.backup b/backup/20260605_185500/CheckInManager.java.backup deleted file mode 100644 index b8e612c..0000000 --- a/backup/20260605_185500/CheckInManager.java.backup +++ /dev/null @@ -1,305 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -public class CheckInManager { - - private final Main plugin; - - public CheckInManager(Main plugin) { - this.plugin = plugin; - } - - // ===== 今日是否已签到 ===== - public boolean isCheckedInToday( - String name) { - String last = (String) plugin.getDb() - .getField(name, - "last_checkin_date"); - String today = new SimpleDateFormat( - "yyyy-MM-dd") - .format(new Date()); - return today.equals(last); - } - - // ===== 签到 ===== - // ===== 签到 ===== - public String checkIn(Player p) { - String name = p.getName(); - if (isCheckedInToday(name)) { - return plugin.getConfig2() - .msg("checkin_already"); - } - - Object streakObj = plugin.getDb() - .getField(name, "checkin_streak"); - int streak = streakObj != null - ? ((Number) streakObj).intValue() - : 0; - - String lastDate = (String) plugin.getDb() - .getField(name, "last_checkin_date"); - String today = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date()); - - if (lastDate != null && !lastDate.isEmpty()) { - try { - java.util.Date lastD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(lastDate); - java.util.Date todayD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(today); - long diff = todayD.getTime() - - lastD.getTime(); - if (diff > 86400000L * 2) { - // 间隔超过2天,重置连续天数 - streak = 1; - } else if (diff > 86400000L) { - // 间隔1-2天(连续签到),天数+1 - streak++; - } - // 间隔<=1天(同一天),不改变连续天数 - } catch (Exception ignored) { - } - } else { - // 首次签到 - streak = 1; - } - - double multi = 1.0; - if (streak >= 21) multi = 1.5; - else if (streak >= 14) multi = 1.3; - else if (streak >= 7) multi = 1.1; - - int basePoints = - plugin.getConfig2().checkinPoints; - int points = (int) (basePoints * multi); - - plugin.getDb().addPoints(name, points); - plugin.getDb().setField(name, - "checkin_streak", streak); - plugin.getDb().setField(name, - "last_checkin_date", today); - - Object totalObj = plugin.getDb() - .getField(name, "total_checkin_days"); - int total = totalObj != null - ? ((Number) totalObj).intValue() - : 0; - plugin.getDb().setField(name, - "total_checkin_days", total + 1); - - // ★ 债券盲盒奖励(6参数版,写入流水)★ - int bondMin = - plugin.getConfig2().checkinBondMin; - int bondMax = - plugin.getConfig2().checkinBondMax; - if (bondMin <= 0) bondMin = 1; - if (bondMax < bondMin) bondMax = bondMin; - int bondReward = bondMin - + new java.util.Random() - .nextInt(bondMax - bondMin + 1); - plugin.getBonds().addBonds(name, bondReward, - "daily_sign", "", "签到系统", - "每日签到连续" + streak + "天"); - - return plugin.getConfig2() - .msg("checkin_success") - .replace("{points}", - String.valueOf(points)) - .replace("{streak}", - String.valueOf(streak)) - .replace("{multi}", - String.valueOf(multi)) - + " §e+" + bondReward + "债券"; - } - - // ===== 获取连续天数 ===== - public int getStreak(String name) { - Object val = plugin.getDb() - .getField(name, - "checkin_streak"); - if (val instanceof Number) { - return ((Number) val) - .intValue(); - } - return 0; - } - // 标准化日期 - private String normalizeDate(String input) { - String s = input.trim().replace("/", "-"); - if (s.matches("\\d{8}")) { - return s.substring(0, 4) + "-" - + s.substring(4, 6) + "-" - + s.substring(6, 8); - } - return s; - } - // ===== 补签入口 ===== - public String backCheckIn( - Player p, String dateStr) { - String name = p.getName(); - String normalized = - normalizeDate(dateStr); - - // 严格日期验证 - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd"); - sdf.setLenient(false); - java.util.Date target; - try { - target = sdf.parse(normalized); - } catch (Exception ex) { - return "§c无效日期: " + dateStr; - } - String reformat = sdf.format(target); - if (!reformat.equals(normalized)) { - return "§c无效日期: " + dateStr; - } - - String today = sdf.format(new Date()); - if (normalized.compareTo(today) >= 0) { - return "§c今天不需要补签"; - } - - String threeDaysAgo = getPastDate(3); - if (normalized.compareTo( - threeDaysAgo) < 0) { - return "§c只能补签3天内" - + "(最多到 " + threeDaysAgo + ")"; - } - - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - boolean alreadyBacked = false; - if (!existing.isEmpty()) { - for (String d : existing.split(",")) { - if (normalizeDate(d.trim()) - .equals(normalized)) { - alreadyBacked = true; - break; - } - } - } - if (alreadyBacked) { - return "§c该日期已补签: " + normalized; - } - - Object normalCheck = - plugin.getDb().getField( - name, "last_checkin_date"); - String lastCheckin = normalCheck != null - ? normalCheck.toString() : ""; - if (lastCheckin.equals(normalized)) { - return "§c该日期已签到: " + normalized; - } - - // ★ 补签消耗:3债券 + 10积分 - // 积分不够时:用 9债券(3倍)代替 ★ - int bondCost = 3; - int pointCost = 10; - int bondAltCost = 9; // 3倍债券替代 - - int myBonds = plugin.getBonds() - .getBonds(name); - Object ptsObj = plugin.getDb() - .getField(name, "points"); - int myPoints = ptsObj != null - ? ((Number) ptsObj).intValue() - : 0; - - - // 方案A:3债券 + 10积分 - boolean canA = myBonds >= bondCost - && myPoints >= pointCost; - // 方案B:9债券(积分不够时) - boolean canB = myBonds >= bondAltCost - && myPoints < pointCost; - - if (!canA && !canB) { - if (myPoints < pointCost - && myBonds < bondAltCost) { - return "§c补签费用不足" - + "(需3债券+10积分," - + "或9债券代替)" - + " 当前: " - + myBonds + "债券 " - + myPoints + "积分"; - } - if (myBonds < bondCost) { - return "§c债券不足(需" - + bondCost + "枚)"; - } - return "§c积分不足(需" - + pointCost + "分)"; - } - - // 执行扣款 - if (canA) { - // 方案A:3债券 + 10积分 - plugin.getBonds().deductBonds( - name, bondCost, - "admin_deduct", "", - "补签系统", "补签扣款"); - plugin.getDb().deductPoints( - name, pointCost); - } else { - // 方案B:9债券代替 - plugin.getBonds().deductBonds( - name, bondAltCost, - "admin_deduct", "", - "补签系统", "补签扣款(债券替代)"); - } - - doBackCheckIn(p, normalized); - - String costMsg = canA - ? bondCost + "债券+" + pointCost + "积分" - : bondAltCost + "债券(替代)"; - return "§a补签成功!日期: " - + normalized - + " §7消耗: " + costMsg; - } - - - - // ===== 执行补签 ===== - // ===== 执行补签 ===== - public void doBackCheckIn( - Player p, String dateStr) { - String name = p.getName(); - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - String updated = existing.isEmpty() - ? dateStr - : existing + "," + dateStr; - plugin.getDb().setField(name, - "backcheck_dates", updated); - int streak = getStreak(name); - plugin.getDb().setField(name, - "checkin_streak", streak + 1); - // ★ 补签不发放奖励,只记录 - } - - // ===== 获取N天前日期 ===== - private String getPastDate( - int daysAgo) { - Calendar cal = - Calendar.getInstance(); - cal.add(Calendar.DAY_OF_MONTH, - -daysAgo); - return new SimpleDateFormat( - "yyyy-MM-dd") - .format(cal.getTime()); - } -} \ No newline at end of file diff --git a/backup/20260605_185500/DatabaseManager.java.backup b/backup/20260605_185500/DatabaseManager.java.backup deleted file mode 100644 index cb2b8f7..0000000 --- a/backup/20260605_185500/DatabaseManager.java.backup +++ /dev/null @@ -1,1758 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.logging.Logger; - -public class DatabaseManager { - - private Connection db; - private final File dataFolder; - private final Logger logger; - - public DatabaseManager(File dataFolder) { - this.dataFolder = dataFolder; - this.logger = Logger.getLogger("Sdf1_login"); - } - - // ==================== 初始化 ==================== - - public void init() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(dataFolder, - "login.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 用户表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "users (" - + "player_name TEXT PRIMARY KEY," - + "password_hash TEXT NOT NULL," - + "password_salt TEXT NOT NULL," - + "email TEXT DEFAULT ''," - + "register_time INTEGER DEFAULT 0," - + "last_login_time INTEGER DEFAULT 0," - + "total_online_time INTEGER DEFAULT 0," - + "last_online_check INTEGER DEFAULT 0," - + "is_logged_in INTEGER DEFAULT 0," - + "blocks_broken INTEGER DEFAULT 0," - + "blocks_placed INTEGER DEFAULT 0," - + "gift_stage INTEGER DEFAULT 0," - + "gift_claimed TEXT DEFAULT ''," - + "invite_code TEXT DEFAULT ''," - + "invited_by TEXT DEFAULT ''," - + "monthly_invite_count INTEGER DEFAULT 0," - + "invite_month TEXT DEFAULT ''," - + "points INTEGER DEFAULT 0," - + "checkin_streak INTEGER DEFAULT 0," - + "last_checkin_date TEXT DEFAULT ''," - + "total_checkin_days INTEGER DEFAULT 0," - + "temp_password TEXT DEFAULT ''," - + "temp_pw_expire INTEGER DEFAULT 0," - + "temp_pw_used INTEGER DEFAULT 0," - + "ip_address TEXT DEFAULT ''," - + "tasks_completed TEXT DEFAULT ''," - + "premium_uuid TEXT DEFAULT ''," - + "register_ip TEXT DEFAULT ''," - + "register_type TEXT DEFAULT 'manual'," - + "backcheck_dates TEXT DEFAULT ''" - + ")"); - - - // 安全添加 users 缺失列 - safeAdd(st, "tasks_completed", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_password", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_pw_expire", - "INTEGER DEFAULT 0"); - safeAdd(st, "temp_pw_used", - "INTEGER DEFAULT 0"); - safeAdd(st, "ip_address", - "TEXT DEFAULT ''"); - safeAdd(st, "premium_uuid", - "TEXT DEFAULT ''"); - safeAdd(st, "register_ip", - "TEXT DEFAULT ''"); - safeAdd(st, "register_type", - "TEXT DEFAULT 'manual'"); - - // ===== 背包备份表:安全迁移 ===== - // 检查旧表是否存在(用 inventory_data 列判断) - boolean hasOldTable = false; - try { - ResultSet rs = st.executeQuery( - "SELECT inventory_data " - + "FROM inventory_backups " - + "LIMIT 1"); - hasOldTable = true; - rs.close(); - } catch (SQLException e) { - hasOldTable = false; - } - - if (hasOldTable) { - // 旧表存在,迁移数据到新表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups_new (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - // 迁移旧数据 - try { - st.execute("INSERT INTO " - + "inventory_backups_new " - + "(player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience) " - + "SELECT player_name, " - + "inventory_data, " - + "armor_data, " - + "extra_data, " - + "player_level, " - + "player_exp " - + "FROM inventory_backups"); - } catch (SQLException ignored) { - } - st.execute("DROP TABLE " - + "inventory_backups"); - st.execute("ALTER TABLE " - + "inventory_backups_new " - + "RENAME TO " - + "inventory_backups"); - } else { - // 无旧表或已迁移,直接创建 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - } - - // 安全报警表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "security_alerts (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "alert_ip TEXT NOT NULL," - + "alert_time INTEGER NOT NULL," - + "alert_date TEXT NOT NULL," - + "notified INTEGER DEFAULT 0," - + "notified_time INTEGER DEFAULT 0)"); - // PVP区域表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "name TEXT NOT NULL UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER DEFAULT 0" - + ")"); - -// PVP击杀总榜表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT NOT NULL," - + "region_name TEXT NOT NULL," - + "kills INTEGER DEFAULT 0," - + "deaths INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, region_name)" - + ")"); - - -// 菜单图标表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "menu_icons (" - + "player_name TEXT PRIMARY KEY," - + "icon_b64 TEXT DEFAULT ''," - + "item_name TEXT DEFAULT ''," - + "save_time INTEGER DEFAULT 0" - + ")"); - - // 工单表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "tickets (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "type TEXT NOT NULL," - + "status TEXT DEFAULT 'pending'," - + "priority TEXT DEFAULT 'normal'," - + "requester TEXT NOT NULL," - + "assigned_to TEXT DEFAULT ''," - + "title TEXT DEFAULT ''," - + "description TEXT DEFAULT ''," - + "reward_amount INTEGER DEFAULT 0," - + "score INTEGER DEFAULT 0," - + "target_player TEXT DEFAULT ''," - + "create_time INTEGER NOT NULL," - + "update_time INTEGER DEFAULT 0," - + "complete_time INTEGER DEFAULT 0," - + "admin_confirmed INTEGER DEFAULT 0," - + "reject_reason TEXT DEFAULT ''" - + ")"); - - // 工单回复表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "ticket_replies (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "ticket_id INTEGER NOT NULL," - + "sender TEXT NOT NULL," - + "message TEXT NOT NULL," - + "create_time INTEGER NOT NULL" - + ")"); - - // 服务商表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "service_providers (" - + "player_name TEXT PRIMARY KEY," - + "role TEXT DEFAULT 'waiter'," - + "active INTEGER DEFAULT 1," - + "join_time INTEGER DEFAULT 0" - + ")"); - // 垃圾回收站物品表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "items (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "item_data TEXT DEFAULT ''," - + "amount INTEGER DEFAULT 1," - + "create_time INTEGER DEFAULT 0" - + ")"); - safeAdd(st, "menu_snowball", - "INTEGER DEFAULT 1"); - st.close(); - logger.info("[Sdf1_login] 数据库初始化完成"); - } catch (Exception e) { - throw new RuntimeException( - "[Sdf1_login] DB初始化失败: " - + e.getMessage(), e); - } - } - - - private void safeAdd(Statement st, - String col, String def) { - try { - st.executeUpdate("ALTER TABLE users " - + "ADD COLUMN " + col + " " + def); - } catch (SQLException ignored) { - } - } - - public void close() { - try { - if (db != null && !db.isClosed()) - db.close(); - } catch (Exception ignored) { - } - } -// ==================== 菜单图标 ==================== - - public void saveMenuIcon(String name, - String iconB64, String itemName) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO menu_icons " - + "(player_name, icon_b64, " - + "item_name, save_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, iconB64); - ps.setString(3, itemName); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT icon_b64 FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - String b64 = null; - if (rs.next()) { - b64 = rs.getString("icon_b64"); - } - rs.close(); - ps.close(); - return b64; - } catch (SQLException e) { - return null; - } - } - - public void deleteMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM menu_icons " - + "WHERE player_name=? " - + "AND icon_b64 IS NOT NULL " - + "AND icon_b64 != '' " - + "LIMIT 1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - // ==================== 用户操作 ==================== - - public boolean userExists(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public void createUser(String name, String hash, - String salt) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO users " - + "(player_name, " - + "password_hash, " - + "password_salt, " - + "register_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, hash); - ps.setString(3, salt); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ========== 临时密码管理 ========== - - public void clearTempPassword(String name) { - setField(name, "temp_password", null); - setField(name, "temp_pw_expire", 0); - setField(name, "temp_pw_used", 1); - } - - public void clearAllTempPasswords() { - try { - Statement st = db.createStatement(); - st.executeUpdate( - "UPDATE users SET " - + "temp_password=NULL," - + "temp_pw_expire=0," - + "temp_pw_used=1 " - + "WHERE temp_password " - + "IS NOT NULL"); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getPlayerIPByName( - String name) { - Object ip = getField(name, "ip"); - return ip != null - ? String.valueOf(ip) : null; - } - - public boolean checkPasswordOrTemp( - String name, String hash) { - // 先检查主密码 - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String mainHash = - rs.getString("password_hash"); - rs.close(); - ps.close(); - if (hash.equals(mainHash)) - return true; - } else { - rs.close(); - ps.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - // 再检查临时密码 - try { - PreparedStatement ps2 = db.prepareStatement( - "SELECT temp_password, " - + "temp_pw_expire, " - + "temp_pw_used " - + "FROM users " - + "WHERE player_name=?"); - ps2.setString(1, name); - ResultSet rs2 = ps2.executeQuery(); - if (rs2.next()) { - String tempHash = - rs2.getString( - "temp_password"); - long expire = - rs2.getLong( - "temp_pw_expire"); - int used = - rs2.getInt( - "temp_pw_used"); - rs2.close(); - ps2.close(); - if (tempHash != null - && !tempHash.isEmpty() - && hash.equals(tempHash) - && System.currentTimeMillis() - < expire - && used != 1) { - return true; - } - } else { - rs2.close(); - ps2.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - public Map getUser(String name) { - Map r = - new LinkedHashMap<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - r.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return r; - } - - public void setField(String name, String field, - Object val) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE users SET " + field - + "=? " - + "WHERE player_name=?"); - if (val instanceof Integer) - ps.setInt(1, (Integer) val); - else if (val instanceof Long) - ps.setLong(1, (Long) val); - else - ps.setString(1, - String.valueOf(val)); - ps.setString(2, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Object getField(String name, - String field) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT " + field - + " FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - Object val = rs.next() - ? rs.getObject(1) : null; - rs.close(); - ps.close(); - return val; - } catch (SQLException e) { - return null; - } - } - - public boolean checkPassword(String name, - String hash) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String stored = - rs.getString("password_hash"); - rs.close(); - ps.close(); - return hash.equals(stored); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - - public boolean isUsingTempPassword(String name) { - String h = (String) getField(name, - "temp_password"); - return h != null && !h.isEmpty(); - } - - public void setLoggedIn(String name, - boolean val) { - setField(name, "is_logged_in", - val ? 1 : 0); - } - - public void addPoints(String name, int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - setField(name, "points", curVal + amount); - } - - public boolean deductPoints(String name, - int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - if (curVal < amount) return false; - setField(name, "points", - curVal - amount); - return true; - } - public List> - getAllUsers() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM users"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - public List> - getTicketsByProviderAll( - String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deleteUser(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void recordIP(String name, String ip) { - if (ip != null && !ip.isEmpty()) { - setField(name, "ip_address", ip); - } - } - - // ==================== IP查询 ==================== - - /** - * 统计指定IP注册的账号数量 - */ - public int countIPAccounts(String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address=?"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count; - } catch (SQLException e) { - return 0; - } - } - - /** - * 获取指定IP下所有账号详情 - */ - public List> - getAccountDetailsByIP(String ip) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, " - + "register_time " - + "FROM users " - + "WHERE ip_address=? " - + "ORDER BY register_time"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("register_time", - rs.getLong("register_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 背包备份 ==================== - - public void saveInventoryBackup(String name, - String contents, String armor, - String extra, int level, - double experience) { - try { - PreparedStatement del = - db.prepareStatement( - "DELETE FROM " - + "inventory_backups " - + "WHERE player_name=?"); - del.setString(1, name); - del.executeUpdate(); - del.close(); - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO " - + "inventory_backups" - + " (player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience, " - + "save_time) " - + "VALUES " - + "(?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, contents); - ps.setString(3, armor); - ps.setString(4, extra); - ps.setInt(5, level); - ps.setDouble(6, experience); - ps.setLong(7, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public String[] loadInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time " - + "DESC LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void deleteInventoryBackup( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM inventory_backups " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - boolean has = rs.next(); - rs.close(); - ps.close(); - return has; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } -// ==================== PVP区域 ==================== - - public void savePVPRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions " - + "(name, world, " - + "x1, y1, z1, " - + "x2, y2, z2, " - + "create_time) " - + "VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map - getPVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getAllPVPRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - db.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deletePVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - -// ==================== PVP击杀 ==================== - - public int getPVPKills( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int kills = 0; - if (rs.next()) - kills = rs.getInt("kills"); - rs.close(); - ps.close(); - return kills; - } catch (SQLException e) { - return 0; - } - } - - public int getPVPDeaths( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int deaths = 0; - if (rs.next()) - deaths = rs.getInt("deaths"); - rs.close(); - ps.close(); - return deaths; - } catch (SQLException e) { - return 0; - } - } - - public void addPVPKill( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,1,0) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "kills=kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void addPVPDeath( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,0,1) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "deaths=deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getPVPKillTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT player_name, " - + "kills, deaths " - + "FROM pvp_kills " - + "WHERE " - + "region_name=? " - + "AND kills>0 " - + "ORDER BY kills " - + "DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - // ==================== 安全报警 ==================== - - public void recordSecurityAlert( - String playerName, String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO security_alerts " - + "(player_name, alert_ip, " - + "alert_time, alert_date) " - + "VALUES (?,?,?,?)"); - ps.setString(1, playerName); - ps.setString(2, ip); - long now = System.currentTimeMillis(); - ps.setLong(3, now); - ps.setString(4, - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date( - now))); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - - } - - public List> - getUnnotifiedAlerts( - String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM security_alerts " - + "WHERE player_name=? " - + "AND notified=0 " - + "ORDER BY alert_time ASC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void markAlertNotified(int alertId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE security_alerts " - + "SET notified=1, " - + "notified_time=? " - + "WHERE id=?"); - ps.setLong(1, - System.currentTimeMillis()); - ps.setInt(2, alertId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - /** - * 获取玩家最近N条背包备份 - */ - public List> - getInventoryBackups(String name, - int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id, save_time " - + "FROM inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time DESC " - + "LIMIT ?"); - ps.setString(1, name); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", - rs.getInt("id")); - row.put("save_time", - rs.getLong("save_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - /** - * 获取指定ID的备份数据 - */ - public String[] getInventoryBackupById( - int backupId) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM inventory_backups " - + "WHERE id=?"); - ps.setInt(1, backupId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - - // ==================== 工单操作 ==================== - - public int createTicket(String type, - String status, String priority, - String requester, String title, - String description, - int rewardAmount, - String targetPlayer) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO tickets " - + "(type, status, priority, " - + "requester, title, " - + "description, " - + "reward_amount, " - + "target_player, " - + "create_time) " - + "VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, type); - ps.setString(2, status); - ps.setString(3, priority); - ps.setString(4, requester); - ps.setString(5, title); - ps.setString(6, description); - ps.setInt(7, rewardAmount); - ps.setString(8, targetPlayer); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ResultSet rs = - ps.getGeneratedKeys(); - int id = -1; - if (rs.next()) id = rs.getInt(1); - rs.close(); - ps.close(); - return id; - } catch (SQLException e) { - e.printStackTrace(); - return -1; - } - } - - public void updateTicketStatus(int ticketId, - String status) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET status=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, status); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void assignTicket(int ticketId, - String provider) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "assigned_to=?, " - + "status='processing', " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, provider); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void completeTicket(int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='completed', " - + "complete_time=?, " - + "update_time=? " - + "WHERE id=?"); - long now = System.currentTimeMillis(); - ps.setLong(1, now); - ps.setLong(2, now); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void confirmTicket(int ticketId, - int score) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "admin_confirmed=1, " - + "score=?, " - + "status='resolved', " - + "update_time=? " - + "WHERE id=?"); - ps.setInt(1, score); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public void rejectTicketComplete(int ticketId, - String reason) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='rejected', " - + "reject_reason=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, reason); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 更新工单任意字段 - */ - public void updateTicketField(int ticketId, - String field, Object value) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " + field - + "=? WHERE id=?"); - if (value instanceof Integer) - ps.setInt(1, (Integer) value); - else if (value instanceof Long) - ps.setLong(1, (Long) value); - else - ps.setString(1, - String.valueOf(value)); - ps.setInt(2, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map getTicket( - int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE id=?"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getTicketsByStatus(String status) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status=? " - + "ORDER BY create_time DESC"); - ps.setString(1, status); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void addTicketReply(int ticketId, - String sender, String message) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO ticket_replies " - + "(ticket_id, sender, " - + "message, create_time) " - + "VALUES (?,?,?,?)"); - ps.setInt(1, ticketId); - ps.setString(2, sender); - ps.setString(3, message); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - // 更新工单状态为已回复 - updateTicketStatus(ticketId, - "replied"); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getTicketReplies(int ticketId) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM ticket_replies " - + "WHERE ticket_id=? " - + "ORDER BY create_time ASC"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - /** - * 获取已回复但超时未互动的工单 - */ - public List> - getRepliedStaleTickets( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='replied' " - + "AND update_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByPlayer(String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE requester=? " - + "ORDER BY create_time DESC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByProvider(String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "AND (status='processing' " - + "OR status='replied') " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getUnconfirmedCompleted( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='completed' " - + "AND admin_confirmed=0 " - + "AND complete_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 服务商操作 ==================== - - public void addServiceProvider(String name, - String role) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO " - + "service_providers " - + "(player_name, role, " - + "active, join_time) " - + "VALUES (?,?,1,?)"); - ps.setString(1, name); - ps.setString(2, role); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void removeServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM service_providers " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean isServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM service_providers " - + "WHERE player_name=? " - + "AND active=1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public List> - getAllServiceProviders() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM service_providers " - + "WHERE active=1"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Connection getDb() { - return db; - } -} diff --git a/backup/20260605_185500/Main.java.backup b/backup/20260605_185500/Main.java.backup deleted file mode 100644 index ed02c8c..0000000 --- a/backup/20260605_185500/Main.java.backup +++ /dev/null @@ -1,5568 +0,0 @@ -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 garbageBusy = - new ConcurrentHashMap<>(); - // [ADDED] 群系检测节流 - private final Map lastBiomeCheck = - new ConcurrentHashMap<>(); - // 在 private WelcomeManager welcome; 字段(若不存在)添加: - private WelcomeManager welcome; - private final Set pendingAdminAuth = - new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - /* - 资源包下载部分控制,不经过RadioDownloadListener的熔断控制 - */ - private RadioDownloadListener radioDL; - private final Set chatInputActive = - ConcurrentHashMap.newKeySet(); - // 补签确认:玩家名 → "日期:总积分" - public final Map - pendingBackCheck = - new HashMap<>(); - public MenuManager menu; - - private final Set loggedIn = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Set needsPasswordChange = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map joinTime = - new ConcurrentHashMap<>(); - private final Map joinLoc = - new HashMap<>(); - private final Map ipAutoLogin = - new HashMap<>(); - private static final long IP_SKIP_MS = 300000L; - private SalesStatsManager salesStats; - - private final Map 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 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 - pwdRollback = - new ConcurrentHashMap<>(); - private final Map menuChatField = - new java.util.concurrent.ConcurrentHashMap<>(); - private final Map 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 getLoggedIn() { - return loggedIn; - } - - public Set 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 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 entry - : areaProtection.getPendingClearTasks().entrySet()) { - entry.getValue().cancel(); - // 立即清理效果 - Player p = Bukkit.getPlayer(entry.getKey()); - if (p != null && p.isOnline()) { - List 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 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> it = - joinTime.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry 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 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> alerts = - db.getUnnotifiedAlerts(name); - if (alerts.isEmpty()) return; - p.sendMessage("§c§l========== 安全报警 =========="); - for (Map 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 - + ": "); - 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 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> 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 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 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 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 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 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 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 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 filterTab(List opts, String prefix) { - List 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 urls = - chatFilter.extractUrls(msg); - if (urls.isEmpty()) return; - - // 过滤白名单 - List 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 sameIP = bondManager - .getSameIPPlayers(target); - List 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> txs = transfersOnly - ? bondManager.getTransferTransactions( - target, days) - : bondManager.getTransactions( - target, days); - - int slot = 0; - for (Map 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 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 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 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 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 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 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 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 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 "); - 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 "); - 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 "); - 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> 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 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 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 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 §7- 添加白名单URL"); - p.sendMessage("§e/chat remove §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 onTabComplete( - CommandSender sender, Command cmd, - String label, String[] args) { - List 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 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 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 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 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 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 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; - } -} \ No newline at end of file diff --git a/backup/20260605_185500/QuestStorage.java.backup b/backup/20260605_185500/QuestStorage.java.backup deleted file mode 100644 index 45dbabf..0000000 --- a/backup/20260605_185500/QuestStorage.java.backup +++ /dev/null @@ -1,138 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -public class QuestStorage { - - private Connection conn; - - public QuestStorage(File dataFolder) { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - dataFolder, - "quest_data.db"); - conn = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getPath()); - initTable(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void initTable() - throws SQLException { - Statement stmt = - conn.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_conditions (" - + "player TEXT NOT NULL, " - + "condition_key TEXT NOT NULL, " - + "value TEXT DEFAULT '1', " - + "timestamp INTEGER, " - + "PRIMARY KEY " - + "(player, condition_key))"); - stmt.close(); - } - - /** - * 标记条件已完成(value = "1") - */ - public void setCondition( - String player, - String condition) { - setConditionRaw( - player, condition, "1"); - } - - /** - * 写入任意值(计数器等) - */ - public void setConditionRaw( - String player, - String condition, - String value) { - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO " - + "quest_conditions " - + "(player, " - + "condition_key, " - + "value, timestamp) " - + "VALUES " - + "(?, ?, ?, ?)"); - ps.setString(1, player); - ps.setString(2, condition); - ps.setString(3, value); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 查询条件值 - */ - public Object getCondition( - String player, - String condition) { - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value FROM " - + "quest_conditions " - + "WHERE player = ? " - + "AND " - + "condition_key = ?"); - ps.setString(1, player); - ps.setString(2, condition); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - String val = - rs.getString("value"); - rs.close(); - ps.close(); - // 尝试解析为数字 - try { - if (val.contains(".")) { - return Double.parseDouble( - val); - } - return Long.parseLong(val); - } catch (NumberFormatException e) { - return val; - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) { - conn.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } -} diff --git a/backup/20260605_185500/QuestTracker.java.backup b/backup/20260605_185500/QuestTracker.java.backup deleted file mode 100644 index 0089ee2..0000000 --- a/backup/20260605_185500/QuestTracker.java.backup +++ /dev/null @@ -1,3692 +0,0 @@ -package Sdf1_login; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.generator.structure.Structure; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -// 如果 Bonds 在 Sdf1_login 包下: -import Sdf1_login.BondManager; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - - -public class QuestTracker { - - private final Plugin plugin; - private final QuestStorage storage; - private final Map - playerBiome = new HashMap<>(); - private final Map> - questCache = new HashMap<>(); - private final List - checkInRecords = new ArrayList<>(); - private final Map - lastCheckInPos = new HashMap<>(); - private static boolean loggedStructureRegistry - = false; - - public void recordCheckIn( - String player, - String biomeKey, - String category, - String worldName, - double x, double y, double z) { - String record = player - + "|" + worldName - + "|" + biomeKey - + "|" + category - + "|" + String.format( - "%.1f", x) - + "|" + String.format( - "%.1f", y) - + "|" + String.format( - "%.1f", z) - + "|" + System.currentTimeMillis(); - checkInRecords.add(record); - plugin.getLogger().info( - "[QuestTracker] 打卡记录: " - + record); - } - - public List getCheckInRecords() { - return new ArrayList<>(checkInRecords); - } - - public List getPlayerRecords( - String player) { - List list = new ArrayList<>(); - for (String r : checkInRecords) { - if (r.startsWith( - player.toLowerCase() - + "|")) { - list.add(r); - } - } - return list; - } - - - public QuestTracker(Plugin plugin) { - this.plugin = plugin; - this.storage = new QuestStorage( - plugin.getDataFolder()); - - loadAllQuests(); - } - - // ========== 重载任务配置 ========== - public void reload() { - questCache.clear(); - loadAllQuests(); - plugin.getLogger().info( - "[QuestTracker] 任务配置已重载"); - } - - public void debugPlayer(String player) { - plugin.getLogger().info( - "[QuestTracker] === " - + player + " 任务数据 ==="); - } - public void shutdown() { - if (storage != null) { - storage.close(); - } - } - - /** - * 玩家移动时记录当前群系 - */ - public void onPlayerMove( - String player, - String biomeName) { - playerBiome.put(player, biomeName); - } - /** - * 标记玩家已打卡该群系 - */ - // ========== 群系分类映射表 ========== - private static final Map - BIOME_CATEGORY = new HashMap<>(); - static { - // 海洋群系 - BIOME_CATEGORY.put("minecraft:ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋"); - // 平原群系 - BIOME_CATEGORY.put("minecraft:plains", "平原"); - BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原"); - BIOME_CATEGORY.put("minecraft:meadow", "平原"); - // 森林群系 - BIOME_CATEGORY.put("minecraft:forest", "森林"); - BIOME_CATEGORY.put("minecraft:flower_forest", "森林"); - BIOME_CATEGORY.put("minecraft:birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:dark_forest", "森林"); - BIOME_CATEGORY.put("minecraft:taiga", "森林"); - BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:cherry_grove", "森林"); - BIOME_CATEGORY.put("minecraft:jungle", "森林"); - BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林"); - BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林"); - // 沙漠群系 - BIOME_CATEGORY.put("minecraft:desert", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠"); - BIOME_CATEGORY.put("minecraft:beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠"); - // 恶地群系 - BIOME_CATEGORY.put("minecraft:badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地"); - // 雪原群系 - BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原"); - BIOME_CATEGORY.put("minecraft:grove", "雪原"); - BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原"); - BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_river", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原"); - // 沙砾群系 - BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾"); - // 沼泽群系 - BIOME_CATEGORY.put("minecraft:swamp", "沼泽"); - BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽"); - // 河流 - BIOME_CATEGORY.put("minecraft:river", "河流"); - // 蘑菇岛 - BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛"); - // 地下群系 - BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下"); - BIOME_CATEGORY.put("minecraft:lush_caves", "地下"); - BIOME_CATEGORY.put("minecraft:deep_dark", "地下"); - // 下界群系 - BIOME_CATEGORY.put("minecraft:nether_wastes", "下界"); - BIOME_CATEGORY.put("minecraft:crimson_forest", "下界"); - BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); - BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); - BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); - // 岩岸/沙滩 - BIOME_CATEGORY.put("minecraft:stony_shore", - "石岸"); -// 海岸 - BIOME_CATEGORY.put("minecraft:mushroom_fields", - "蘑菇岛"); -// 苍白之园 - BIOME_CATEGORY.put( - "minecraft:pale_garden", "森林"); -// 末地小型岛屿 - BIOME_CATEGORY.put( - "minecraft:small_end_islands", "末地"); -// 主世界结构归类 - BIOME_CATEGORY.put("minecraft:village", "平原"); - BIOME_CATEGORY.put( - "minecraft:desert_pyramid", "沙漠"); - BIOME_CATEGORY.put( - "minecraft:jungle_pyramid", "森林"); - BIOME_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - - // 末地群系 - BIOME_CATEGORY.put("minecraft:the_end", "末地"); - BIOME_CATEGORY.put("minecraft:the_void", "末地"); - BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); - - - } - - // 所有群系大类列表 - private static final String[] - ALL_CATEGORIES = { - "海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "石岸", "蘑菇岛", "地下", - "下界", "末地" - }; - - // ===== 结构映射(PaperMC 1.21.4 实际结构 key)===== - private static final Map - STRUCTURE_CATEGORY = - new HashMap<>(); - static { - // 主世界结构 - STRUCTURE_CATEGORY.put( - "minecraft:village_plains", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_desert", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_savanna", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_snowy", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_taiga", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:desert_pyramid", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:jungle_pyramid", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - STRUCTURE_CATEGORY.put( - "minecraft:igloo", "雪原"); - STRUCTURE_CATEGORY.put( - "minecraft:mansion", "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:pillager_outpost", - "掠夺者前哨站"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft_mesa", - "地下"); - // 要塞可生成在任何主世界群系下,映射为"主世界" - // 结构检测返回"主世界"时跳过Y轴"地下"判定 - STRUCTURE_CATEGORY.put( - "minecraft:stronghold", - "主世界"); - // 远古城市是结构,应分类为"远古城市"而非"地下" - STRUCTURE_CATEGORY.put( - "minecraft:ancient_city", - "远古城市"); - STRUCTURE_CATEGORY.put( - "minecraft:trail_ruins", - "远古遗迹"); - STRUCTURE_CATEGORY.put( - "minecraft:trial_chambers", - "试炼密室"); - STRUCTURE_CATEGORY.put( - "minecraft:monument", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_cold", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_warm", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck_beached", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:buried_treasure", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_desert", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_jungle", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_mountain", - "山地"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_ocean", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_swamp", - "沼泽"); - // 下界结构 - STRUCTURE_CATEGORY.put( - "minecraft:fortress", "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:bastion_remnant", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:nether_fossil", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_nether", - "下界"); - // 末地结构 - STRUCTURE_CATEGORY.put( - "minecraft:end_city", "末地"); - } - - // 所有群系分类的条件关键词映射 - private static final Map - CATEGORY_KEYWORDS = new HashMap<>(); - static { - CATEGORY_KEYWORDS.put("海洋", "海洋"); - CATEGORY_KEYWORDS.put("平原", "平原"); - CATEGORY_KEYWORDS.put("森林", "森林"); - CATEGORY_KEYWORDS.put("沙漠", "沙漠"); - CATEGORY_KEYWORDS.put("恶地", "恶地"); - CATEGORY_KEYWORDS.put("雪原", "雪原"); - CATEGORY_KEYWORDS.put("沙砾", "沙砾"); - CATEGORY_KEYWORDS.put("沼泽", "沼泽"); - CATEGORY_KEYWORDS.put("河流", "河流"); - CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛"); - CATEGORY_KEYWORDS.put("地下", "地下"); - CATEGORY_KEYWORDS.put("下界", "下界"); - CATEGORY_KEYWORDS.put("末地", "末地"); - // 条件文本中的别名映射 - CATEGORY_KEYWORDS.put("地狱", "下界"); - CATEGORY_KEYWORDS.put("下届", "下界"); - CATEGORY_KEYWORDS.put("主岛", "末地"); - CATEGORY_KEYWORDS.put("石岸", "石岸"); - CATEGORY_KEYWORDS.put("沙滩", "石岸"); - CATEGORY_KEYWORDS.put("海岸", "石岸"); - - // ===== 结构名称关键词映射 ===== - // 主世界结构 - CATEGORY_KEYWORDS.put("村庄", "村庄"); - CATEGORY_KEYWORDS.put("沙漠神殿", "沙漠"); - CATEGORY_KEYWORDS.put("丛林神庙", "森林"); - CATEGORY_KEYWORDS.put("沼泽小屋", "沼泽"); - CATEGORY_KEYWORDS.put("雪屋", "雪原"); - CATEGORY_KEYWORDS.put("林地府邸", "森林"); - CATEGORY_KEYWORDS.put("掠夺者前哨站", "平原"); - CATEGORY_KEYWORDS.put("废弃矿井", "地下"); - CATEGORY_KEYWORDS.put("要塞", "主世界"); - CATEGORY_KEYWORDS.put("远古城市", "远古城市"); - CATEGORY_KEYWORDS.put("古迹废墟", "地下"); - CATEGORY_KEYWORDS.put("试炼密室", "地下"); - CATEGORY_KEYWORDS.put("海底废墟", "海洋"); - CATEGORY_KEYWORDS.put("沉船", "海洋"); - CATEGORY_KEYWORDS.put("埋藏的宝箱", "海洋"); - CATEGORY_KEYWORDS.put("废弃传送门", "主世界"); - CATEGORY_KEYWORDS.put("化石", "主世界"); - - // 下界结构 - CATEGORY_KEYWORDS.put("下界要塞", "下界"); - CATEGORY_KEYWORDS.put("猪灵堡垒", "下界"); - CATEGORY_KEYWORDS.put("玄武岩结构", "下界"); - - // 末地结构 - CATEGORY_KEYWORDS.put("末地城", "末地"); - CATEGORY_KEYWORDS.put("末地船", "末地"); - - // 下界相关别名 - CATEGORY_KEYWORDS.put("地狱要塞", "下界"); - CATEGORY_KEYWORDS.put("下界堡垒", "下界"); - - } - - /** - * 用世界维度名分类(最可靠) - * 世界名含 the_nether → 下界 - * 世界名含 the_end → 末地 - * 其余 → 主世界 - */ - public String mapBiomeCategory( - String rawKey, String worldName) { - // 优先用世界名判断维度 - if (worldName != null) { - String w = - worldName.toLowerCase(); - if (w.contains("nether")) - return "下界"; - if (w.contains("end")) - return "末地"; - } - - // 用群系映射表 - if (rawKey != null) { - String lower = - rawKey.toLowerCase(); - // 带 minecraft: 前缀的直接查 - String cat = - BIOME_CATEGORY.get(lower); - if (cat != null) - return cat; - - // 不带前缀的尝试补全 - String prefixed = - "minecraft:" + lower; - cat = BIOME_CATEGORY.get(prefixed); - if (cat != null) - return cat; - - // 模糊匹配群系名 - for (Map.Entry entry - : BIOME_CATEGORY.entrySet()) { - if (entry.getKey() - .contains(lower) - || lower.contains( - entry.getKey() - .replace( - "minecraft:", - ""))) { - return entry.getValue(); - } - } - } - - return "主世界"; - } - - // ========== 结构检测 ========== - - /** - * 检测指定位置是否存在自然生成的结构 - * 返回结构分类(如"村庄"、"沙漠"等),未找到返回null - * 使用 PaperMC World.hasStructureAt() + Registry.STRUCTURE API - */ - private String detectStructureAt( - String worldName, double x, - double y, double z) { - try { - World w = Bukkit.getWorld(worldName); - if (w == null) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测: 世界 '" - + worldName + "' 不存在"); - return null; - } - - int bx = (int) Math.floor(x); - int by = (int) Math.floor(y); - int bz = (int) Math.floor(z); - - // 首次调用时打印所有注册表结构键 - if (!loggedStructureRegistry) { - plugin.getLogger().info( - "[QuestTracker] === 结构注册表诊断 ==="); - int count = 0; - for (Structure s : Registry.STRUCTURE) { - NamespacedKey key = - Registry.STRUCTURE.getKey(s); - plugin.getLogger().info( - "[QuestTracker] 注册表结构 #" - + count + ": " + key); - count++; - } - plugin.getLogger().info( - "[QuestTracker] 注册表共 " - + count + " 个结构"); - plugin.getLogger().info( - "[QuestTracker] === 诊断结束 ==="); - loggedStructureRegistry = true; - } - - // 第1步: 精确位置检查 - io.papermc.paper.math.Position exactPos = - io.papermc.paper.math.Position.block( - bx, by, bz); - for (Structure structure : Registry.STRUCTURE) { - boolean hasAt = - w.hasStructureAt(exactPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey(structure); - String keyStr = (key != null) - ? key.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY.get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(精确): " - + keyStr + " -> category=" - + (cat != null - ? cat : "未映射")); - if (cat != null) return cat; - } - } - - // 第2步: Y轴偏移检查 (-5 到 +10) - for (int dy = -5; dy <= 10; dy++) { - if (dy == 0) continue; - int testY = by + dy; - io.papermc.paper.math.Position testPos = - io.papermc.paper.math.Position.block( - bx, testY, bz); - for (Structure structure - : Registry.STRUCTURE) { - boolean hasAt = w.hasStructureAt( - testPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey(structure); - String keyStr = (key != null) - ? key.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY - .get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(Y偏移" - + dy + "): " - + keyStr - + " -> category=" - + (cat != null - ? cat : "未映射")); - if (cat != null) return cat; - } - } - } - - // 第3步: 水平偏移检查 (±1格) - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) continue; - io.papermc.paper.math.Position offsetPos = - io.papermc.paper.math.Position - .block(bx + dx, by, - bz + dz); - for (Structure structure - : Registry.STRUCTURE) { - boolean hasAt = w.hasStructureAt( - offsetPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey( - structure); - String keyStr = (key != null) - ? key.toString() - : "unknown"; - String cat = - STRUCTURE_CATEGORY - .get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(偏移" - + dx + "," - + dz + "): " - + keyStr - + " -> category=" - + (cat != null - ? cat - : "未映射")); - if (cat != null) return cat; - } - } - } - } - - plugin.getLogger().info( - "[QuestTracker] 所有位置检查完毕, " - + "未匹配任何结构 (pos=" - + bx + "," + by + "," + bz - + ")"); - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测异常: " - + ex.getClass().getSimpleName() - + ": " + ex.getMessage()); - ex.printStackTrace(); - } - return null; - } - - /** - * 将结构中文名映射到对应的大类 - * 用于匹配任务条件中的结构名 - */ - private String mapStructureCategory( - String structureName) { - if (structureName == null) return ""; - - // 主世界结构 - if (structureName.contains("村庄")) - return "村庄"; - if (structureName.contains("沙漠神殿")) - return "沙漠"; - if (structureName.contains("丛林神庙") || structureName.contains("丛林神殿")) - return "森林"; - if (structureName.contains("沼泽小屋")) - return "沼泽"; - if (structureName.contains("雪屋") || structureName.contains("冰屋")) - return "雪原"; - if (structureName.contains("林地府邸")) - return "森林"; - if (structureName.contains("掠夺者前哨站")) - return "掠夺者前哨站"; - if (structureName.contains("废弃矿井") || structureName.contains("恶地废弃矿井")) - return "地下"; - // 要塞可生成在任何主世界群系下,返回"主世界" - if (structureName.contains("要塞")) - return "主世界"; - // 远古城市是结构,应分类为"远古城市" - if (structureName.contains("远古城市")) - return "远古城市"; - if (structureName.contains("远古遗迹") || structureName.contains("古迹废墟")) - return "远古遗迹"; - if (structureName.contains("试炼密室")) - return "试炼密室"; - if (structureName.contains("海底神殿") || structureName.contains("海洋神殿")) - return "海洋"; - if (structureName.contains("海底废墟") || structureName.contains("冻洋废墟") || structureName.contains("暖洋废墟")) - return "海洋"; - if (structureName.contains("沉船")) - return "海洋"; - if (structureName.contains("埋藏的宝箱") || structureName.contains("埋藏的宝藏")) - return "海洋"; - if (structureName.contains("废弃传送门")) - return "平原"; - if (structureName.contains("化石")) - return "主世界"; - - // 下界结构 - if (structureName.contains("下界要塞")) - return "下界"; - if (structureName.contains("猪灵堡垒") || structureName.contains("堡垒遗迹")) - return "下界"; - if (structureName.contains("玄武岩结构") || structureName.contains("下界化石")) - return "下界"; - - // 末地结构 - if (structureName.contains("末地城")) - return "末地"; - if (structureName.contains("末地船")) - return "末地"; - - return ""; - } - - -// ========== 解析为区块 ========== - - private List
parseIntoSections( - List lines) { - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - return parseHashSections(lines); - } else if (hasDash) { - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - continue; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseHashSections(converted); - } - return new ArrayList<>(); - } - - private List
parseHashSections( - List lines) { - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add(new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } else if (t.isEmpty()) { - // skip - } else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty() - && curTitle != null) { - curLines.add(cond); - } - } - } - if (curTitle != null) { - sections.add(new Section( - curTitle, curLines)); - } - return sections; - } - - private List
buildFlatSections( - List lines, - String category) { - List conditions = - new ArrayList<>(); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - conditions.add(cond); - } - } - if (conditions.isEmpty()) - return new ArrayList<>(); - List
list = - new ArrayList<>(); - list.add(new Section( - "第一阶段", conditions)); - return list; - } - - // ========== 文件加载入口 ========== - - private void loadAllQuests() { - loadCategory("新人任务"); - loadCategory("主线任务"); - loadCategory("支线任务"); - } - - private void loadCategory(String name) { - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 扫描中..."); - File folder = new File( - plugin.getDataFolder(), - name); - String path = "plugins/Sdf1_login/" - + name + "/"; - - if (!folder.exists()) { - folder.mkdirs(); - plugin.getLogger().warning( - "[QuestTracker] 已创建目录: " - + path); - questCache.put(name, - new ArrayList<>()); - return; - } - - File[] mdFiles = folder.listFiles( - (d, n) -> n.endsWith(".md")); - - if (mdFiles == null - || mdFiles.length == 0) { - plugin.getLogger().warning( - "[QuestTracker] " + path - + " 下无 .md 文件"); - questCache.put(name, - new ArrayList<>()); - return; - } - Arrays.sort(mdFiles); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 发现 " + mdFiles.length - + " 个文件,开始扫描..."); - - // 合并所有文件的阶段 - TreeMap merged = - new TreeMap<>(); - int fileCount = 0; - - for (int fi = 0; - fi < mdFiles.length; fi++) { - File file = mdFiles[fi]; - List lines = - readFileLines(file); - - if (lines.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 空文件,跳过"); - continue; - } - - List
sections = - parseIntoSections(lines); - - if (sections.isEmpty()) { - sections = buildFlatSections( - lines, name); - } - - if (sections.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 无法识别,跳过"); - continue; - } - - // 每个文件独立处理阶段号 - int defaultNum = fi + 1; - buildSingleFileStages( - sections, name, - merged, defaultNum); - - fileCount++; - } - - if (merged.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " + name - + ": 无有效任务数据"); - questCache.put(name, - new ArrayList<>()); - return; - } - - // 日志 - for (Map.Entry en - : merged.entrySet()) { - int num = en.getKey() >= 1000 - ? en.getKey() - 999 - : en.getKey(); - plugin.getLogger().info( - "[QuestTracker] " + name - + " 阶段" + num - + " \"" - + en.getValue().displayName - + "\" → 条件" - + en.getValue() - .conditions.size() - + "项, 详情" - + en.getValue() - .details.size() - + "项, 奖励" - + en.getValue() - .rewards.size() - + "项"); - } - - List list = - new ArrayList<>( - merged.values()); - questCache.put(name, list); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 解析完成 → " - + list.size() + " 个阶段" - + " (来自 " + fileCount - + " 个文件)"); - } - - private Integer extractStageNumber( - String title) { - if (title == null) return null; - String t = title.toLowerCase().trim(); - - // 含阶段/stage/关/chapter 关键词 - boolean isStage = - t.contains("阶段") - || t.contains("stage") - || t.contains("关卡") - || t.contains("章节") - || t.contains("chapter"); - - if (isStage) { - String digits = title - .replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - try { - return Integer.parseInt(digits); - } catch (NumberFormatException e) { - // fall through - } - } - return parseChineseNumber(title); - } - - // 无关键词但以数字开头(如 "1. 挖掘") - String trimmed = t - .replaceAll("^[.、\\s]+", "") - .trim(); - if (trimmed.length() > 0 - && Character.isDigit( - trimmed.charAt(0))) { - String num = trimmed - .replaceAll("[^0-9]", ""); - if (!num.isEmpty()) { - try { - return Integer.parseInt(num); - } catch (NumberFormatException e) { - // fall through - } - } - } - - return null; - } -// ========== 领取记录 ========== - - /** - * 标记已领取某阶段奖励 - */ - public void markClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return; - // 用阶段编号代替显示名,保证稳定 - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - storage.setCondition(player, key); - } - - public boolean hasClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return false; - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - return toBool( - storage.getCondition( - player, key)); - } - - private int parseChineseNumber( - String title) { - String[] cn = {"一", "二", "三", - "四", "五", "六", "七", - "八", "九", "十"}; - if (title.contains("十")) { - for (int j = 0; j < 9; j++) { - if (title.contains(cn[j])) { - return 10 + j + 1; - } - } - return 10; - } - for (int i = 0; i < 9; i++) { - if (title.contains(cn[i])) { - return i + 1; - } - } - return 0; - } - - // ========== md 解析核心 ========== - - /** - * 读取文件所有行 - */ - private List readFileLines( - File file) { - List lines = - new ArrayList<>(); - try { - BufferedReader br = - new BufferedReader( - new FileReader(file)); - String line; - while ((line = br.readLine()) - != null) { - lines.add(line); - } - br.close(); - } catch (Exception e) { - plugin.getLogger().warning( - "[QuestTracker] 读取 " - + file.getName() - + " 失败: " - + e.getMessage()); - } - return lines; - } - - /** - * 自动检测格式并解析单个文件 - */ - private List parseMdFile( - File file, String category) { - List lines = - readFileLines(file); - if (lines.isEmpty()) - return new ArrayList<>(); - - // 检测分隔符 - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) - hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - // # 分隔格式 - return parseSections( - lines, category); - } else if (hasDash) { - // ---- 分隔格式:转换为 # 后复用 - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - title = "separator"; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseSections( - converted, category); - } else { - // 无分隔符:整文件一个阶段 - return parseFlat(lines, category); - } - } - - /** - * # 分隔格式 → 区块 → 阶段 - */ - private List parseSections( - List lines, - String category) { - // 拆分为区块 - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - - // # 开头 → 新区块 - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add( - new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } - // 空行/分隔线 → 跳过 - else if (t.isEmpty() - || t.startsWith("----") - || t.startsWith("====") - || t.startsWith("***")) { - // skip - } - // 普通行 → 区块内容 - else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - curLines.add(cond); - } - } - } - // 保存最后一个区块 - if (curTitle != null) { - sections.add( - new Section( - curTitle, curLines)); - } - - // 直接转为 QuestFile 列表 - List result = - new ArrayList<>(); - int num = 1; - for (Section sec : sections) { - QuestFile qf = new QuestFile(); - qf.displayName = - extractDisplayName( - sec.title); - qf.fileName = category; - qf.conditions = - new ArrayList<>(sec.lines); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - result.add(qf); - num++; - } - return result; - } - - /** - * 无分隔符:整文件 = 一个阶段 - */ - private List parseFlat( - List lines, - String category) { - List list = - new ArrayList<>(); - QuestFile qf = makeDefaultStage( - category); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - qf.conditions.add(cond); - } - } - if (!qf.conditions.isEmpty()) { - list.add(qf); - } - return list; - } -// ========== Vault 经济奖励发放 ========== - - /** - * 发放阶段奖励 - * 含经济内容的走 Vault API或债券 - */ - public void claimRewards( - Player player, - QuestFile qf) { - if (qf == null - || qf.rewards == null - || qf.rewards.isEmpty()) { - player.sendMessage("§c无可领取的奖励"); - return; - } - - Main main = (Main) plugin; - // ★ 读取配置:economy = Vault,bonds = 债券(默认) - boolean useEconomy = "economy".equalsIgnoreCase( - main.getConfigMgr().rewardChannel); - - // Vault 经济引用(仅 useEconomy 时使用) - net.milkbowl.vault.economy.Economy econ = null; - if (useEconomy) { - econ = main.getEconomy(); - } - - int sentCount = 0; - - for (String reward : qf.rewards) { - String rt = reward.trim(); - if (rt.isEmpty()) continue; - - // 检查可选奖励标记 - if (hasOptionalMarker(rt)) { - if (!areAllConditionsMet(player.getName(), qf)) { - player.sendMessage( - "§7[可选奖励] 需完成所有可选条件: " + rt); - continue; - } - } - - // 按 + 分割子项 - String[] parts = rt.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 尝试识别经济金额 - double amt = extractEconomyAmount(part); - if (amt > 0) { - // ★ 根据配置分发 - if (useEconomy && econ != null) { - // —— Vault 经济 —— - double before = - econ.getBalance(player); - econ.depositPlayer(player, amt); - double after = - econ.getBalance(player); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(余额: §e" - + String.format("%.2f", before) - + " §7→ §a" - + String.format("%.2f", after) - + "§7)"); - if (plugin instanceof Main) { - ((Main) plugin).getCommission() - .payCommission( - player.getName(), amt); - } - } else { - // —— 债券(默认) —— - int bondAmt = - (int) Math.round(amt); - BondManager bonds = main.getBonds(); - if (bonds != null) { - int before = bonds.getBonds( - player.getName()); - bonds.addBonds( - player.getName(), - bondAmt, - "quest_reward", - qf.displayName, - "任务系统", - "任务奖励:" + qf.displayName); - int after = bonds.getBonds( - player.getName()); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(债券: §e" - + before - + " §7→ §a" - + after + "§7)"); - } else { - player.sendMessage( - "§c债券系统不可用"); - } - } - sentCount++; - continue; - } - - // 尝试发物品 - java.util.List items = - parseRewardItems(part); - if (!items.isEmpty()) { - for (ItemStack is : items) { - player.getInventory().addItem(is); - } - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - continue; - } - - // 非经济非物品 → 仅消息 - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - } - } - - plugin.getLogger().info( - "[QuestTracker] " + player.getName() - + " 预取了 \"" + qf.displayName - + "\" 共 " + sentCount + "项奖励"); - } - - /** - * 检查奖励文本是否含可选标记 - */ - private boolean hasOptionalMarker( - String text) { - return text.contains("(可选)") - || text.contains("(进阶)") - || text.contains("【可选】") - || text.contains("【进阶】"); - } - - /** - * 检查阶段所有条件(含可选)是否全部满足 - * 用于决定可选奖励是否发放 - */ - private boolean areAllConditionsMet( - String player, QuestFile qf) { - if (qf.conditions == null - || qf.conditions.isEmpty()) - return true; - for (String cond : qf.conditions) { - if (!isConditionMet(player, cond)) - return false; - } - return true; - } - - /** - * 从奖励文本中提取经济金额 - * 支持: "500游戏币" "500" "金币500" - * "$500" "1000-5000"(随机) "1000~5000" - */ - private double extractEconomyAmount( - String text) { - String t = text.replaceAll(" ", ""); - - // 必须包含经济关键词才算经济奖励 - boolean hasEcon = - t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额") - || t.contains("发钱") - || t.contains("债券"); // ★ 新增 - - - if (!hasEcon) return 0; - - // 范围: "1000-5000" 或 "1000~5000" - Matcher rm = Pattern - .compile("(\\d+)[-~](\\d+)") - .matcher(t); - if (rm.find()) { - double min = Double.parseDouble( - rm.group(1)); - double max = Double.parseDouble( - rm.group(2)); - return min + Math.random() - * (max - min); - } - - // 纯数字(含 $ 前缀) - Matcher nm = Pattern - .compile("\\$?(\\d+(\\.\\d+)?)") - .matcher(t); - if (nm.find()) { - return Double.parseDouble( - nm.group(1)); - } - - return 0; - } - /** - * 解析奖励物品文本 - * "皮革套装*1" → 4件皮革装备 - * "木剑*1" → 1把木剑 - * "钻石*64" → 64颗钻石 - */ - private List parseRewardItems( - String text) { - List result = - new ArrayList<>(); - - // 去掉条件前缀 - // 如 "完成注册登录:皮革套装*1" - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - - // 按 + 分割多个物品 - String[] parts = - text.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 跳过经济文本 - if (extractEconomyAmount(part) > 0) - continue; - - int amount = 1; - String itemName = part; - - // 解析 *N 数量 - if (part.contains("*")) { - String[] sp = - part.split("\\*"); - itemName = sp[0].trim(); - try { - amount = Integer.parseInt( - sp[1].trim()); - } catch (Exception e) { - amount = 1; - } - } - - List mats = - matchMaterials(itemName); - for (Material m : mats) { - int give = m.name() - .contains("HELMET") - || m.name() - .contains("CHESTPLATE") - || m.name() - .contains("LEGGINGS") - || m.name() - .contains("BOOTS") - ? 1 : amount; - result.add( - new ItemStack(m, give)); - } - } - return result; - } - - /** - * 中文物品名 → Material 列表 - * 套装返回多个部件 - */ - private List matchMaterials( - String name) { - List list = - new ArrayList<>(); - String n = name.toLowerCase().trim(); - - // 套装 - if (n.contains("皮革套装") - || n.contains("皮套装")) { - list.add(Material.LEATHER_HELMET); - list.add(Material.LEATHER_CHESTPLATE); - list.add(Material.LEATHER_LEGGINGS); - list.add(Material.LEATHER_BOOTS); - return list; - } - if (n.contains("铁套装") - || n.contains("铁甲")) { - list.add(Material.IRON_HELMET); - list.add(Material.IRON_CHESTPLATE); - list.add(Material.IRON_LEGGINGS); - list.add(Material.IRON_BOOTS); - return list; - } - if (n.contains("钻石套装") - || n.contains("钻甲")) { - list.add(Material.DIAMOND_HELMET); - list.add(Material.DIAMOND_CHESTPLATE); - list.add(Material.DIAMOND_LEGGINGS); - list.add(Material.DIAMOND_BOOTS); - return list; - } - // 木制工具套装 - if (n.contains("木制工具") - || n.contains("木工具")) { - list.add(Material.WOODEN_SWORD); - list.add(Material.WOODEN_PICKAXE); - list.add(Material.WOODEN_AXE); - list.add(Material.WOODEN_SHOVEL); - return list; - } - if (n.contains("石制工具") - || n.contains("石工具")) { - list.add(Material.STONE_SWORD); - list.add(Material.STONE_PICKAXE); - list.add(Material.STONE_AXE); - list.add(Material.STONE_SHOVEL); - return list; - } - if (n.contains("铁工具")) { - list.add(Material.IRON_SWORD); - list.add(Material.IRON_PICKAXE); - list.add(Material.IRON_AXE); - list.add(Material.IRON_SHOVEL); - return list; - } - - // 单个物品 - if (n.contains("木剑")) - list.add(Material.WOODEN_SWORD); - else if (n.contains("木镐")) - list.add(Material.WOODEN_PICKAXE); - else if (n.contains("铁剑")) - list.add(Material.IRON_SWORD); - else if (n.contains("铁镐")) - list.add(Material.IRON_PICKAXE); - else if (n.contains("钻石剑")) - list.add(Material.DIAMOND_SWORD); - else if (n.contains("钻石镐")) - list.add(Material.DIAMOND_PICKAXE); - else if (n.contains("钻石")) - list.add(Material.DIAMOND); - else if (n.contains("铁锭")) - list.add(Material.IRON_INGOT); - else if (n.contains("金锭") - || n.contains("金块")) - list.add(Material.GOLD_INGOT); - else if (n.contains("煤炭")) - list.add(Material.COAL); - else if (n.contains("木棍")) - list.add(Material.STICK); - else if (n.contains("弓")) - list.add(Material.BOW); - else if (n.contains("箭")) - list.add(Material.ARROW); - else if (n.contains("床")) - list.add(Material.RED_BED); - else { - // 尝试直接匹配 Material 名 - try { - list.add(Material.valueOf( - n.toUpperCase() - .replaceAll("[^A-Z_]", - ""))); - } catch (Exception e) { - // 未知物品 - } - } - return list; - } - - private QuestFile getOrCreateStage( - TreeMap map, - int key, - String category) { - if (!map.containsKey(key)) { - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + key; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - } - return map.get(key); - } - - /** - * 单个文件的区块 → 阶段 - * defaultStageNum: 文件无阶段标题时的默认编号 - */ - private void buildSingleFileStages( - List
sections, - String category, - TreeMap stageMap, - int defaultStageNum) { - Integer curKey = null; - - for (Section sec : sections) { - Integer stageNum = - extractStageNumber(sec.title); - - if (stageNum != null) { - curKey = stageNum; - } else if (curKey == null) { - curKey = defaultStageNum; - } - - QuestFile qf = getOrCreateStage( - stageMap, curKey, category); - - String displayName = - extractDisplayName(sec.title); - - // 设置显示名 - if (!displayName.isEmpty() - && qf.displayName - .startsWith("阶段 ")) { - qf.displayName = displayName; - } - - // 路由 - if (stageNum != null - && sec.lines.isEmpty()) { - if (!displayName.isEmpty()) { - qf.displayName = displayName; - } - } - else if (isRewardTitle(sec.title)) { - // 奖励 → 只进 rewards - qf.rewards.addAll(sec.lines); - } - else if (isDetailsTitle(sec.title)) { - // 详情 → 进 details + conditions - qf.details.addAll(sec.lines); - for (String line : sec.lines) { - if (!qf.conditions - .contains(line)) { - qf.conditions.add(line); - } - } - } - else if (isConditionsTitle( - sec.title)) { - // 条件 → 只进 conditions - qf.conditions.addAll(sec.lines); - } - else { - // 其他 → 条件 - qf.conditions.addAll(sec.lines); - } - } - } - - /** - * "主线任务-斗志昂扬" → "斗志昂扬" - * "支线-钓鱼" → "钓鱼" - * "阶段 1" → "阶段 1" - * "第一阶段" → "第一阶段" - */ - private String extractDisplayName( - String title) { - if (title == null) return ""; - // 按 - 或 — 或 – 分割 - String[] parts = title.split( - "\\s*[-—–]\\s*", 2); - if (parts.length >= 2) { - String right = parts[1].trim(); - if (!right.isEmpty()) { - return right; - } - } - return title; - } - private DatabaseManager getDb() { - return ((Main) plugin).getDb(); - } - - private boolean isDetailsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("任务详情") - || t.contains("详情") - || t.contains("任务说明") - || t.contains("任务目标") - || t.contains("description") - || t.contains("任务要求"); - } - - private boolean isConditionsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("解锁条件") - || t.contains("条件") - || t.contains("前置条件") - || t.contains("前置要求") - || t.contains("解锁") - || t.contains("condition"); - } - - - private void ensureStage( - TreeMap map, - String category, - QuestFile current, - int counter) { - if (current != null) return; - // 用大数字确保排在有编号阶段后面 - int key = 1000 + counter; - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + (counter + 1); - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - // 更新调用方的引用(通过返回值) - // 这里用 map 操作,current 无法直接更新 - // 所以改用返回值方式 - } - /** - * 自动将条件中的奖励内容移到 rewards - */ - private void separateRewards(QuestFile qf) { - List remaining = - new ArrayList<>(); - for (String line : qf.conditions) { - if (isRewardContent(line)) { - if (!qf.rewards.contains(line)) { - qf.rewards.add(line); - } - } else { - remaining.add(line); - } - } - qf.conditions = remaining; - } - private boolean checkStageDependency( - String player, - String condition) { - Matcher m = Pattern.compile( - "第([一二三四五六七八九十\\d]+)" - + "阶段") - .matcher(condition); - - while (m.find()) { - String numStr = m.group(1); - int stageNum = - parseChineseNumber(numStr); - if (stageNum == 0) { - try { - stageNum = - Integer.parseInt(numStr); - } catch (Exception e) { - return false; - } - } - - boolean stageDone = false; - for (List quests - : questCache.values()) { - for (QuestFile qf : quests) { - Integer qfNum = - extractStageNumber( - qf.displayName); - if (qfNum != null - && qfNum == stageNum) { - if (isStageComplete(player, - qf.conditions)) { - stageDone = true; - } - break; - } - } - if (stageDone) break; - } - - if (!stageDone) return false; - } - return true; - } - - /** - * 判断一行内容是否为奖励 - */ - private boolean isRewardContent( - String line) { - String l = line.toLowerCase(); - // 含货币关键词 - if (l.contains("游戏币") - || l.contains("金币") - || l.contains("积分") - || l.contains("钻石") - || l.contains("发钱") - || l.contains("发放") - || l.contains("经验") - || l.contains("等级") - || l.contains("经济") - || l.contains("余额") - || l.contains("块钱")) - return true; - // 纯数字+单位(如 "500游戏币") - if (l.matches( - ".*\\d+\\s*(游戏币|金币|元|积分|" - + "钻石|块钱|经验|级|经济|余额).*")) - return true; - // $前缀(如 "$500") - if (l.matches(".*\\$\\d+.*")) - return true; - // 范围格式(如 "1000-5000" 或 "1000~5000") - if (l.matches(".*\\d+[-~]\\d+.*") - && !l.contains("阶段") - && !l.contains("stage")) - return true; - return false; - } - - // ========== 标题分类 ========== - - private boolean isStageTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("阶段") - || t.contains("stage") - || t.contains("第一") - || t.contains("第二") - || t.contains("第三") - || t.contains("第四") - || t.contains("第五"); - } - - private boolean isRewardTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("奖励") - || t.contains("奖品") - || t.contains("报酬") - || t.contains("reward") - || t.contains("奖赏") - || t.contains("礼包内容") - || t.contains("礼包") - || t.contains("物品奖励"); - } - - - private QuestFile makeDefaultStage( - String category) { - QuestFile qf = new QuestFile(); - qf.displayName = "第一阶段"; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - return qf; - } - - private static class Section { - final String title; - final List lines; - Section(String title, - List lines) { - this.title = title; - this.lines = lines; - } - } - - // ========== 公开查询 ========== - - public List getQuests( - String category) { - List list = - questCache.get(category); - return list != null ? list - : new ArrayList<>(); - } - - public int getCompletedStageCount( - String player, - List quests) { - int count = 0; - if (quests == null) return 0; - for (QuestFile qf : quests) { - if (isStageComplete(player, - qf.conditions)) { - count++; - } - } - return count; - } - - public QuestFile getCurrentStage( - String player, - List quests) { - if (quests == null) return null; - for (QuestFile qf : quests) { - if (!isStageComplete( - player, qf.conditions)) { - return qf; - } - } - return null; - } - - public int getStageNumber( - List quests, - QuestFile qf) { - if (quests == null || qf == null) - return 0; - return quests.indexOf(qf) + 1; - } - - public int getStageNumber( - QuestFile qf) { - if (qf == null) return 0; - List quests = - getQuests(qf.fileName); - return getStageNumber(quests, qf); - } - - public boolean isStageCompleted( - String player, - QuestFile qf) { - if (qf == null) return false; - return isStageComplete( - player, qf.conditions); - } - - public List getUnmetConditions( - String player, - List conditions) { - List unmet = - new ArrayList<>(); - if (conditions == null) return unmet; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) { - unmet.add( - getProgress( - player, cond)); - } - } - return unmet; - } - public String getProgress( - String player, String rawCond) { - if (player == null || rawCond == null) - return "§7未知"; - String key = stripOptional(rawCond); - - if (key.contains("打卡") - && key.contains("群系")) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return "§7打卡 " + biome - + "群系: §e" - + catCount + "/1"; - } - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - if (key.contains("完成") - && key.contains("阶段")) { - boolean done = checkStageDependency( - player, key); - return done - ? "§a✓ " + key - : "§c✗ " + key; - } - - if ("完成注册登录".equals(key)) { - boolean done = getDb() != null - && getDb().userExists( - player); - return done - ? "§a✓ 完成注册登录" - : "§c✗ 完成注册登录"; - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - boolean done = val != null - && !val.toString().isEmpty(); - return done - ? "§a✓ 绑定Email" - : "§c✗ 绑定Email"; - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + needed + "小时"; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - int cur = getCounter( - player, "CHAT"); - return "§7聊天: §e" - + cur + "/1"; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return "§7方块操作: §e" - + total + "/" + needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return "§7连续登录: §e" - + streak + "/" + needed + "天"; - } - - if (key.startsWith("MINE:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "MINE:" + parts[1]); - return "§7挖 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "VILLAGER_TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - - } - - // ===== 击杀泛解析(支持中文列表) ===== -// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等 - if (key.contains("击杀") && key.contains("只")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - - String mobPart = key; - java.util.regex.Matcher killM = - java.util.regex.Pattern - .compile("击杀\\d+只(.+)") - .matcher(key); - if (killM.find()) { - mobPart = killM.group(1).trim(); - } - - String[] mobNames = mobPart - .split("[、,,,]+"); - int totalKills = 0; - StringBuilder names = new StringBuilder(); - for (String name : mobNames) { - String trimmed = name.trim(); - if (trimmed.isEmpty()) continue; - String entityType = - chineseMobToEntity(trimmed); - if (!entityType.isEmpty()) { - totalKills += getCounter( - player, - "KILL:" + entityType); - if (names.length() > 0) - names.append("/"); - names.append(trimmed); - } - } - return "§7击杀" + names + ": §e" - + totalKills + "/" + needed; - } - -// 单一KILL:xxx:N 格式(原有逻辑保留) - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - int needed = parseInt(p[2]); - int cur = getCounter(player, - "KILL:" + p[1]); - return "§7击杀" + p[1] + ": §e" - + cur + "/" + needed; - } - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "KILL_PLAYER"); - return "§7击杀玩家: §e" - + cur + "/" + needed; - } - - if (key.startsWith("CRAFT")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "CRAFT"); - return "§7合成: §e" - + cur + "/" + needed; - } - - if (key.startsWith("FISH")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "FISH"); - return "§7钓鱼: §e" - + cur + "/" + needed; - } - - if (key.startsWith("PLAYTIME")) { - String[] parts = key.split(":"); - long needed = parts.length >= 2 - ? Long.parseLong(parts[1]) - : 0; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + formatMs(needed); - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7上报村庄: §e" - + cur + "/" + needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7探索村庄: §e" - + cur + "/1"; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return "§7近7天登录: §e" - + streak + "/" - + needed + "天"; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7新手任务: §e" - + done + "/" - + quests.size(); - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7主线任务: §e" - + done + "/" - + quests.size(); - } - // ===== 泛解析:击杀类进度 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < mobTypes.size(); i++) { - total += getCounter(player, - "KILL:" + mobTypes.get(i)); - if (i > 0) names.append("/"); - names.append(reverseMobName( - mobTypes.get(i))); - } - return "§7击杀" + names + ": §e" - + total + "/" + needed; - } - } - - // ===== 泛解析:村民交易进度 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - // ===== 泛解析:采集/挖矿进度 ===== - if (key.contains("采集") - || key.contains("挖")) { - int needed = - parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < matTypes.size(); i++) { - total += getCounter( - player, - "MINE:" + matTypes - .get(i)); - if (i > 0) - names.append("/"); - names.append( - translateMaterialName( - matTypes - .get(i))); - } - return "§7采集" + names - + ": §e" + total - + "/" + needed; - } - } - - // ===== 泛解析:合成进度 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "CRAFT"); - return "§7合成: §e" + cur + "/" + needed; - } - - // ===== 泛解析:钓鱼进度 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "FISH"); - return "§7钓鱼: §e" + cur + "/" + needed; - } - - return "§7" + key; - } - private String translateMaterialName( - String name) { - switch (name) { - case "COAL_ORE": - return "煤矿石"; - case "DEEPSLATE_COAL_ORE": - return "深层煤矿石"; - case "IRON_ORE": - return "铁矿石"; - case "DEEPSLATE_IRON_ORE": - return "深层铁矿石"; - case "COPPER_ORE": - return "铜矿石"; - case "DEEPSLATE_COPPER_ORE": - return "深层铜矿石"; - case "GOLD_ORE": - return "金矿石"; - case "DEEPSLATE_GOLD_ORE": - return "深层金矿石"; - case "DIAMOND_ORE": - return "钻石矿石"; - case "DEEPSLATE_DIAMOND_ORE": - return "深层钻石矿石"; - case "EMERALD_ORE": - return "绿宝石矿石"; - case "DEEPSLATE_EMERALD_ORE": - return "深层绿宝石矿石"; - case "LAPIS_ORE": - return "青金石矿石"; - case "DEEPSLATE_LAPIS_ORE": - return "深层青金石矿石"; - case "REDSTONE_ORE": - return "红石矿石"; - case "DEEPSLATE_REDSTONE_ORE": - return "深层红石矿石"; - case "NETHER_GOLD_ORE": - return "下界金矿石"; - case "NETHER_QUARTZ_ORE": - return "下界石英矿石"; - case "ANCIENT_DEBRIS": - return "远古残骸"; - case "COAL": - return "煤炭"; - case "RAW_IRON": - return "粗铁"; - case "RAW_GOLD": - return "粗金"; - case "RAW_COPPER": - return "粗铜"; - case "DIAMOND": - return "钻石"; - case "EMERALD": - return "绿宝石"; - case "LAPIS_LAZULI": - return "青金石"; - case "REDSTONE": - return "红石"; - case "QUARTZ": - return "石英"; - default: - return name; - } - } - - /** - * 中文物种名 → Bukkit EntityType 名 - * 支持击杀泛解析的模糊匹配 - */ - private String chineseMobToEntity(String cn) { - String n = cn.toLowerCase().trim(); - - // 直接别名表 - Map mobMap = new HashMap<>(); - mobMap.put("骷髅", "SKELETON"); - mobMap.put("僵尸", "ZOMBIE"); - mobMap.put("苦力怕", "CREEPER"); - mobMap.put("蜘蛛", "SPIDER"); - mobMap.put("洞穴蜘蛛", "CAVE_SPIDER"); - mobMap.put("小白", "SKELETON"); - mobMap.put("末影人", "ENDERMAN"); - mobMap.put("enderman", "ENDERMAN"); - mobMap.put("猪灵", "PIGLIN"); - mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE"); - mobMap.put("恶魂", "GHAST"); - mobMap.put("凋灵骷髅", "WITHER_SKELETON"); - mobMap.put("烈焰人", "BLAZE"); - mobMap.put("史莱姆", "SLIME"); - mobMap.put("岩浆怪", "MAGMA_CUBE"); - mobMap.put("女巫", "WITCH"); - mobMap.put("蠹虫", "SILVERFISH"); - mobMap.put("潜影贝", "SHULKER"); - mobMap.put("守卫者", "GUARDIAN"); - mobMap.put("远古守卫者", "ELDER_GUARDIAN"); - mobMap.put("溺尸", "DROWNED"); - mobMap.put("僵尸村民", "ZOMBIE_VILLAGER"); - mobMap.put("尸壳", "HUSK"); - mobMap.put("流浪者", "STRAY"); - mobMap.put("幻翼", "PHANTOM"); - mobMap.put("掠夺者", "PILLAGER"); - mobMap.put("卫道士", "VINDICATOR"); - mobMap.put("唤魔者", "EVOKER"); - mobMap.put("恼鬼", "VEX"); - mobMap.put("劫掠兽", "RAVAGER"); - mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY"); - mobMap.put("凋灵", "WITHER"); - mobMap.put("末影龙", "ENDER_DRAGON"); - - // 精确匹配 - if (mobMap.containsKey(n)) { - return mobMap.get(n); - } - - // 模糊匹配(包含关系) - for (Map.Entry en - : mobMap.entrySet()) { - if (n.contains(en.getKey()) - || en.getKey().contains(n)) { - return en.getValue(); - } - } - - // 尝试直接用英文名 - try { - return org.bukkit.entity.EntityType - .valueOf(n.toUpperCase() - .replace(" ", "_")) - .name(); - } catch (Exception e) { - return ""; - } - } - - public List checkInBiome( - String player, - String rawBiomeKey, - String worldName, - double x, double y, double z) { - List matched = - new ArrayList<>(); - - // 32格去重 - String posKey = - player + "_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - String lastPos = - lastCheckInPos.get(posKey); - if (curPos.equals(lastPos)) { - plugin.getLogger().info( - "[QuestTracker] 打卡忽略: " - + player - + " 32格内重复"); - return matched; - } - lastCheckInPos.put(posKey, curPos); - - String category = - mapBiomeCategory( - rawBiomeKey, worldName); - - // ===== 结构检测优先于Y轴"地下"判断 ===== - // 要塞等结构可生成在任何主世界群系下,不应固定为"地下" - // 使用 PaperMC API 检测玩家所在位置的结构 - String structureCat = null; - try { - structureCat = detectStructureAt( - worldName, x, y, z); - } catch (Exception ignored) {} - - if (structureCat != null - && !structureCat.isEmpty() - && !"地下".equals(structureCat)) { - // 结构检测成功且非"地下"类,结构分类优先 - category = structureCat; - plugin.getLogger().info( - "[QuestTracker] 结构覆盖: " - + category - + " (结构: " - + structureCat + ")"); - } else { - // 未检测到有效结构,使用Y轴检测判定地下 - // 地下检测:Y低于表面则归类为地下 - try { - org.bukkit.World uw = - Bukkit.getWorld(worldName); - if (uw != null) { - int surfaceY = uw - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - } - } catch (Exception ignored) {} - if (structureCat != null - && !structureCat.isEmpty()) { - plugin.getLogger().info( - "[QuestTracker] 结构" - + structureCat - + " 使用Y轴判定"); - } else { - plugin.getLogger().info( - "[QuestTracker] 未检测到结构: world=" - + worldName + ", pos=" - + String.format( - "%.0f,%.0f,%.0f", - x, y, z)); - } - } - - - // 记录总打卡 - int allCur = getCounter( - player, "biome_ALL"); - setCounter(player, "biome_ALL", - allCur + 1); - - // 记录分类打卡 - if (!category.isEmpty() - && !"主世界" - .equals(category)) { - String catKey = - "biomecat_" + category; - int catCur = getCounter( - player, catKey); - setCounter(player, catKey, - catCur + 1); - } - - // 记录具体群系 - String bioKey = - "biome_" + rawBiomeKey; - int bioCur = getCounter( - player, bioKey); - setCounter(player, bioKey, - bioCur + 1); - - // 检查所有任务条件是否匹配 - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - for (String cat : categories) { - List quests = - getQuests(cat); - for (QuestFile qf : quests) { - if (isStageCompleted( - player, qf)) - continue; - for (String cond - : qf.conditions) { - if (isOptional(cond)) - continue; - String condKey = - stripOptional(cond); - if (!condKey - .contains("打卡")) - continue; - // 支持群系和结构两种打卡条件 - boolean isBiomeCond = - condKey.contains("群系") - || condKey - .contains("风景点"); - boolean isStructureCond = - condKey.contains("结构") - || condKey - .contains("村庄") - || condKey - .contains("神殿") - || condKey - .contains("神庙") - || condKey - .contains("小屋") - || condKey - .contains("雪屋") - || condKey - .contains("府邸") - || condKey - .contains("前哨站") - || condKey - .contains("矿井") - || condKey - .contains("要塞") - || condKey - .contains("城市") - || condKey - .contains("废墟") - || condKey - .contains("密室") - || condKey - .contains("沉船") - || condKey - .contains("宝箱") - || condKey - .contains("传送门") - || condKey - .contains("化石") - || condKey - .contains("堡垒"); - if (!isBiomeCond - && !isStructureCond) - continue; - String target = - extractBiome(condKey); - plugin.getLogger().info( - "[QuestTracker] 条件匹配: condKey=\"" - + condKey + "\", target=\"" - + target + "\", category=\"" - + category + "\", isBiomeCond=" - + isBiomeCond + ", isStructureCond=" - + isStructureCond); - boolean hit = false; - if ("ALL".equals(target) - || "风景点" - .equals(target)) { - hit = true; - } else if (!target.isEmpty() - && target.equals( - category)) { - hit = true; - } - if (hit && !matched - .contains(cond)) { - matched.add(cond); - } - } - } - } - - return matched; - } - - /* - * 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录 - */ - public String checkInAtPosition( - String player, String worldName, - double x, double y, double z) { - - List history = - loadCheckInPositions(player); - for (double[] pos : history) { - double dist = Math.sqrt( - Math.pow(x - pos[0], 2) - + Math.pow(y - pos[1], 2) - + Math.pow(z - pos[2], 2)); - if (dist <= 32) { - return "§c距离上次打卡点太近(" - + String.format( - "%.0f", dist) - + "格)"; - } - } - - // ===== 获取群系和分类 ===== - String biome = "minecraft:plains"; - String category = "平原"; - String structureCat = null; - try { - org.bukkit.World w = - Bukkit.getWorld(worldName); - if (w != null) { - org.bukkit.Location loc = - new org.bukkit.Location( - w, - (int) Math.floor(x), - (int) Math.floor(y), - (int) Math.floor(z)); - biome = loc.getBlock() - .getBiome().getKey() - .toString(); - category = mapBiomeCategory( - biome, worldName); - - // ===== 结构检测优先于Y轴"地下"判断 ===== - structureCat = - detectStructureAt( - worldName, x, y, z); - plugin.getLogger().info( - "[QuestTracker] checkInAtPosition 结构检测: " - + structureCat + " at " - + String.format("%.0f,%.0f,%.0f", x, y, z)); - - if (structureCat != null - && !structureCat.isEmpty() - && !"地下".equals(structureCat)) { - // 结构检测成功且非"地下"类,结构分类优先 - category = structureCat; - } else { - // 未检测到有效结构,使用Y轴检测判定地下 - int surfaceY = w - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - } - - if ("下界".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.NETHER) { - category = "下界"; - } else if ("末地".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.THE_END) { - category = "末地"; - } - } - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 群系读取失败: " - + ex.getMessage()); - } - - plugin.getLogger().info( - "[QuestTracker] 打卡: " - + player - + " world=" + worldName - + " biome=" + biome - + " cat=" + category - + " pos=" - + String.format( - "%.0f,%.0f,%.0f", - x, y, z)); - - saveCheckInPosition( - player, x, y, z); - recordCheckIn( - player, biome, category, - worldName, x, y, z); - List matched = - checkInBiome( - player, biome, - worldName, x, y, z); - - StringBuilder sb = new StringBuilder(); - sb.append("§a打卡成功!群系: §e") - .append(category); - if (structureCat != null && !structureCat.isEmpty()) { - sb.append("\n§a结构: §e").append(structureCat); - } - if (!matched.isEmpty()) { - sb.append("\n§a完成任务条件:"); - for (String m : matched) { - String qn = - findQuestName(m); - sb.append("\n§7• "); - if (qn != null) { - sb.append("§e[") - .append(qn) - .append("§7] §f"); - } - sb.append(m); - } - } - return sb.toString(); - } - - - /** - * 根据条件文本查找所属任务名 - */ - private String findQuestName( - String condition) { - for (String cat : Arrays.asList( - "新人任务", - "主线任务", - "支线任务")) { - for (QuestFile qf : - getQuests(cat)) { - if (qf.conditions.contains( - condition)) { - return qf.displayName; - } - } - } - return null; - } - - - /** - * 加载玩家所有历史打卡坐标 - * 格式: x1,y1,z1;x2,y2,z2;... - */ - private List loadCheckInPositions( - String player) { - List list = new ArrayList<>(); - Object val = storage.getCondition( - player, "checkin_all_pos"); - if (val == null) return list; - String data = val.toString(); - if (data.isEmpty()) return list; - String[] records = data.split(";"); - for (String r : records) { - String[] parts = r.split(","); - if (parts.length >= 3) { - try { - list.add(new double[]{ - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2])}); - } catch (NumberFormatException e) { - // 跳过损坏数据 - } - } - } - return list; - } - - /** - * 保存打卡坐标(追加到已有记录) - */ - private void saveCheckInPosition( - String player, double x, - double y, double z) { - List list = - loadCheckInPositions(player); - list.add(new double[]{x, y, z}); - StringBuilder sb = new StringBuilder(); - for (double[] pos : list) { - if (sb.length() > 0) sb.append(";"); - sb.append(pos[0]).append(",") - .append(pos[1]).append(",") - .append(pos[2]); - } - storage.setConditionRaw( - player, - "checkin_all_pos", - sb.toString()); - } - // ========== 中文生物名 → EntityType 映射 ========== - private static final Map - MOB_NAME_MAP = new HashMap<>(); - static { - MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER"); - MOB_NAME_MAP.put("骷髅", "SKELETON"); - MOB_NAME_MAP.put("僵尸", "ZOMBIE"); - MOB_NAME_MAP.put("苦力怕", "CREEPER"); - MOB_NAME_MAP.put("蜘蛛", "SPIDER"); - MOB_NAME_MAP.put("末影人", "ENDERMAN"); - MOB_NAME_MAP.put("女巫", "WITCH"); - MOB_NAME_MAP.put("烈焰人", "BLAZE"); - MOB_NAME_MAP.put("僵尸猪灵", - "ZOMBIFIED_PIGLIN"); - MOB_NAME_MAP.put("猪灵", "PIGLIN"); - MOB_NAME_MAP.put("恶魂", "GHAST"); - MOB_NAME_MAP.put("凋灵骷髅", - "WITHER_SKELETON"); - MOB_NAME_MAP.put("史莱姆", "SLIME"); - MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE"); - MOB_NAME_MAP.put("溺尸", "DROWNED"); - MOB_NAME_MAP.put("尸壳", "HUSK"); - MOB_NAME_MAP.put("流髑", "STRAY"); - MOB_NAME_MAP.put("幻翼", "PHANTOM"); - MOB_NAME_MAP.put("守卫者", "GUARDIAN"); - MOB_NAME_MAP.put("远古守卫者", - "ELDER_GUARDIAN"); - MOB_NAME_MAP.put("掠夺者", "PILLAGER"); - MOB_NAME_MAP.put("卫道士", "VINDICATOR"); - MOB_NAME_MAP.put("唤魔者", "EVOKER"); - MOB_NAME_MAP.put("恼鬼", "VEX"); - MOB_NAME_MAP.put("劫掠兽", "RAVAGER"); - MOB_NAME_MAP.put("潜影贝", "SHULKER"); - MOB_NAME_MAP.put("末影螨", "ENDERMITE"); - MOB_NAME_MAP.put("凋灵", "WITHER"); - MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON"); - MOB_NAME_MAP.put("监守者", "WARDEN"); - MOB_NAME_MAP.put("嗅探兽", "SNIFFER"); - MOB_NAME_MAP.put("僵尸村民", - "ZOMBIE_VILLAGER"); - } - - /** - * 从条件文本中提取所有匹配的生物类型 - * 按名称长度降序匹配,避免短名误匹配 - */ - private List extractMobTypes( - String text) { - List types = new ArrayList<>(); - List> sorted = - new ArrayList<>( - MOB_NAME_MAP.entrySet()); - sorted.sort((a, b) -> - b.getKey().length() - - a.getKey().length()); - String remaining = text; - for (Map.Entry entry - : sorted) { - if (remaining.contains( - entry.getKey())) { - if (!types.contains( - entry.getValue())) { - types.add(entry.getValue()); - } - remaining = remaining.replace( - entry.getKey(), ""); - } - } - return types; - } - - /** - * EntityType → 中文名 - */ - private String reverseMobName( - String entityType) { - for (Map.Entry entry - : MOB_NAME_MAP.entrySet()) { - if (entry.getValue() - .equals(entityType)) { - return entry.getKey(); - } - } - return entityType; - } - - /** - * 从条件文本中提取矿物名 - * "采集任意矿物(煤炭除外)1组" - * → [COAL_ORE, IRON_ORE, GOLD_ORE, ...] - */ - private List extractMaterialTypes( - String text) { - List types = new ArrayList<>(); - // 排除列表 - List excludes = - new ArrayList<>(); - if (text.contains("煤炭除外")) { - excludes.add("COAL"); - excludes.add("COAL_ORE"); - excludes.add("DEEPSLATE_COAL_ORE"); - } - // 通配符:"矿物" "矿石" - if (text.contains("矿物") - || text.contains("矿石")) { - String[] ores = { - "COAL_ORE", - "DEEPSLATE_COAL_ORE", - "IRON_ORE", - "DEEPSLATE_IRON_ORE", - "COPPER_ORE", - "DEEPSLATE_COPPER_ORE", - "GOLD_ORE", - "DEEPSLATE_GOLD_ORE", - "DIAMOND_ORE", - "DEEPSLATE_DIAMOND_ORE", - "EMERALD_ORE", - "DEEPSLATE_EMERALD_ORE", - "LAPIS_ORE", - "DEEPSLATE_LAPIS_ORE", - "REDSTONE_ORE", - "DEEPSLATE_REDSTONE_ORE", - "NETHER_GOLD_ORE", - "NETHER_QUARTZ_ORE", - "ANCIENT_DEBRIS" - }; - for (String ore : ores) { - if (!excludes.contains(ore)) { - types.add(ore); - } - } - return types; - } - // 单个矿物名 - if (text.contains("煤炭")) - types.add("COAL"); - if (text.contains("铁矿") - || text.contains("铁锭")) - types.add("RAW_IRON"); - if (text.contains("金矿") - || text.contains("金锭")) - types.add("RAW_GOLD"); - if (text.contains("钻石")) - types.add("DIAMOND"); - if (text.contains("绿宝石")) - types.add("EMERALD"); - if (text.contains("青金石")) - types.add("LAPIS_LAZULI"); - if (text.contains("红石")) - types.add("REDSTONE"); - if (text.contains("铜")) - types.add("RAW_COPPER"); - if (text.contains("下界石英") - || text.contains("石英")) - types.add("QUARTZ"); - return types; - } - - - /** - * 从条件文本中提取目标群系分类 - * "打卡上报地狱任意群系" → "下界" - * "打卡任意海洋群系" → "海洋" - * "打卡任意群系" → "ALL" - * "打卡任意风景点" → "" - * "打卡村庄" → "村庄" - * "打卡沙漠神殿" → "沙漠" - */ - private String extractBiome(String text) { - if (text == null) return ""; - - // 优先匹配结构名称(更具体) - String structureResult = - mapStructureCategory(text); - if (!structureResult.isEmpty()) { - return structureResult; - } - - // 逐个匹配分类关键词 - for (Map.Entry en - : CATEGORY_KEYWORDS.entrySet()) { - if (text.contains(en.getKey())) { - return en.getValue(); - } - } - if (text.contains("风景点") - || text.contains("风景")) { - return "ALL"; - } - - // 有"群系"但无具体分类 → ALL - if (text.contains("群系")) { - return "ALL"; - } - return ""; - } - - - // ========== 标记条件 ========== - - public void markConditionComplete( - String player, - String conditionName) { - if (player == null - || conditionName == null) - return; - storage.setCondition( - player, conditionName); - } - - // ========== 事件回调 ========== - - public void onBlockMined( - String player, String material) { - String key = "MINE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - // 总计数 - setCounter(player, "MINE:ALL", - getCounter(player, "MINE:ALL") - + 1); - } - public boolean onReportVillage( - String player, - String worldName, - double x, double y, double z) { - String dbKey = "village_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - Object lastObj = - storage.getCondition( - player, dbKey); - String lastPos = lastObj != null - ? lastObj.toString() : ""; - if (curPos.equals(lastPos)) { - return false; - } - storage.setConditionRaw( - player, dbKey, curPos); - - String key = "REPORT_VILLAGE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - return true; - } - - public void onMobKill( - String player, String mobType) { - String key = "KILL:" + mobType; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayerKill( - String player) { - String key = "KILL_PLAYER"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onItemCrafted( - String player) { - String key = "CRAFT"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onFishCaught( - String player) { - String key = "FISH"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayTime( - String player, long ms) { - String key = "PLAYTIME"; - long cur = getCounterLong( - player, key); - setCounter(player, key, cur + ms); - } - - // ========== 内部:条件判定 ========== - - private boolean isStageComplete( - String player, - List conditions) { - if (conditions == null - || conditions.isEmpty()) - return true; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) - return false; - } - return true; - } - - public boolean isConditionMet( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return false; - - String key = stripOptional(rawCond); - - // 无条件类文本直接通过 - if (key.equals("无前级条件") - || key.equals("无条件") - || key.equals("无") - || key.equals("none") - || key.equals("无需条件") - || key.equals("无解锁条件")) { - return true; - } - - if (key.contains("打卡") - && (key.contains("群系") - || key.contains("风景点") - || key.contains("结构") - || key.contains("村庄") - || key.contains("神殿") - || key.contains("神庙") - || key.contains("小屋") - || key.contains("雪屋") - || key.contains("府邸") - || key.contains("前哨站") - || key.contains("矿井") - || key.contains("要塞") - || key.contains("城市") - || key.contains("废墟") - || key.contains("密室") - || key.contains("废墟") - || key.contains("沉船") - || key.contains("宝箱") - || key.contains("传送门") - || key.contains("化石") - || key.contains("堡垒"))) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return total >= 1; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return catCount >= 1; - } - return getCounter( - player, "biome_ALL") >= 1; - } - - if (key.contains("完成") - && key.contains("阶段")) { - return checkStageDependency( - player, key); - } - - if ("完成注册登录".equals(key)) { - return getDb() != null - && getDb().userExists( - player); - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - return val != null - && !val.toString().isEmpty(); - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return cur >= needed * 3600000L; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - return getCounter( - player, "CHAT") >= 1; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return total >= needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return streak >= needed; - } - - if (key.startsWith("MINE:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "MINE:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "KILL:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "VILLAGER_TRADE") - >= needed; - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter(player, - "KILL_PLAYER") >= n; - } - - if (key.startsWith("CRAFT")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "CRAFT") >= n; - } - - if (key.startsWith("FISH")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "FISH") >= n; - } - - if (key.startsWith("PLAYTIME")) { - String[] p = key.split(":"); - long n = p.length >= 2 - ? Long.parseLong(p[1]) : 0; - return getCounterLong(player, - "PLAYTIME") >= n; - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "REPORT_VILLAGE") - >= needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - return getCounter( - player, "REPORT_VILLAGE") - >= 1; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return streak >= needed; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - // ===== 泛解析:击杀类中文条件 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - for (String mobType : mobTypes) { - total += getCounter(player, - "KILL:" + mobType); - } - return total >= needed; - } - } - - // ===== 泛解析:村民交易条件 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "TRADE") - >= needed; - } - - // ===== 泛解析:采集/挖矿类条件 ===== - if (key.contains("采集") || key.contains("挖")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - for (String mat : matTypes) { - total += getCounter(player, - "MINE:" + mat); - } - return total >= needed; - } - } - - // ===== 泛解析:合成类条件 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "CRAFT") - >= needed; - } - - // ===== 泛解析:钓鱼类条件 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "FISH") - >= needed; - } - - return false; - } - - public void debugLogin(String player) { - try { - var user = getDb().getUser(player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "=== " + player - + " 数据库全部字段 ==="); - for (var entry - : user.entrySet()) { - plugin.getLogger().info( - "[QuestTracker] " - + entry.getKey() - + " = " - + entry.getValue() - + " (" - + (entry.getValue() - != null - ? entry.getValue() - .getClass() - .getSimpleName() - : "null") - + ")"); - } - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "getUser返回null: " - + player); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "debug异常: " - + e.getMessage()); - } - } - - // ========== 内部:计数器 ========== - - private int getCounter( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - return 0; - } - private int getIntField( - String player, String field) { - try { - Object val = getDb().getField( - player, field); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - if (val != null) { - return Integer.parseInt( - val.toString().trim()); - } - } catch (Exception e) { - // 忽略 - } - return 0; - } - - public void onVillagerTrade(String player) { - String key = "VILLAGER_TRADE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - private long getCounterLong( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).longValue(); - } - return 0; - } - - private void setCounter( - String player, String key, - int value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - private void setCounter( - String player, String key, - long value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - // ========== 工具 ========== - - private boolean toBool(Object val) { - if (val == null) return false; - if (val instanceof Boolean) - return (Boolean) val; - if (val instanceof Number) - return ((Number) val) - .intValue() != 0; - String s = String.valueOf(val); - return "1".equals(s) - || "true" - .equalsIgnoreCase(s); - } - - public boolean isOptional(String cond) { - if (cond == null) return false; - String c = cond; - return c.contains("(进阶)") - || c.contains("(可选)") - || c.contains(",可选)") - || c.contains(",可选)") - || c.contains("【可选】") - || c.contains("【进阶】"); - } - - - private String stripOptional(String cond) { - if (cond == null) return ""; - return cond.replace("(进阶)", "") - .replace("(可选)", "") - .replace(",可选)", "") - .replace(",可选)", "") - .replace("【可选】", "") - .replace("【进阶】", "") - .trim(); - } - - private int parseInt(String s) { - - try { - return Integer.parseInt(s.trim()); - } catch (Exception e) { - return 0; - } - } - private Object getDbField( - String player, String field) { - Main main = (Main) plugin; - return main.getDb().getField( - player, field); - } - - /** - * 从文本中提取第一个数字 - * "在线时间≥1小时" → 1 - * "破坏/放置方块100个" → 100 - */ - private int parseNumberFromText( - String text) { - java.util.regex.Matcher m = - java.util.regex.Pattern - .compile("(\\d+)") - .matcher(text); - if (m.find()) { - try { - return Integer.parseInt( - m.group(1)); - } catch (Exception e) { - return 0; - } - } - return 0; - } - public void onBlockPlaced( - String player, String material) { - String key = "PLACE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - setCounter(player, "PLACE:ALL", - getCounter(player, "PLACE:ALL") - + 1); - } - - public void onPlayerChat( - String player) { - String key = "CHAT"; - setCounter(player, key, - getCounter(player, key) + 1); - } - - private String formatMs(long ms) { - long sec = ms / 1000; - long min = sec / 60; - long hr = min / 60; - if (hr > 0) - return hr + "小时" - + (min % 60) + "分"; - if (min > 0) - return min + "分" - + (sec % 60) + "秒"; - return sec + "秒"; - } - - // ========== 数据结构 ========== - - public static class QuestFile { - public String displayName = ""; - public String fileName = ""; - public List details = - new ArrayList<>(); - public List rewards = - new ArrayList<>(); - public List conditions = - new ArrayList<>(); - - public boolean hasConditions() { - return conditions != null - && !conditions.isEmpty(); - } - } -} diff --git a/backup/Main_backup_20260606_235342.java b/backup/Main_backup_20260606_235342.java deleted file mode 100644 index 95903d1..0000000 --- a/backup/Main_backup_20260606_235342.java +++ /dev/null @@ -1,5734 +0,0 @@ -package Sdf1_login; - - -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.io.File; - -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 garbageBusy = - new ConcurrentHashMap<>(); - // [ADDED] 群系检测节流 - private final Map lastBiomeCheck = - new ConcurrentHashMap<>(); - // [ADDED] 垃圾站宝箱物品投入次数计数器(事不过三) - private final Map treasureDiscardCount = - new ConcurrentHashMap<>(); - // 在 private WelcomeManager welcome; 字段(若不存在)添加: - private WelcomeManager welcome; - private final Set pendingAdminAuth = - new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - /* - 资源包下载部分控制,不经过RadioDownloadListener的熔断控制 - */ - private RadioDownloadListener radioDL; - private final Set chatInputActive = - ConcurrentHashMap.newKeySet(); - // 补签确认:玩家名 → "日期:总积分" - public final Map - pendingBackCheck = - new HashMap<>(); - public MenuManager menu; - - private final Set loggedIn = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Set needsPasswordChange = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map joinTime = - new ConcurrentHashMap<>(); - private final Map joinLoc = - new HashMap<>(); - private final Map ipAutoLogin = - new HashMap<>(); - private static final long IP_SKIP_MS = 300000L; - private SalesStatsManager salesStats; - - private final Map 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 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 - pwdRollback = - new ConcurrentHashMap<>(); - private final Map menuChatField = - new java.util.concurrent.ConcurrentHashMap<>(); - private final Map 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 getLoggedIn() { - return loggedIn; - } - - public Set 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(); - // 启动时清理.sdf1临时文件 - cleanupSdf1Files(); - 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 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() { - // 卸载时清理.sdf1临时文件 - cleanupSdf1Files(); - 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 entry - : areaProtection.getPendingClearTasks().entrySet()) { - entry.getValue().cancel(); - // 立即清理效果 - Player p = Bukkit.getPlayer(entry.getKey()); - if (p != null && p.isOnline()) { - List marked = - areaProtection.getPlayerMarkedEffects() - .remove(entry.getKey()); - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - } - } - } - areaProtection.stopEnforceTask(); - areaProtection.saveWhitelists(); - areaProtection.getAlreadyForced().clear(); - - } - } - - /** - * 清理插件目录下的 .sdf1 后缀文件 - * 用于防风控任务准备 - */ - private void cleanupSdf1Files() { - File dataFolder = getDataFolder(); - if (dataFolder == null || !dataFolder.exists()) { - return; - } - - File[] sdf1Files = dataFolder.listFiles( - (dir, name) -> name.endsWith(".sdf1") - ); - - if (sdf1Files != null && sdf1Files.length > 0) { - int deletedCount = 0; - for (File file : sdf1Files) { - if (file.delete()) { - deletedCount++; - } - } - getLogger().info("[Sdf1_login] 已清理 " - + deletedCount + " 个 .sdf1 临时文件"); - } - } - - private void setupEconomy() { - if (getServer().getPluginManager() - .getPlugin("Vault") == null) return; - RegisteredServiceProvider 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> it = - joinTime.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry 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 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); - - // ★ 玩家上线时恢复区域效果 - AreaProtection areaProt = getAreaProtection(); - if (areaProt != null) { - areaProt.onPlayerJoin(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> alerts = - db.getUnnotifiedAlerts(name); - if (alerts.isEmpty()) return; - p.sendMessage("§c§l========== 安全报警 =========="); - for (Map 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; - if (isTreasureItem(item)) - continue; // Sdf1_game宝箱物品不计入 - 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 - + ": "); - 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 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> 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 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 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 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(); - - // ★ 登录阶段始终允许飞行,防止悬空被踢出 - p.setAllowFlight(true); - - if (!db.userExists(name)) { - if (config.maxAccountsPerIP > 0 - && ip != null - && !ipGroup.canRegister(ip)) { - p.kickPlayer( - buildIPKickMessage(p, ip)); - return; - } - } - 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); - } - - - - // ===== 登录连续天数更新(基于上线,非签到) ===== - if (db.userExists(name)) { - String today = new java.text.SimpleDateFormat( - "yyyy-MM-dd").format(new java.util.Date()); - Object lastLoginObj = db.getField(name, "last_login_date"); - String lastLoginDate = lastLoginObj != null ? lastLoginObj.toString() : ""; - Object streakObj = db.getField(name, "login_streak"); - int loginStreak = streakObj instanceof Number ? ((Number) streakObj).intValue() : 0; - - if (!lastLoginDate.isEmpty() && !lastLoginDate.equals(today)) { - try { - java.util.Date lastD = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(lastLoginDate); - java.util.Date todayD = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(today); - long diff = todayD.getTime() - lastD.getTime(); - if (diff > 86400000L * 2) { - loginStreak = 1; - } else if (diff > 86400000L) { - loginStreak++; - } - } catch (Exception ignored) {} - } else if (lastLoginDate.isEmpty()) { - loginStreak = 1; - } - db.setField(name, "login_streak", loginStreak); - db.setField(name, "last_login_date", today); - } - - 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; - if (isTreasureItem(it)) continue; // 跳过Sdf1_game宝箱物品 - } - - 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 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 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 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 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); - } - - /** - * 检查物品是否是Sdf1_game宝箱自定义物品 - * Sdf1_game的宝箱物品lore中包含"§0§k"标记 - */ - private boolean isTreasureItem(ItemStack item) { - if (item == null) return false; - if (!item.hasItemMeta()) return false; - ItemMeta meta = item.getItemMeta(); - if (meta == null) return false; - if (!meta.hasLore()) return false; - List lore = meta.getLore(); - if (lore == null) return false; - for (String line : lore) { - // Sdf1_game宝箱物品标记格式: §0§kCUSTOM|玩家|... - if (line.contains("\u00a70\u00a7k")) - return true; - } - return false; - } - - - @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 filterTab(List opts, String prefix) { - List r = new ArrayList<>(); - for (String o : opts) { - if (o.toLowerCase().startsWith(prefix.toLowerCase())) { - r.add(o); - } - } - return r; - } - - - /* - 雪球菜单: - 同时检测左右手 - */ - @EventHandler - public void onInteract(PlayerInteractEvent e) { - // ★ 过滤物理交互(压力板、绊线等),防止GUI被反复打开 - if (e.getAction() == org.bukkit.event.block.Action.PHYSICAL) return; - - Player p = e.getPlayer(); - if (isFrozen(p)) { - e.setCancelled(true); - return; - } - - ItemStack hand = p.getInventory() - .getItemInMainHand(); - ItemStack offhand = p.getInventory() - .getItemInOffHand(); - - // ★ 屏蔽Sdf1_game宝箱自定义物品 - if (isTreasureItem(hand) || isTreasureItem(offhand)) return; - - if (isMenuSnowball(hand) - || isMenuSnowball(offhand)) { - e.setCancelled(true); - gui.openMain(p); - return; - } - - // ★ 自定义菜单物品:只需PDC标记即可触发(不再额外检查DB) - if (isCustomMenuTrigger(hand) - || isCustomMenuTrigger(offhand)) { - 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); - } - // ★ Sdf1_game宝箱物品不干预,由Sdf1_game自行处理 - } - - - @EventHandler - public void onPickup(PlayerPickupItemEvent e) { - Player p = e.getPlayer(); - if (isFrozen(p)) { - e.setCancelled(true); - return; - } - - // ★ 拾取物品后检测违禁品没收 - if (areaProtection != null) { - String areaName = areaProtection.getPlayerArea(p); - if (areaName != null) { - AreaProtection.AreaConfig ac = - areaProtection.getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null - && !areaProtection.isExemptFromConfiscation( - p, areaName)) { - areaProtection.handleConfiscate(p, ac); - } - } - } - } - - @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 urls = - chatFilter.extractUrls(msg); - if (urls.isEmpty()) return; - - // 过滤白名单 - List 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 5) - int pointsBalance = points.getPoints(target); - g.setItem(5, mkItem(Material.EMERALD, - "§6§l积分余额: §e" + pointsBalance + " §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 sameIP = bondManager - .getSameIPPlayers(target); - List 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> txs = transfersOnly - ? bondManager.getTransferTransactions( - target, days) - : bondManager.getTransactions( - target, days); - - int slot = 0; - for (Map 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 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(); - - // ★ 宝箱物品防护(事不过三) - if (isTreasureItem(toSave)) { - UUID uid = p.getUniqueId(); - int count = treasureDiscardCount - .getOrDefault(uid, 0) + 1; - treasureDiscardCount.put(uid, count); - if (count < 3) { - // 前两次:警告 + 踢回背包 - p.sendMessage("§c§l[防护] §f该物品是宝箱自定义物品," - + "禁止投入垃圾站!" - + "§7(第" + count + "/2次警告)"); - // 物品已在toSave中克隆,原物品已被清空,踢回 - p.getInventory().addItem(toSave); - } else { - // 第三次:直接没收 - p.sendMessage("§c§l[防护] §f您多次尝试投入宝箱物品," - + "物品已被没收!"); - treasureDiscardCount.remove(uid); - } - return; - } - - 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())) { - e.setCancelled(true); - ItemStack toSave = - e.getCursor().clone(); - p.getOpenInventory().setCursor(null); - - // ★ 宝箱物品防护(事不过三) - if (isTreasureItem(toSave)) { - UUID uid = p.getUniqueId(); - int count = treasureDiscardCount - .getOrDefault(uid, 0) + 1; - treasureDiscardCount.put(uid, count); - if (count < 3) { - // 前两次:警告 + 踢回背包 - p.sendMessage("§c§l[防护] §f该物品是宝箱自定义物品," - + "禁止投入垃圾站!" - + "§7(第" + count + "/2次警告)"); - p.getInventory().addItem(toSave); - } else { - // 第三次:直接没收 - p.sendMessage("§c§l[防护] §f您多次尝试投入宝箱物品," - + "物品已被没收!"); - treasureDiscardCount.remove(uid); - } - return; - } - - 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 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 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 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 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); - } 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 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 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 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 "); - 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 "); - 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 "); - 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; - } - // 重载时清理.sdf1临时文件 - cleanupSdf1Files(); - 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> 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 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 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 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 §7- 添加白名单URL"); - p.sendMessage("§e/chat remove §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 onTabComplete( - CommandSender sender, Command cmd, - String label, String[] args) { - List 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 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 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 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 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 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 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; - } -} \ No newline at end of file diff --git a/backup/QuestTracker.java.backup b/backup/QuestTracker.java.backup deleted file mode 100644 index f2eda67..0000000 --- a/backup/QuestTracker.java.backup +++ /dev/null @@ -1,3662 +0,0 @@ -package Sdf1_login; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.generator.structure.Structure; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -// 如果 Bonds 在 Sdf1_login 包下: -import Sdf1_login.BondManager; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - - -public class QuestTracker { - - private final Plugin plugin; - private final QuestStorage storage; - private final Map - playerBiome = new HashMap<>(); - private final Map> - questCache = new HashMap<>(); - private final List - checkInRecords = new ArrayList<>(); - private final Map - lastCheckInPos = new HashMap<>(); - private static boolean loggedStructureRegistry - = false; - - public void recordCheckIn( - String player, - String biomeKey, - String category, - String worldName, - double x, double y, double z) { - String record = player - + "|" + worldName - + "|" + biomeKey - + "|" + category - + "|" + String.format( - "%.1f", x) - + "|" + String.format( - "%.1f", y) - + "|" + String.format( - "%.1f", z) - + "|" + System.currentTimeMillis(); - checkInRecords.add(record); - plugin.getLogger().info( - "[QuestTracker] 打卡记录: " - + record); - } - - public List getCheckInRecords() { - return new ArrayList<>(checkInRecords); - } - - public List getPlayerRecords( - String player) { - List list = new ArrayList<>(); - for (String r : checkInRecords) { - if (r.startsWith( - player.toLowerCase() - + "|")) { - list.add(r); - } - } - return list; - } - - - public QuestTracker(Plugin plugin) { - this.plugin = plugin; - this.storage = new QuestStorage( - plugin.getDataFolder()); - - loadAllQuests(); - } - - public void debugPlayer(String player) { - plugin.getLogger().info( - "[QuestTracker] === " - + player + " 任务数据 ==="); - } - public void shutdown() { - if (storage != null) { - storage.close(); - } - } - - /** - * 玩家移动时记录当前群系 - */ - public void onPlayerMove( - String player, - String biomeName) { - playerBiome.put(player, biomeName); - } - /** - * 标记玩家已打卡该群系 - */ - // ========== 群系分类映射表 ========== - private static final Map - BIOME_CATEGORY = new HashMap<>(); - static { - // 海洋群系 - BIOME_CATEGORY.put("minecraft:ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋"); - // 平原群系 - BIOME_CATEGORY.put("minecraft:plains", "平原"); - BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原"); - BIOME_CATEGORY.put("minecraft:meadow", "平原"); - // 森林群系 - BIOME_CATEGORY.put("minecraft:forest", "森林"); - BIOME_CATEGORY.put("minecraft:flower_forest", "森林"); - BIOME_CATEGORY.put("minecraft:birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:dark_forest", "森林"); - BIOME_CATEGORY.put("minecraft:taiga", "森林"); - BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:cherry_grove", "森林"); - BIOME_CATEGORY.put("minecraft:jungle", "森林"); - BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林"); - BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林"); - // 沙漠群系 - BIOME_CATEGORY.put("minecraft:desert", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠"); - BIOME_CATEGORY.put("minecraft:beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠"); - // 恶地群系 - BIOME_CATEGORY.put("minecraft:badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地"); - // 雪原群系 - BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原"); - BIOME_CATEGORY.put("minecraft:grove", "雪原"); - BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原"); - BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_river", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原"); - // 沙砾群系 - BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾"); - // 沼泽群系 - BIOME_CATEGORY.put("minecraft:swamp", "沼泽"); - BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽"); - // 河流 - BIOME_CATEGORY.put("minecraft:river", "河流"); - // 蘑菇岛 - BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛"); - // 地下群系 - BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下"); - BIOME_CATEGORY.put("minecraft:lush_caves", "地下"); - BIOME_CATEGORY.put("minecraft:deep_dark", "地下"); - // 下界群系 - BIOME_CATEGORY.put("minecraft:nether_wastes", "下界"); - BIOME_CATEGORY.put("minecraft:crimson_forest", "下界"); - BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); - BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); - BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); - // 岩岸/沙滩 - BIOME_CATEGORY.put("minecraft:stony_shore", - "石岸"); -// 海岸 - BIOME_CATEGORY.put("minecraft:mushroom_fields", - "蘑菇岛"); -// 苍白之园 - BIOME_CATEGORY.put( - "minecraft:pale_garden", "森林"); -// 末地小型岛屿 - BIOME_CATEGORY.put( - "minecraft:small_end_islands", "末地"); -// 主世界结构归类 - BIOME_CATEGORY.put("minecraft:village", "平原"); - BIOME_CATEGORY.put( - "minecraft:desert_pyramid", "沙漠"); - BIOME_CATEGORY.put( - "minecraft:jungle_pyramid", "森林"); - BIOME_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - - // 末地群系 - BIOME_CATEGORY.put("minecraft:the_end", "末地"); - BIOME_CATEGORY.put("minecraft:the_void", "末地"); - BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); - - - } - - // 所有群系大类列表 - private static final String[] - ALL_CATEGORIES = { - "海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "石岸", "蘑菇岛", "地下", - "下界", "末地" - }; - - // ===== 结构映射(PaperMC 1.21.4 实际结构 key)===== - private static final Map - STRUCTURE_CATEGORY = - new HashMap<>(); - static { - // 主世界结构 - STRUCTURE_CATEGORY.put( - "minecraft:village_plains", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_desert", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_savanna", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_snowy", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_taiga", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:desert_pyramid", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:jungle_pyramid", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - STRUCTURE_CATEGORY.put( - "minecraft:igloo", "雪原"); - STRUCTURE_CATEGORY.put( - "minecraft:mansion", "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:pillager_outpost", - "掠夺者前哨站"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft_mesa", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:stronghold", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:ancient_city", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:trail_ruins", - "远古遗迹"); - STRUCTURE_CATEGORY.put( - "minecraft:trial_chambers", - "试炼密室"); - STRUCTURE_CATEGORY.put( - "minecraft:monument", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_cold", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_warm", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck_beached", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:buried_treasure", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_desert", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_jungle", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_mountain", - "山地"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_ocean", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_swamp", - "沼泽"); - // 下界结构 - STRUCTURE_CATEGORY.put( - "minecraft:fortress", "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:bastion_remnant", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:nether_fossil", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_nether", - "下界"); - // 末地结构 - STRUCTURE_CATEGORY.put( - "minecraft:end_city", "末地"); - } - - // 所有群系分类的条件关键词映射 - private static final Map - CATEGORY_KEYWORDS = new HashMap<>(); - static { - CATEGORY_KEYWORDS.put("海洋", "海洋"); - CATEGORY_KEYWORDS.put("平原", "平原"); - CATEGORY_KEYWORDS.put("森林", "森林"); - CATEGORY_KEYWORDS.put("沙漠", "沙漠"); - CATEGORY_KEYWORDS.put("恶地", "恶地"); - CATEGORY_KEYWORDS.put("雪原", "雪原"); - CATEGORY_KEYWORDS.put("沙砾", "沙砾"); - CATEGORY_KEYWORDS.put("沼泽", "沼泽"); - CATEGORY_KEYWORDS.put("河流", "河流"); - CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛"); - CATEGORY_KEYWORDS.put("地下", "地下"); - CATEGORY_KEYWORDS.put("下界", "下界"); - CATEGORY_KEYWORDS.put("末地", "末地"); - // 条件文本中的别名映射 - CATEGORY_KEYWORDS.put("地狱", "下界"); - CATEGORY_KEYWORDS.put("下届", "下界"); - CATEGORY_KEYWORDS.put("主岛", "末地"); - CATEGORY_KEYWORDS.put("石岸", "石岸"); - CATEGORY_KEYWORDS.put("沙滩", "石岸"); - CATEGORY_KEYWORDS.put("海岸", "石岸"); - - // ===== 结构名称关键词映射 ===== - // 主世界结构 - CATEGORY_KEYWORDS.put("村庄", "村庄"); - CATEGORY_KEYWORDS.put("沙漠神殿", "沙漠"); - CATEGORY_KEYWORDS.put("丛林神庙", "森林"); - CATEGORY_KEYWORDS.put("沼泽小屋", "沼泽"); - CATEGORY_KEYWORDS.put("雪屋", "雪原"); - CATEGORY_KEYWORDS.put("林地府邸", "森林"); - CATEGORY_KEYWORDS.put("掠夺者前哨站", "平原"); - CATEGORY_KEYWORDS.put("废弃矿井", "地下"); - CATEGORY_KEYWORDS.put("要塞", "地下"); - CATEGORY_KEYWORDS.put("远古城市", "地下"); - CATEGORY_KEYWORDS.put("古迹废墟", "地下"); - CATEGORY_KEYWORDS.put("试炼密室", "地下"); - CATEGORY_KEYWORDS.put("海底废墟", "海洋"); - CATEGORY_KEYWORDS.put("沉船", "海洋"); - CATEGORY_KEYWORDS.put("埋藏的宝箱", "海洋"); - CATEGORY_KEYWORDS.put("废弃传送门", "主世界"); - CATEGORY_KEYWORDS.put("化石", "主世界"); - - // 下界结构 - CATEGORY_KEYWORDS.put("下界要塞", "下界"); - CATEGORY_KEYWORDS.put("猪灵堡垒", "下界"); - CATEGORY_KEYWORDS.put("玄武岩结构", "下界"); - - // 末地结构 - CATEGORY_KEYWORDS.put("末地城", "末地"); - CATEGORY_KEYWORDS.put("末地船", "末地"); - - // 下界相关别名 - CATEGORY_KEYWORDS.put("地狱要塞", "下界"); - CATEGORY_KEYWORDS.put("下界堡垒", "下界"); - - } - - /** - * 用世界维度名分类(最可靠) - * 世界名含 the_nether → 下界 - * 世界名含 the_end → 末地 - * 其余 → 主世界 - */ - public String mapBiomeCategory( - String rawKey, String worldName) { - // 优先用世界名判断维度 - if (worldName != null) { - String w = - worldName.toLowerCase(); - if (w.contains("nether")) - return "下界"; - if (w.contains("end")) - return "末地"; - } - - // 用群系映射表 - if (rawKey != null) { - String lower = - rawKey.toLowerCase(); - // 带 minecraft: 前缀的直接查 - String cat = - BIOME_CATEGORY.get(lower); - if (cat != null) - return cat; - - // 不带前缀的尝试补全 - String prefixed = - "minecraft:" + lower; - cat = BIOME_CATEGORY.get(prefixed); - if (cat != null) - return cat; - - // 模糊匹配群系名 - for (Map.Entry entry - : BIOME_CATEGORY.entrySet()) { - if (entry.getKey() - .contains(lower) - || lower.contains( - entry.getKey() - .replace( - "minecraft:", - ""))) { - return entry.getValue(); - } - } - } - - return "主世界"; - } - - // ========== 结构检测 ========== - - /** - * 检测指定位置是否存在自然生成的结构 - * 返回结构分类(如"村庄"、"沙漠"等),未找到返回null - * 使用 PaperMC World.hasStructureAt() + Registry.STRUCTURE API - */ - private String detectStructureAt( - String worldName, double x, - double y, double z) { - try { - World w = Bukkit.getWorld(worldName); - if (w == null) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测: 世界 '" - + worldName + "' 不存在"); - return null; - } - - int bx = (int) Math.floor(x); - int by = (int) Math.floor(y); - int bz = (int) Math.floor(z); - - // 首次调用时打印所有注册表结构键 - if (!loggedStructureRegistry) { - plugin.getLogger().info( - "[QuestTracker] === 结构注册表诊断 ==="); - int count = 0; - for (Structure s : Registry.STRUCTURE) { - NamespacedKey key = - Registry.STRUCTURE.getKey(s); - plugin.getLogger().info( - "[QuestTracker] 注册表结构 #" - + count + ": " + key); - count++; - } - plugin.getLogger().info( - "[QuestTracker] 注册表共 " - + count + " 个结构"); - plugin.getLogger().info( - "[QuestTracker] === 诊断结束 ==="); - loggedStructureRegistry = true; - } - - // 第1步: 精确位置检查 - io.papermc.paper.math.Position exactPos = - io.papermc.paper.math.Position.block( - bx, by, bz); - for (Structure structure : Registry.STRUCTURE) { - boolean hasAt = - w.hasStructureAt(exactPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey(structure); - String keyStr = (key != null) - ? key.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY.get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(精确): " - + keyStr + " -> category=" - + (cat != null - ? cat : "未映射")); - if (cat != null) return cat; - } - } - - // 第2步: Y轴偏移检查 (-5 到 +10) - for (int dy = -5; dy <= 10; dy++) { - if (dy == 0) continue; - int testY = by + dy; - io.papermc.paper.math.Position testPos = - io.papermc.paper.math.Position.block( - bx, testY, bz); - for (Structure structure - : Registry.STRUCTURE) { - boolean hasAt = w.hasStructureAt( - testPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey(structure); - String keyStr = (key != null) - ? key.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY - .get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(Y偏移" - + dy + "): " - + keyStr - + " -> category=" - + (cat != null - ? cat : "未映射")); - if (cat != null) return cat; - } - } - } - - // 第3步: 水平偏移检查 (±1格) - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dz == 0) continue; - io.papermc.paper.math.Position offsetPos = - io.papermc.paper.math.Position - .block(bx + dx, by, - bz + dz); - for (Structure structure - : Registry.STRUCTURE) { - boolean hasAt = w.hasStructureAt( - offsetPos, structure); - if (hasAt) { - NamespacedKey key = - Registry.STRUCTURE - .getKey( - structure); - String keyStr = (key != null) - ? key.toString() - : "unknown"; - String cat = - STRUCTURE_CATEGORY - .get(keyStr); - plugin.getLogger().info( - "[QuestTracker] 结构匹配(偏移" - + dx + "," - + dz + "): " - + keyStr - + " -> category=" - + (cat != null - ? cat - : "未映射")); - if (cat != null) return cat; - } - } - } - } - - plugin.getLogger().info( - "[QuestTracker] 所有位置检查完毕, " - + "未匹配任何结构 (pos=" - + bx + "," + by + "," + bz - + ")"); - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测异常: " - + ex.getClass().getSimpleName() - + ": " + ex.getMessage()); - ex.printStackTrace(); - } - return null; - } - - /** - * 将结构中文名映射到对应的大类 - * 用于匹配任务条件中的结构名 - */ - private String mapStructureCategory( - String structureName) { - if (structureName == null) return ""; - - // 主世界结构 - if (structureName.contains("村庄")) - return "村庄"; - if (structureName.contains("沙漠神殿")) - return "沙漠"; - if (structureName.contains("丛林神庙") || structureName.contains("丛林神殿")) - return "森林"; - if (structureName.contains("沼泽小屋")) - return "沼泽"; - if (structureName.contains("雪屋") || structureName.contains("冰屋")) - return "雪原"; - if (structureName.contains("林地府邸")) - return "森林"; - if (structureName.contains("掠夺者前哨站")) - return "掠夺者前哨站"; - if (structureName.contains("废弃矿井") || structureName.contains("恶地废弃矿井")) - return "地下"; - if (structureName.contains("要塞")) - return "地下"; - if (structureName.contains("远古城市")) - return "地下"; - if (structureName.contains("远古遗迹") || structureName.contains("古迹废墟")) - return "远古遗迹"; - if (structureName.contains("试炼密室")) - return "试炼密室"; - if (structureName.contains("海底神殿") || structureName.contains("海洋神殿")) - return "海洋"; - if (structureName.contains("海底废墟") || structureName.contains("冻洋废墟") || structureName.contains("暖洋废墟")) - return "海洋"; - if (structureName.contains("沉船")) - return "海洋"; - if (structureName.contains("埋藏的宝箱") || structureName.contains("埋藏的宝藏")) - return "海洋"; - if (structureName.contains("废弃传送门")) - return "平原"; - if (structureName.contains("化石")) - return "主世界"; - - // 下界结构 - if (structureName.contains("下界要塞")) - return "下界"; - if (structureName.contains("猪灵堡垒") || structureName.contains("堡垒遗迹")) - return "下界"; - if (structureName.contains("玄武岩结构") || structureName.contains("下界化石")) - return "下界"; - - // 末地结构 - if (structureName.contains("末地城")) - return "末地"; - if (structureName.contains("末地船")) - return "末地"; - - return ""; - } - - -// ========== 解析为区块 ========== - - private List
parseIntoSections( - List lines) { - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - return parseHashSections(lines); - } else if (hasDash) { - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - continue; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseHashSections(converted); - } - return new ArrayList<>(); - } - - private List
parseHashSections( - List lines) { - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add(new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } else if (t.isEmpty()) { - // skip - } else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty() - && curTitle != null) { - curLines.add(cond); - } - } - } - if (curTitle != null) { - sections.add(new Section( - curTitle, curLines)); - } - return sections; - } - - private List
buildFlatSections( - List lines, - String category) { - List conditions = - new ArrayList<>(); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - conditions.add(cond); - } - } - if (conditions.isEmpty()) - return new ArrayList<>(); - List
list = - new ArrayList<>(); - list.add(new Section( - "第一阶段", conditions)); - return list; - } - - // ========== 文件加载入口 ========== - - private void loadAllQuests() { - loadCategory("新人任务"); - loadCategory("主线任务"); - loadCategory("支线任务"); - } - - private void loadCategory(String name) { - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 扫描中..."); - File folder = new File( - plugin.getDataFolder(), - name); - String path = "plugins/Sdf1_login/" - + name + "/"; - - if (!folder.exists()) { - folder.mkdirs(); - plugin.getLogger().warning( - "[QuestTracker] 已创建目录: " - + path); - questCache.put(name, - new ArrayList<>()); - return; - } - - File[] mdFiles = folder.listFiles( - (d, n) -> n.endsWith(".md")); - - if (mdFiles == null - || mdFiles.length == 0) { - plugin.getLogger().warning( - "[QuestTracker] " + path - + " 下无 .md 文件"); - questCache.put(name, - new ArrayList<>()); - return; - } - Arrays.sort(mdFiles); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 发现 " + mdFiles.length - + " 个文件,开始扫描..."); - - // 合并所有文件的阶段 - TreeMap merged = - new TreeMap<>(); - int fileCount = 0; - - for (int fi = 0; - fi < mdFiles.length; fi++) { - File file = mdFiles[fi]; - List lines = - readFileLines(file); - - if (lines.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 空文件,跳过"); - continue; - } - - List
sections = - parseIntoSections(lines); - - if (sections.isEmpty()) { - sections = buildFlatSections( - lines, name); - } - - if (sections.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 无法识别,跳过"); - continue; - } - - // 每个文件独立处理阶段号 - int defaultNum = fi + 1; - buildSingleFileStages( - sections, name, - merged, defaultNum); - - fileCount++; - } - - if (merged.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " + name - + ": 无有效任务数据"); - questCache.put(name, - new ArrayList<>()); - return; - } - - // 日志 - for (Map.Entry en - : merged.entrySet()) { - int num = en.getKey() >= 1000 - ? en.getKey() - 999 - : en.getKey(); - plugin.getLogger().info( - "[QuestTracker] " + name - + " 阶段" + num - + " \"" - + en.getValue().displayName - + "\" → 条件" - + en.getValue() - .conditions.size() - + "项, 详情" - + en.getValue() - .details.size() - + "项, 奖励" - + en.getValue() - .rewards.size() - + "项"); - } - - List list = - new ArrayList<>( - merged.values()); - questCache.put(name, list); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 解析完成 → " - + list.size() + " 个阶段" - + " (来自 " + fileCount - + " 个文件)"); - } - - private Integer extractStageNumber( - String title) { - if (title == null) return null; - String t = title.toLowerCase().trim(); - - // 含阶段/stage/关/chapter 关键词 - boolean isStage = - t.contains("阶段") - || t.contains("stage") - || t.contains("关卡") - || t.contains("章节") - || t.contains("chapter"); - - if (isStage) { - String digits = title - .replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - try { - return Integer.parseInt(digits); - } catch (NumberFormatException e) { - // fall through - } - } - return parseChineseNumber(title); - } - - // 无关键词但以数字开头(如 "1. 挖掘") - String trimmed = t - .replaceAll("^[.、\\s]+", "") - .trim(); - if (trimmed.length() > 0 - && Character.isDigit( - trimmed.charAt(0))) { - String num = trimmed - .replaceAll("[^0-9]", ""); - if (!num.isEmpty()) { - try { - return Integer.parseInt(num); - } catch (NumberFormatException e) { - // fall through - } - } - } - - return null; - } -// ========== 领取记录 ========== - - /** - * 标记已领取某阶段奖励 - */ - public void markClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return; - // 用阶段编号代替显示名,保证稳定 - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - storage.setCondition(player, key); - } - - public boolean hasClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return false; - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - return toBool( - storage.getCondition( - player, key)); - } - - private int parseChineseNumber( - String title) { - String[] cn = {"一", "二", "三", - "四", "五", "六", "七", - "八", "九", "十"}; - if (title.contains("十")) { - for (int j = 0; j < 9; j++) { - if (title.contains(cn[j])) { - return 10 + j + 1; - } - } - return 10; - } - for (int i = 0; i < 9; i++) { - if (title.contains(cn[i])) { - return i + 1; - } - } - return 0; - } - - // ========== md 解析核心 ========== - - /** - * 读取文件所有行 - */ - private List readFileLines( - File file) { - List lines = - new ArrayList<>(); - try { - BufferedReader br = - new BufferedReader( - new FileReader(file)); - String line; - while ((line = br.readLine()) - != null) { - lines.add(line); - } - br.close(); - } catch (Exception e) { - plugin.getLogger().warning( - "[QuestTracker] 读取 " - + file.getName() - + " 失败: " - + e.getMessage()); - } - return lines; - } - - /** - * 自动检测格式并解析单个文件 - */ - private List parseMdFile( - File file, String category) { - List lines = - readFileLines(file); - if (lines.isEmpty()) - return new ArrayList<>(); - - // 检测分隔符 - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) - hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - // # 分隔格式 - return parseSections( - lines, category); - } else if (hasDash) { - // ---- 分隔格式:转换为 # 后复用 - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - title = "separator"; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseSections( - converted, category); - } else { - // 无分隔符:整文件一个阶段 - return parseFlat(lines, category); - } - } - - /** - * # 分隔格式 → 区块 → 阶段 - */ - private List parseSections( - List lines, - String category) { - // 拆分为区块 - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - - // # 开头 → 新区块 - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add( - new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } - // 空行/分隔线 → 跳过 - else if (t.isEmpty() - || t.startsWith("----") - || t.startsWith("====") - || t.startsWith("***")) { - // skip - } - // 普通行 → 区块内容 - else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - curLines.add(cond); - } - } - } - // 保存最后一个区块 - if (curTitle != null) { - sections.add( - new Section( - curTitle, curLines)); - } - - // 直接转为 QuestFile 列表 - List result = - new ArrayList<>(); - int num = 1; - for (Section sec : sections) { - QuestFile qf = new QuestFile(); - qf.displayName = - extractDisplayName( - sec.title); - qf.fileName = category; - qf.conditions = - new ArrayList<>(sec.lines); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - result.add(qf); - num++; - } - return result; - } - - /** - * 无分隔符:整文件 = 一个阶段 - */ - private List parseFlat( - List lines, - String category) { - List list = - new ArrayList<>(); - QuestFile qf = makeDefaultStage( - category); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - qf.conditions.add(cond); - } - } - if (!qf.conditions.isEmpty()) { - list.add(qf); - } - return list; - } -// ========== Vault 经济奖励发放 ========== - - /** - * 发放阶段奖励 - * 含经济内容的走 Vault API或债券 - */ - public void claimRewards( - Player player, - QuestFile qf) { - if (qf == null - || qf.rewards == null - || qf.rewards.isEmpty()) { - player.sendMessage("§c无可领取的奖励"); - return; - } - - Main main = (Main) plugin; - // ★ 读取配置:economy = Vault,bonds = 债券(默认) - boolean useEconomy = "economy".equalsIgnoreCase( - main.getConfigMgr().rewardChannel); - - // Vault 经济引用(仅 useEconomy 时使用) - net.milkbowl.vault.economy.Economy econ = null; - if (useEconomy) { - econ = main.getEconomy(); - } - - int sentCount = 0; - - for (String reward : qf.rewards) { - String rt = reward.trim(); - if (rt.isEmpty()) continue; - - // 检查可选奖励标记 - if (hasOptionalMarker(rt)) { - if (!areAllConditionsMet(player.getName(), qf)) { - player.sendMessage( - "§7[可选奖励] 需完成所有可选条件: " + rt); - continue; - } - } - - // 按 + 分割子项 - String[] parts = rt.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 尝试识别经济金额 - double amt = extractEconomyAmount(part); - if (amt > 0) { - // ★ 根据配置分发 - if (useEconomy && econ != null) { - // —— Vault 经济 —— - double before = - econ.getBalance(player); - econ.depositPlayer(player, amt); - double after = - econ.getBalance(player); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(余额: §e" - + String.format("%.2f", before) - + " §7→ §a" - + String.format("%.2f", after) - + "§7)"); - if (plugin instanceof Main) { - ((Main) plugin).getCommission() - .payCommission( - player.getName(), amt); - } - } else { - // —— 债券(默认) —— - int bondAmt = - (int) Math.round(amt); - BondManager bonds = main.getBonds(); - if (bonds != null) { - int before = bonds.getBonds( - player.getName()); - bonds.addBonds( - player.getName(), - bondAmt, - "quest_reward", - qf.displayName, - "任务系统", - "任务奖励:" + qf.displayName); - int after = bonds.getBonds( - player.getName()); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(债券: §e" - + before - + " §7→ §a" - + after + "§7)"); - } else { - player.sendMessage( - "§c债券系统不可用"); - } - } - sentCount++; - continue; - } - - // 尝试发物品 - java.util.List items = - parseRewardItems(part); - if (!items.isEmpty()) { - for (ItemStack is : items) { - player.getInventory().addItem(is); - } - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - continue; - } - - // 非经济非物品 → 仅消息 - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - } - } - - plugin.getLogger().info( - "[QuestTracker] " + player.getName() - + " 预取了 \"" + qf.displayName - + "\" 共 " + sentCount + "项奖励"); - } - - /** - * 检查奖励文本是否含可选标记 - */ - private boolean hasOptionalMarker( - String text) { - return text.contains("(可选)") - || text.contains("(进阶)") - || text.contains("【可选】") - || text.contains("【进阶】"); - } - - /** - * 检查阶段所有条件(含可选)是否全部满足 - * 用于决定可选奖励是否发放 - */ - private boolean areAllConditionsMet( - String player, QuestFile qf) { - if (qf.conditions == null - || qf.conditions.isEmpty()) - return true; - for (String cond : qf.conditions) { - if (!isConditionMet(player, cond)) - return false; - } - return true; - } - - /** - * 从奖励文本中提取经济金额 - * 支持: "500游戏币" "500" "金币500" - * "$500" "1000-5000"(随机) "1000~5000" - */ - private double extractEconomyAmount( - String text) { - String t = text.replaceAll(" ", ""); - - // 必须包含经济关键词才算经济奖励 - boolean hasEcon = - t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额") - || t.contains("发钱") - || t.contains("债券"); // ★ 新增 - - - if (!hasEcon) return 0; - - // 范围: "1000-5000" 或 "1000~5000" - Matcher rm = Pattern - .compile("(\\d+)[-~](\\d+)") - .matcher(t); - if (rm.find()) { - double min = Double.parseDouble( - rm.group(1)); - double max = Double.parseDouble( - rm.group(2)); - return min + Math.random() - * (max - min); - } - - // 纯数字(含 $ 前缀) - Matcher nm = Pattern - .compile("\\$?(\\d+(\\.\\d+)?)") - .matcher(t); - if (nm.find()) { - return Double.parseDouble( - nm.group(1)); - } - - return 0; - } - /** - * 解析奖励物品文本 - * "皮革套装*1" → 4件皮革装备 - * "木剑*1" → 1把木剑 - * "钻石*64" → 64颗钻石 - */ - private List parseRewardItems( - String text) { - List result = - new ArrayList<>(); - - // 去掉条件前缀 - // 如 "完成注册登录:皮革套装*1" - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - - // 按 + 分割多个物品 - String[] parts = - text.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 跳过经济文本 - if (extractEconomyAmount(part) > 0) - continue; - - int amount = 1; - String itemName = part; - - // 解析 *N 数量 - if (part.contains("*")) { - String[] sp = - part.split("\\*"); - itemName = sp[0].trim(); - try { - amount = Integer.parseInt( - sp[1].trim()); - } catch (Exception e) { - amount = 1; - } - } - - List mats = - matchMaterials(itemName); - for (Material m : mats) { - int give = m.name() - .contains("HELMET") - || m.name() - .contains("CHESTPLATE") - || m.name() - .contains("LEGGINGS") - || m.name() - .contains("BOOTS") - ? 1 : amount; - result.add( - new ItemStack(m, give)); - } - } - return result; - } - - /** - * 中文物品名 → Material 列表 - * 套装返回多个部件 - */ - private List matchMaterials( - String name) { - List list = - new ArrayList<>(); - String n = name.toLowerCase().trim(); - - // 套装 - if (n.contains("皮革套装") - || n.contains("皮套装")) { - list.add(Material.LEATHER_HELMET); - list.add(Material.LEATHER_CHESTPLATE); - list.add(Material.LEATHER_LEGGINGS); - list.add(Material.LEATHER_BOOTS); - return list; - } - if (n.contains("铁套装") - || n.contains("铁甲")) { - list.add(Material.IRON_HELMET); - list.add(Material.IRON_CHESTPLATE); - list.add(Material.IRON_LEGGINGS); - list.add(Material.IRON_BOOTS); - return list; - } - if (n.contains("钻石套装") - || n.contains("钻甲")) { - list.add(Material.DIAMOND_HELMET); - list.add(Material.DIAMOND_CHESTPLATE); - list.add(Material.DIAMOND_LEGGINGS); - list.add(Material.DIAMOND_BOOTS); - return list; - } - // 木制工具套装 - if (n.contains("木制工具") - || n.contains("木工具")) { - list.add(Material.WOODEN_SWORD); - list.add(Material.WOODEN_PICKAXE); - list.add(Material.WOODEN_AXE); - list.add(Material.WOODEN_SHOVEL); - return list; - } - if (n.contains("石制工具") - || n.contains("石工具")) { - list.add(Material.STONE_SWORD); - list.add(Material.STONE_PICKAXE); - list.add(Material.STONE_AXE); - list.add(Material.STONE_SHOVEL); - return list; - } - if (n.contains("铁工具")) { - list.add(Material.IRON_SWORD); - list.add(Material.IRON_PICKAXE); - list.add(Material.IRON_AXE); - list.add(Material.IRON_SHOVEL); - return list; - } - - // 单个物品 - if (n.contains("木剑")) - list.add(Material.WOODEN_SWORD); - else if (n.contains("木镐")) - list.add(Material.WOODEN_PICKAXE); - else if (n.contains("铁剑")) - list.add(Material.IRON_SWORD); - else if (n.contains("铁镐")) - list.add(Material.IRON_PICKAXE); - else if (n.contains("钻石剑")) - list.add(Material.DIAMOND_SWORD); - else if (n.contains("钻石镐")) - list.add(Material.DIAMOND_PICKAXE); - else if (n.contains("钻石")) - list.add(Material.DIAMOND); - else if (n.contains("铁锭")) - list.add(Material.IRON_INGOT); - else if (n.contains("金锭") - || n.contains("金块")) - list.add(Material.GOLD_INGOT); - else if (n.contains("煤炭")) - list.add(Material.COAL); - else if (n.contains("木棍")) - list.add(Material.STICK); - else if (n.contains("弓")) - list.add(Material.BOW); - else if (n.contains("箭")) - list.add(Material.ARROW); - else if (n.contains("床")) - list.add(Material.RED_BED); - else { - // 尝试直接匹配 Material 名 - try { - list.add(Material.valueOf( - n.toUpperCase() - .replaceAll("[^A-Z_]", - ""))); - } catch (Exception e) { - // 未知物品 - } - } - return list; - } - - private QuestFile getOrCreateStage( - TreeMap map, - int key, - String category) { - if (!map.containsKey(key)) { - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + key; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - } - return map.get(key); - } - - /** - * 单个文件的区块 → 阶段 - * defaultStageNum: 文件无阶段标题时的默认编号 - */ - private void buildSingleFileStages( - List
sections, - String category, - TreeMap stageMap, - int defaultStageNum) { - Integer curKey = null; - - for (Section sec : sections) { - Integer stageNum = - extractStageNumber(sec.title); - - if (stageNum != null) { - curKey = stageNum; - } else if (curKey == null) { - curKey = defaultStageNum; - } - - QuestFile qf = getOrCreateStage( - stageMap, curKey, category); - - String displayName = - extractDisplayName(sec.title); - - // 设置显示名 - if (!displayName.isEmpty() - && qf.displayName - .startsWith("阶段 ")) { - qf.displayName = displayName; - } - - // 路由 - if (stageNum != null - && sec.lines.isEmpty()) { - if (!displayName.isEmpty()) { - qf.displayName = displayName; - } - } - else if (isRewardTitle(sec.title)) { - // 奖励 → 只进 rewards - qf.rewards.addAll(sec.lines); - } - else if (isDetailsTitle(sec.title)) { - // 详情 → 进 details + conditions - qf.details.addAll(sec.lines); - for (String line : sec.lines) { - if (!qf.conditions - .contains(line)) { - qf.conditions.add(line); - } - } - } - else if (isConditionsTitle( - sec.title)) { - // 条件 → 只进 conditions - qf.conditions.addAll(sec.lines); - } - else { - // 其他 → 条件 - qf.conditions.addAll(sec.lines); - } - } - } - - /** - * "主线任务-斗志昂扬" → "斗志昂扬" - * "支线-钓鱼" → "钓鱼" - * "阶段 1" → "阶段 1" - * "第一阶段" → "第一阶段" - */ - private String extractDisplayName( - String title) { - if (title == null) return ""; - // 按 - 或 — 或 – 分割 - String[] parts = title.split( - "\\s*[-—–]\\s*", 2); - if (parts.length >= 2) { - String right = parts[1].trim(); - if (!right.isEmpty()) { - return right; - } - } - return title; - } - private DatabaseManager getDb() { - return ((Main) plugin).getDb(); - } - - private boolean isDetailsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("任务详情") - || t.contains("详情") - || t.contains("任务说明") - || t.contains("任务目标") - || t.contains("description") - || t.contains("任务要求"); - } - - private boolean isConditionsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("解锁条件") - || t.contains("条件") - || t.contains("前置条件") - || t.contains("前置要求") - || t.contains("解锁") - || t.contains("condition"); - } - - - private void ensureStage( - TreeMap map, - String category, - QuestFile current, - int counter) { - if (current != null) return; - // 用大数字确保排在有编号阶段后面 - int key = 1000 + counter; - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + (counter + 1); - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - // 更新调用方的引用(通过返回值) - // 这里用 map 操作,current 无法直接更新 - // 所以改用返回值方式 - } - /** - * 自动将条件中的奖励内容移到 rewards - */ - private void separateRewards(QuestFile qf) { - List remaining = - new ArrayList<>(); - for (String line : qf.conditions) { - if (isRewardContent(line)) { - if (!qf.rewards.contains(line)) { - qf.rewards.add(line); - } - } else { - remaining.add(line); - } - } - qf.conditions = remaining; - } - private boolean checkStageDependency( - String player, - String condition) { - Matcher m = Pattern.compile( - "第([一二三四五六七八九十\\d]+)" - + "阶段") - .matcher(condition); - - while (m.find()) { - String numStr = m.group(1); - int stageNum = - parseChineseNumber(numStr); - if (stageNum == 0) { - try { - stageNum = - Integer.parseInt(numStr); - } catch (Exception e) { - return false; - } - } - - boolean stageDone = false; - for (List quests - : questCache.values()) { - for (QuestFile qf : quests) { - Integer qfNum = - extractStageNumber( - qf.displayName); - if (qfNum != null - && qfNum == stageNum) { - if (isStageComplete(player, - qf.conditions)) { - stageDone = true; - } - break; - } - } - if (stageDone) break; - } - - if (!stageDone) return false; - } - return true; - } - - /** - * 判断一行内容是否为奖励 - */ - private boolean isRewardContent( - String line) { - String l = line.toLowerCase(); - // 含货币关键词 - if (l.contains("游戏币") - || l.contains("金币") - || l.contains("积分") - || l.contains("钻石") - || l.contains("发钱") - || l.contains("发放") - || l.contains("经验") - || l.contains("等级") - || l.contains("经济") - || l.contains("余额") - || l.contains("块钱")) - return true; - // 纯数字+单位(如 "500游戏币") - if (l.matches( - ".*\\d+\\s*(游戏币|金币|元|积分|" - + "钻石|块钱|经验|级|经济|余额).*")) - return true; - // $前缀(如 "$500") - if (l.matches(".*\\$\\d+.*")) - return true; - // 范围格式(如 "1000-5000" 或 "1000~5000") - if (l.matches(".*\\d+[-~]\\d+.*") - && !l.contains("阶段") - && !l.contains("stage")) - return true; - return false; - } - - // ========== 标题分类 ========== - - private boolean isStageTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("阶段") - || t.contains("stage") - || t.contains("第一") - || t.contains("第二") - || t.contains("第三") - || t.contains("第四") - || t.contains("第五"); - } - - private boolean isRewardTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("奖励") - || t.contains("奖品") - || t.contains("报酬") - || t.contains("reward") - || t.contains("奖赏") - || t.contains("礼包内容") - || t.contains("礼包") - || t.contains("物品奖励"); - } - - - private QuestFile makeDefaultStage( - String category) { - QuestFile qf = new QuestFile(); - qf.displayName = "第一阶段"; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - return qf; - } - - private static class Section { - final String title; - final List lines; - Section(String title, - List lines) { - this.title = title; - this.lines = lines; - } - } - - // ========== 公开查询 ========== - - public List getQuests( - String category) { - List list = - questCache.get(category); - return list != null ? list - : new ArrayList<>(); - } - - public int getCompletedStageCount( - String player, - List quests) { - int count = 0; - if (quests == null) return 0; - for (QuestFile qf : quests) { - if (isStageComplete(player, - qf.conditions)) { - count++; - } - } - return count; - } - - public QuestFile getCurrentStage( - String player, - List quests) { - if (quests == null) return null; - for (QuestFile qf : quests) { - if (!isStageComplete( - player, qf.conditions)) { - return qf; - } - } - return null; - } - - public int getStageNumber( - List quests, - QuestFile qf) { - if (quests == null || qf == null) - return 0; - return quests.indexOf(qf) + 1; - } - - public int getStageNumber( - QuestFile qf) { - if (qf == null) return 0; - List quests = - getQuests(qf.fileName); - return getStageNumber(quests, qf); - } - - public boolean isStageCompleted( - String player, - QuestFile qf) { - if (qf == null) return false; - return isStageComplete( - player, qf.conditions); - } - - public List getUnmetConditions( - String player, - List conditions) { - List unmet = - new ArrayList<>(); - if (conditions == null) return unmet; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) { - unmet.add( - getProgress( - player, cond)); - } - } - return unmet; - } - public String getProgress( - String player, String rawCond) { - if (player == null || rawCond == null) - return "§7未知"; - String key = stripOptional(rawCond); - - if (key.contains("打卡") - && key.contains("群系")) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return "§7打卡 " + biome - + "群系: §e" - + catCount + "/1"; - } - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - if (key.contains("完成") - && key.contains("阶段")) { - boolean done = checkStageDependency( - player, key); - return done - ? "§a✓ " + key - : "§c✗ " + key; - } - - if ("完成注册登录".equals(key)) { - boolean done = getDb() != null - && getDb().userExists( - player); - return done - ? "§a✓ 完成注册登录" - : "§c✗ 完成注册登录"; - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - boolean done = val != null - && !val.toString().isEmpty(); - return done - ? "§a✓ 绑定Email" - : "§c✗ 绑定Email"; - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + needed + "小时"; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - int cur = getCounter( - player, "CHAT"); - return "§7聊天: §e" - + cur + "/1"; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return "§7方块操作: §e" - + total + "/" + needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return "§7连续登录: §e" - + streak + "/" + needed + "天"; - } - - if (key.startsWith("MINE:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "MINE:" + parts[1]); - return "§7挖 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "VILLAGER_TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - - } - - // ===== 击杀泛解析(支持中文列表) ===== -// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等 - if (key.contains("击杀") && key.contains("只")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - - String mobPart = key; - java.util.regex.Matcher killM = - java.util.regex.Pattern - .compile("击杀\\d+只(.+)") - .matcher(key); - if (killM.find()) { - mobPart = killM.group(1).trim(); - } - - String[] mobNames = mobPart - .split("[、,,,]+"); - int totalKills = 0; - StringBuilder names = new StringBuilder(); - for (String name : mobNames) { - String trimmed = name.trim(); - if (trimmed.isEmpty()) continue; - String entityType = - chineseMobToEntity(trimmed); - if (!entityType.isEmpty()) { - totalKills += getCounter( - player, - "KILL:" + entityType); - if (names.length() > 0) - names.append("/"); - names.append(trimmed); - } - } - return "§7击杀" + names + ": §e" - + totalKills + "/" + needed; - } - -// 单一KILL:xxx:N 格式(原有逻辑保留) - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - int needed = parseInt(p[2]); - int cur = getCounter(player, - "KILL:" + p[1]); - return "§7击杀" + p[1] + ": §e" - + cur + "/" + needed; - } - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "KILL_PLAYER"); - return "§7击杀玩家: §e" - + cur + "/" + needed; - } - - if (key.startsWith("CRAFT")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "CRAFT"); - return "§7合成: §e" - + cur + "/" + needed; - } - - if (key.startsWith("FISH")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "FISH"); - return "§7钓鱼: §e" - + cur + "/" + needed; - } - - if (key.startsWith("PLAYTIME")) { - String[] parts = key.split(":"); - long needed = parts.length >= 2 - ? Long.parseLong(parts[1]) - : 0; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + formatMs(needed); - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7上报村庄: §e" - + cur + "/" + needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7探索村庄: §e" - + cur + "/1"; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return "§7近7天登录: §e" - + streak + "/" - + needed + "天"; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7新手任务: §e" - + done + "/" - + quests.size(); - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7主线任务: §e" - + done + "/" - + quests.size(); - } - // ===== 泛解析:击杀类进度 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < mobTypes.size(); i++) { - total += getCounter(player, - "KILL:" + mobTypes.get(i)); - if (i > 0) names.append("/"); - names.append(reverseMobName( - mobTypes.get(i))); - } - return "§7击杀" + names + ": §e" - + total + "/" + needed; - } - } - - // ===== 泛解析:村民交易进度 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - // ===== 泛解析:采集/挖矿进度 ===== - if (key.contains("采集") - || key.contains("挖")) { - int needed = - parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < matTypes.size(); i++) { - total += getCounter( - player, - "MINE:" + matTypes - .get(i)); - if (i > 0) - names.append("/"); - names.append( - translateMaterialName( - matTypes - .get(i))); - } - return "§7采集" + names - + ": §e" + total - + "/" + needed; - } - } - - // ===== 泛解析:合成进度 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "CRAFT"); - return "§7合成: §e" + cur + "/" + needed; - } - - // ===== 泛解析:钓鱼进度 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "FISH"); - return "§7钓鱼: §e" + cur + "/" + needed; - } - - return "§7" + key; - } - private String translateMaterialName( - String name) { - switch (name) { - case "COAL_ORE": - return "煤矿石"; - case "DEEPSLATE_COAL_ORE": - return "深层煤矿石"; - case "IRON_ORE": - return "铁矿石"; - case "DEEPSLATE_IRON_ORE": - return "深层铁矿石"; - case "COPPER_ORE": - return "铜矿石"; - case "DEEPSLATE_COPPER_ORE": - return "深层铜矿石"; - case "GOLD_ORE": - return "金矿石"; - case "DEEPSLATE_GOLD_ORE": - return "深层金矿石"; - case "DIAMOND_ORE": - return "钻石矿石"; - case "DEEPSLATE_DIAMOND_ORE": - return "深层钻石矿石"; - case "EMERALD_ORE": - return "绿宝石矿石"; - case "DEEPSLATE_EMERALD_ORE": - return "深层绿宝石矿石"; - case "LAPIS_ORE": - return "青金石矿石"; - case "DEEPSLATE_LAPIS_ORE": - return "深层青金石矿石"; - case "REDSTONE_ORE": - return "红石矿石"; - case "DEEPSLATE_REDSTONE_ORE": - return "深层红石矿石"; - case "NETHER_GOLD_ORE": - return "下界金矿石"; - case "NETHER_QUARTZ_ORE": - return "下界石英矿石"; - case "ANCIENT_DEBRIS": - return "远古残骸"; - case "COAL": - return "煤炭"; - case "RAW_IRON": - return "粗铁"; - case "RAW_GOLD": - return "粗金"; - case "RAW_COPPER": - return "粗铜"; - case "DIAMOND": - return "钻石"; - case "EMERALD": - return "绿宝石"; - case "LAPIS_LAZULI": - return "青金石"; - case "REDSTONE": - return "红石"; - case "QUARTZ": - return "石英"; - default: - return name; - } - } - - /** - * 中文物种名 → Bukkit EntityType 名 - * 支持击杀泛解析的模糊匹配 - */ - private String chineseMobToEntity(String cn) { - String n = cn.toLowerCase().trim(); - - // 直接别名表 - Map mobMap = new HashMap<>(); - mobMap.put("骷髅", "SKELETON"); - mobMap.put("僵尸", "ZOMBIE"); - mobMap.put("苦力怕", "CREEPER"); - mobMap.put("蜘蛛", "SPIDER"); - mobMap.put("洞穴蜘蛛", "CAVE_SPIDER"); - mobMap.put("小白", "SKELETON"); - mobMap.put("末影人", "ENDERMAN"); - mobMap.put("enderman", "ENDERMAN"); - mobMap.put("猪灵", "PIGLIN"); - mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE"); - mobMap.put("恶魂", "GHAST"); - mobMap.put("凋灵骷髅", "WITHER_SKELETON"); - mobMap.put("烈焰人", "BLAZE"); - mobMap.put("史莱姆", "SLIME"); - mobMap.put("岩浆怪", "MAGMA_CUBE"); - mobMap.put("女巫", "WITCH"); - mobMap.put("蠹虫", "SILVERFISH"); - mobMap.put("潜影贝", "SHULKER"); - mobMap.put("守卫者", "GUARDIAN"); - mobMap.put("远古守卫者", "ELDER_GUARDIAN"); - mobMap.put("溺尸", "DROWNED"); - mobMap.put("僵尸村民", "ZOMBIE_VILLAGER"); - mobMap.put("尸壳", "HUSK"); - mobMap.put("流浪者", "STRAY"); - mobMap.put("幻翼", "PHANTOM"); - mobMap.put("掠夺者", "PILLAGER"); - mobMap.put("卫道士", "VINDICATOR"); - mobMap.put("唤魔者", "EVOKER"); - mobMap.put("恼鬼", "VEX"); - mobMap.put("劫掠兽", "RAVAGER"); - mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY"); - mobMap.put("凋灵", "WITHER"); - mobMap.put("末影龙", "ENDER_DRAGON"); - - // 精确匹配 - if (mobMap.containsKey(n)) { - return mobMap.get(n); - } - - // 模糊匹配(包含关系) - for (Map.Entry en - : mobMap.entrySet()) { - if (n.contains(en.getKey()) - || en.getKey().contains(n)) { - return en.getValue(); - } - } - - // 尝试直接用英文名 - try { - return org.bukkit.entity.EntityType - .valueOf(n.toUpperCase() - .replace(" ", "_")) - .name(); - } catch (Exception e) { - return ""; - } - } - - public List checkInBiome( - String player, - String rawBiomeKey, - String worldName, - double x, double y, double z) { - List matched = - new ArrayList<>(); - - // 32格去重 - String posKey = - player + "_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - String lastPos = - lastCheckInPos.get(posKey); - if (curPos.equals(lastPos)) { - plugin.getLogger().info( - "[QuestTracker] 打卡忽略: " - + player - + " 32格内重复"); - return matched; - } - lastCheckInPos.put(posKey, curPos); - - String category = - mapBiomeCategory( - rawBiomeKey, worldName); - // 结构检测(覆盖纯生物群系分类) - // 地下检测:Y低于表面则归类为地下 -// 覆盖废弃矿井/试炼密室/要塞/远古城市等 - try { - org.bukkit.World uw = - Bukkit.getWorld(worldName); - if (uw != null) { - int surfaceY = uw - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - } - } catch (Exception ignored) {} - - // ===== 结构检测(覆盖纯生物群系分类)===== - // 使用 PaperMC API 检测玩家所在位置的结构 - String structureCat = - detectStructureAt( - worldName, x, y, z); - if (structureCat != null - && !structureCat.isEmpty()) { - // 结构检测成功,结构分类优先 - // (站在结构上就不算"地下") - category = structureCat; - plugin.getLogger().info( - "[QuestTracker] 结构覆盖: " - + category - + " (结构: " - + structureCat + ")"); - } else { - plugin.getLogger().info( - "[QuestTracker] 未检测到结构: world=" - + worldName + ", pos=" - + String.format("%.0f,%.0f,%.0f", x, y, z)); - } - - - // 记录总打卡 - int allCur = getCounter( - player, "biome_ALL"); - setCounter(player, "biome_ALL", - allCur + 1); - - // 记录分类打卡 - if (!category.isEmpty() - && !"主世界" - .equals(category)) { - String catKey = - "biomecat_" + category; - int catCur = getCounter( - player, catKey); - setCounter(player, catKey, - catCur + 1); - } - - // 记录具体群系 - String bioKey = - "biome_" + rawBiomeKey; - int bioCur = getCounter( - player, bioKey); - setCounter(player, bioKey, - bioCur + 1); - - // 检查所有任务条件是否匹配 - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - for (String cat : categories) { - List quests = - getQuests(cat); - for (QuestFile qf : quests) { - if (isStageCompleted( - player, qf)) - continue; - for (String cond - : qf.conditions) { - if (isOptional(cond)) - continue; - String condKey = - stripOptional(cond); - if (!condKey - .contains("打卡")) - continue; - // 支持群系和结构两种打卡条件 - boolean isBiomeCond = - condKey.contains("群系") - || condKey - .contains("风景点"); - boolean isStructureCond = - condKey.contains("结构") - || condKey - .contains("村庄") - || condKey - .contains("神殿") - || condKey - .contains("神庙") - || condKey - .contains("小屋") - || condKey - .contains("雪屋") - || condKey - .contains("府邸") - || condKey - .contains("前哨站") - || condKey - .contains("矿井") - || condKey - .contains("要塞") - || condKey - .contains("城市") - || condKey - .contains("废墟") - || condKey - .contains("密室") - || condKey - .contains("沉船") - || condKey - .contains("宝箱") - || condKey - .contains("传送门") - || condKey - .contains("化石") - || condKey - .contains("堡垒"); - if (!isBiomeCond - && !isStructureCond) - continue; - String target = - extractBiome(condKey); - plugin.getLogger().info( - "[QuestTracker] 条件匹配: condKey=\"" - + condKey + "\", target=\"" - + target + "\", category=\"" - + category + "\", isBiomeCond=" - + isBiomeCond + ", isStructureCond=" - + isStructureCond); - boolean hit = false; - if ("ALL".equals(target) - || "风景点" - .equals(target)) { - hit = true; - } else if (!target.isEmpty() - && target.equals( - category)) { - hit = true; - } - if (hit && !matched - .contains(cond)) { - matched.add(cond); - } - } - } - } - - return matched; - } - - /* - * 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录 - */ - public String checkInAtPosition( - String player, String worldName, - double x, double y, double z) { - - List history = - loadCheckInPositions(player); - for (double[] pos : history) { - double dist = Math.sqrt( - Math.pow(x - pos[0], 2) - + Math.pow(y - pos[1], 2) - + Math.pow(z - pos[2], 2)); - if (dist <= 32) { - return "§c距离上次打卡点太近(" - + String.format( - "%.0f", dist) - + "格)"; - } - } - - // ===== 获取群系和分类 ===== - String biome = "minecraft:plains"; - String category = "平原"; - String structureCat = null; - try { - org.bukkit.World w = - Bukkit.getWorld(worldName); - if (w != null) { - org.bukkit.Location loc = - new org.bukkit.Location( - w, - (int) Math.floor(x), - (int) Math.floor(y), - (int) Math.floor(z)); - biome = loc.getBlock() - .getBiome().getKey() - .toString(); - category = mapBiomeCategory( - biome, worldName); - - int surfaceY = w - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - - // ===== 结构检测(覆盖纯生物群系分类)===== - structureCat = - detectStructureAt( - worldName, x, y, z); - plugin.getLogger().info( - "[QuestTracker] checkInAtPosition 结构检测: " - + structureCat + " at " - + String.format("%.0f,%.0f,%.0f", x, y, z)); - if (structureCat != null - && !structureCat.isEmpty()) { - // 结构分类优先于"地下" - category = structureCat; - } - - if ("下界".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.NETHER) { - category = "下界"; - } else if ("末地".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.THE_END) { - category = "末地"; - } - } - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 群系读取失败: " - + ex.getMessage()); - } - - plugin.getLogger().info( - "[QuestTracker] 打卡: " - + player - + " world=" + worldName - + " biome=" + biome - + " cat=" + category - + " pos=" - + String.format( - "%.0f,%.0f,%.0f", - x, y, z)); - - saveCheckInPosition( - player, x, y, z); - recordCheckIn( - player, biome, category, - worldName, x, y, z); - List matched = - checkInBiome( - player, biome, - worldName, x, y, z); - - StringBuilder sb = new StringBuilder(); - sb.append("§a打卡成功!群系: §e") - .append(category); - if (structureCat != null && !structureCat.isEmpty()) { - sb.append("\n§a结构: §e").append(structureCat); - } - if (!matched.isEmpty()) { - sb.append("\n§a完成任务条件:"); - for (String m : matched) { - String qn = - findQuestName(m); - sb.append("\n§7• "); - if (qn != null) { - sb.append("§e[") - .append(qn) - .append("§7] §f"); - } - sb.append(m); - } - } - return sb.toString(); - } - - - /** - * 根据条件文本查找所属任务名 - */ - private String findQuestName( - String condition) { - for (String cat : Arrays.asList( - "新人任务", - "主线任务", - "支线任务")) { - for (QuestFile qf : - getQuests(cat)) { - if (qf.conditions.contains( - condition)) { - return qf.displayName; - } - } - } - return null; - } - - - /** - * 加载玩家所有历史打卡坐标 - * 格式: x1,y1,z1;x2,y2,z2;... - */ - private List loadCheckInPositions( - String player) { - List list = new ArrayList<>(); - Object val = storage.getCondition( - player, "checkin_all_pos"); - if (val == null) return list; - String data = val.toString(); - if (data.isEmpty()) return list; - String[] records = data.split(";"); - for (String r : records) { - String[] parts = r.split(","); - if (parts.length >= 3) { - try { - list.add(new double[]{ - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2])}); - } catch (NumberFormatException e) { - // 跳过损坏数据 - } - } - } - return list; - } - - /** - * 保存打卡坐标(追加到已有记录) - */ - private void saveCheckInPosition( - String player, double x, - double y, double z) { - List list = - loadCheckInPositions(player); - list.add(new double[]{x, y, z}); - StringBuilder sb = new StringBuilder(); - for (double[] pos : list) { - if (sb.length() > 0) sb.append(";"); - sb.append(pos[0]).append(",") - .append(pos[1]).append(",") - .append(pos[2]); - } - storage.setConditionRaw( - player, - "checkin_all_pos", - sb.toString()); - } - // ========== 中文生物名 → EntityType 映射 ========== - private static final Map - MOB_NAME_MAP = new HashMap<>(); - static { - MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER"); - MOB_NAME_MAP.put("骷髅", "SKELETON"); - MOB_NAME_MAP.put("僵尸", "ZOMBIE"); - MOB_NAME_MAP.put("苦力怕", "CREEPER"); - MOB_NAME_MAP.put("蜘蛛", "SPIDER"); - MOB_NAME_MAP.put("末影人", "ENDERMAN"); - MOB_NAME_MAP.put("女巫", "WITCH"); - MOB_NAME_MAP.put("烈焰人", "BLAZE"); - MOB_NAME_MAP.put("僵尸猪灵", - "ZOMBIFIED_PIGLIN"); - MOB_NAME_MAP.put("猪灵", "PIGLIN"); - MOB_NAME_MAP.put("恶魂", "GHAST"); - MOB_NAME_MAP.put("凋灵骷髅", - "WITHER_SKELETON"); - MOB_NAME_MAP.put("史莱姆", "SLIME"); - MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE"); - MOB_NAME_MAP.put("溺尸", "DROWNED"); - MOB_NAME_MAP.put("尸壳", "HUSK"); - MOB_NAME_MAP.put("流髑", "STRAY"); - MOB_NAME_MAP.put("幻翼", "PHANTOM"); - MOB_NAME_MAP.put("守卫者", "GUARDIAN"); - MOB_NAME_MAP.put("远古守卫者", - "ELDER_GUARDIAN"); - MOB_NAME_MAP.put("掠夺者", "PILLAGER"); - MOB_NAME_MAP.put("卫道士", "VINDICATOR"); - MOB_NAME_MAP.put("唤魔者", "EVOKER"); - MOB_NAME_MAP.put("恼鬼", "VEX"); - MOB_NAME_MAP.put("劫掠兽", "RAVAGER"); - MOB_NAME_MAP.put("潜影贝", "SHULKER"); - MOB_NAME_MAP.put("末影螨", "ENDERMITE"); - MOB_NAME_MAP.put("凋灵", "WITHER"); - MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON"); - MOB_NAME_MAP.put("监守者", "WARDEN"); - MOB_NAME_MAP.put("嗅探兽", "SNIFFER"); - MOB_NAME_MAP.put("僵尸村民", - "ZOMBIE_VILLAGER"); - } - - /** - * 从条件文本中提取所有匹配的生物类型 - * 按名称长度降序匹配,避免短名误匹配 - */ - private List extractMobTypes( - String text) { - List types = new ArrayList<>(); - List> sorted = - new ArrayList<>( - MOB_NAME_MAP.entrySet()); - sorted.sort((a, b) -> - b.getKey().length() - - a.getKey().length()); - String remaining = text; - for (Map.Entry entry - : sorted) { - if (remaining.contains( - entry.getKey())) { - if (!types.contains( - entry.getValue())) { - types.add(entry.getValue()); - } - remaining = remaining.replace( - entry.getKey(), ""); - } - } - return types; - } - - /** - * EntityType → 中文名 - */ - private String reverseMobName( - String entityType) { - for (Map.Entry entry - : MOB_NAME_MAP.entrySet()) { - if (entry.getValue() - .equals(entityType)) { - return entry.getKey(); - } - } - return entityType; - } - - /** - * 从条件文本中提取矿物名 - * "采集任意矿物(煤炭除外)1组" - * → [COAL_ORE, IRON_ORE, GOLD_ORE, ...] - */ - private List extractMaterialTypes( - String text) { - List types = new ArrayList<>(); - // 排除列表 - List excludes = - new ArrayList<>(); - if (text.contains("煤炭除外")) { - excludes.add("COAL"); - excludes.add("COAL_ORE"); - excludes.add("DEEPSLATE_COAL_ORE"); - } - // 通配符:"矿物" "矿石" - if (text.contains("矿物") - || text.contains("矿石")) { - String[] ores = { - "COAL_ORE", - "DEEPSLATE_COAL_ORE", - "IRON_ORE", - "DEEPSLATE_IRON_ORE", - "COPPER_ORE", - "DEEPSLATE_COPPER_ORE", - "GOLD_ORE", - "DEEPSLATE_GOLD_ORE", - "DIAMOND_ORE", - "DEEPSLATE_DIAMOND_ORE", - "EMERALD_ORE", - "DEEPSLATE_EMERALD_ORE", - "LAPIS_ORE", - "DEEPSLATE_LAPIS_ORE", - "REDSTONE_ORE", - "DEEPSLATE_REDSTONE_ORE", - "NETHER_GOLD_ORE", - "NETHER_QUARTZ_ORE", - "ANCIENT_DEBRIS" - }; - for (String ore : ores) { - if (!excludes.contains(ore)) { - types.add(ore); - } - } - return types; - } - // 单个矿物名 - if (text.contains("煤炭")) - types.add("COAL"); - if (text.contains("铁矿") - || text.contains("铁锭")) - types.add("RAW_IRON"); - if (text.contains("金矿") - || text.contains("金锭")) - types.add("RAW_GOLD"); - if (text.contains("钻石")) - types.add("DIAMOND"); - if (text.contains("绿宝石")) - types.add("EMERALD"); - if (text.contains("青金石")) - types.add("LAPIS_LAZULI"); - if (text.contains("红石")) - types.add("REDSTONE"); - if (text.contains("铜")) - types.add("RAW_COPPER"); - if (text.contains("下界石英") - || text.contains("石英")) - types.add("QUARTZ"); - return types; - } - - - /** - * 从条件文本中提取目标群系分类 - * "打卡上报地狱任意群系" → "下界" - * "打卡任意海洋群系" → "海洋" - * "打卡任意群系" → "ALL" - * "打卡任意风景点" → "" - * "打卡村庄" → "村庄" - * "打卡沙漠神殿" → "沙漠" - */ - private String extractBiome(String text) { - if (text == null) return ""; - - // 优先匹配结构名称(更具体) - String structureResult = - mapStructureCategory(text); - if (!structureResult.isEmpty()) { - return structureResult; - } - - // 逐个匹配分类关键词 - for (Map.Entry en - : CATEGORY_KEYWORDS.entrySet()) { - if (text.contains(en.getKey())) { - return en.getValue(); - } - } - if (text.contains("风景点") - || text.contains("风景")) { - return "ALL"; - } - - // 有"群系"但无具体分类 → ALL - if (text.contains("群系")) { - return "ALL"; - } - return ""; - } - - - // ========== 标记条件 ========== - - public void markConditionComplete( - String player, - String conditionName) { - if (player == null - || conditionName == null) - return; - storage.setCondition( - player, conditionName); - } - - // ========== 事件回调 ========== - - public void onBlockMined( - String player, String material) { - String key = "MINE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - // 总计数 - setCounter(player, "MINE:ALL", - getCounter(player, "MINE:ALL") - + 1); - } - public boolean onReportVillage( - String player, - String worldName, - double x, double y, double z) { - String dbKey = "village_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - Object lastObj = - storage.getCondition( - player, dbKey); - String lastPos = lastObj != null - ? lastObj.toString() : ""; - if (curPos.equals(lastPos)) { - return false; - } - storage.setConditionRaw( - player, dbKey, curPos); - - String key = "REPORT_VILLAGE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - return true; - } - - public void onMobKill( - String player, String mobType) { - String key = "KILL:" + mobType; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayerKill( - String player) { - String key = "KILL_PLAYER"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onItemCrafted( - String player) { - String key = "CRAFT"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onFishCaught( - String player) { - String key = "FISH"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayTime( - String player, long ms) { - String key = "PLAYTIME"; - long cur = getCounterLong( - player, key); - setCounter(player, key, cur + ms); - } - - // ========== 内部:条件判定 ========== - - private boolean isStageComplete( - String player, - List conditions) { - if (conditions == null - || conditions.isEmpty()) - return true; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) - return false; - } - return true; - } - - public boolean isConditionMet( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return false; - - String key = stripOptional(rawCond); - - // 无条件类文本直接通过 - if (key.equals("无前级条件") - || key.equals("无条件") - || key.equals("无") - || key.equals("none") - || key.equals("无需条件") - || key.equals("无解锁条件")) { - return true; - } - - if (key.contains("打卡") - && (key.contains("群系") - || key.contains("风景点") - || key.contains("结构") - || key.contains("村庄") - || key.contains("神殿") - || key.contains("神庙") - || key.contains("小屋") - || key.contains("雪屋") - || key.contains("府邸") - || key.contains("前哨站") - || key.contains("矿井") - || key.contains("要塞") - || key.contains("城市") - || key.contains("废墟") - || key.contains("密室") - || key.contains("废墟") - || key.contains("沉船") - || key.contains("宝箱") - || key.contains("传送门") - || key.contains("化石") - || key.contains("堡垒"))) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return total >= 1; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return catCount >= 1; - } - return getCounter( - player, "biome_ALL") >= 1; - } - - if (key.contains("完成") - && key.contains("阶段")) { - return checkStageDependency( - player, key); - } - - if ("完成注册登录".equals(key)) { - return getDb() != null - && getDb().userExists( - player); - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - return val != null - && !val.toString().isEmpty(); - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return cur >= needed * 3600000L; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - return getCounter( - player, "CHAT") >= 1; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return total >= needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return streak >= needed; - } - - if (key.startsWith("MINE:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "MINE:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "KILL:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "VILLAGER_TRADE") - >= needed; - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter(player, - "KILL_PLAYER") >= n; - } - - if (key.startsWith("CRAFT")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "CRAFT") >= n; - } - - if (key.startsWith("FISH")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "FISH") >= n; - } - - if (key.startsWith("PLAYTIME")) { - String[] p = key.split(":"); - long n = p.length >= 2 - ? Long.parseLong(p[1]) : 0; - return getCounterLong(player, - "PLAYTIME") >= n; - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "REPORT_VILLAGE") - >= needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - return getCounter( - player, "REPORT_VILLAGE") - >= 1; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return streak >= needed; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - // ===== 泛解析:击杀类中文条件 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - for (String mobType : mobTypes) { - total += getCounter(player, - "KILL:" + mobType); - } - return total >= needed; - } - } - - // ===== 泛解析:村民交易条件 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "TRADE") - >= needed; - } - - // ===== 泛解析:采集/挖矿类条件 ===== - if (key.contains("采集") || key.contains("挖")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - for (String mat : matTypes) { - total += getCounter(player, - "MINE:" + mat); - } - return total >= needed; - } - } - - // ===== 泛解析:合成类条件 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "CRAFT") - >= needed; - } - - // ===== 泛解析:钓鱼类条件 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "FISH") - >= needed; - } - - return false; - } - - public void debugLogin(String player) { - try { - var user = getDb().getUser(player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "=== " + player - + " 数据库全部字段 ==="); - for (var entry - : user.entrySet()) { - plugin.getLogger().info( - "[QuestTracker] " - + entry.getKey() - + " = " - + entry.getValue() - + " (" - + (entry.getValue() - != null - ? entry.getValue() - .getClass() - .getSimpleName() - : "null") - + ")"); - } - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "getUser返回null: " - + player); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "debug异常: " - + e.getMessage()); - } - } - - // ========== 内部:计数器 ========== - - private int getCounter( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - return 0; - } - private int getIntField( - String player, String field) { - try { - Object val = getDb().getField( - player, field); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - if (val != null) { - return Integer.parseInt( - val.toString().trim()); - } - } catch (Exception e) { - // 忽略 - } - return 0; - } - - public void onVillagerTrade(String player) { - String key = "VILLAGER_TRADE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - private long getCounterLong( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).longValue(); - } - return 0; - } - - private void setCounter( - String player, String key, - int value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - private void setCounter( - String player, String key, - long value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - // ========== 工具 ========== - - private boolean toBool(Object val) { - if (val == null) return false; - if (val instanceof Boolean) - return (Boolean) val; - if (val instanceof Number) - return ((Number) val) - .intValue() != 0; - String s = String.valueOf(val); - return "1".equals(s) - || "true" - .equalsIgnoreCase(s); - } - - public boolean isOptional(String cond) { - if (cond == null) return false; - String c = cond; - return c.contains("(进阶)") - || c.contains("(可选)") - || c.contains(",可选)") - || c.contains(",可选)") - || c.contains("【可选】") - || c.contains("【进阶】"); - } - - - private String stripOptional(String cond) { - if (cond == null) return ""; - return cond.replace("(进阶)", "") - .replace("(可选)", "") - .replace(",可选)", "") - .replace(",可选)", "") - .replace("【可选】", "") - .replace("【进阶】", "") - .trim(); - } - - private int parseInt(String s) { - - try { - return Integer.parseInt(s.trim()); - } catch (Exception e) { - return 0; - } - } - private Object getDbField( - String player, String field) { - Main main = (Main) plugin; - return main.getDb().getField( - player, field); - } - - /** - * 从文本中提取第一个数字 - * "在线时间≥1小时" → 1 - * "破坏/放置方块100个" → 100 - */ - private int parseNumberFromText( - String text) { - java.util.regex.Matcher m = - java.util.regex.Pattern - .compile("(\\d+)") - .matcher(text); - if (m.find()) { - try { - return Integer.parseInt( - m.group(1)); - } catch (Exception e) { - return 0; - } - } - return 0; - } - public void onBlockPlaced( - String player, String material) { - String key = "PLACE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - setCounter(player, "PLACE:ALL", - getCounter(player, "PLACE:ALL") - + 1); - } - - public void onPlayerChat( - String player) { - String key = "CHAT"; - setCounter(player, key, - getCounter(player, key) + 1); - } - - private String formatMs(long ms) { - long sec = ms / 1000; - long min = sec / 60; - long hr = min / 60; - if (hr > 0) - return hr + "小时" - + (min % 60) + "分"; - if (min > 0) - return min + "分" - + (sec % 60) + "秒"; - return sec + "秒"; - } - - // ========== 数据结构 ========== - - public static class QuestFile { - public String displayName = ""; - public String fileName = ""; - public List details = - new ArrayList<>(); - public List rewards = - new ArrayList<>(); - public List conditions = - new ArrayList<>(); - - public boolean hasConditions() { - return conditions != null - && !conditions.isEmpty(); - } - } -} diff --git a/backup/README.md b/backup/README.md deleted file mode 100644 index e74ef6a..0000000 --- a/backup/README.md +++ /dev/null @@ -1,569 +0,0 @@ -# Sdf1系列登录插件使用说明 - -# 特别说明:本插件仅有简中语言版本,如需除简中以外的任何版本,请下载插件源码,自行修改! - -## 1. 功能介绍 - -| 名字 | 描述/作用 | 施工状态 | -| :--- | :--- | :--- | -| 登录 | 用户登录/注册 | 已完成 | -| 世界垃圾箱 | 清理服务器凋落物 | 已完成 | -| 工单系统 | 用户提交工单 | 已完成 | -| 签到系统 | 签到积分换奖励 | 已完成 | -| 新人礼包 | 新玩家7日礼包 | 已完成 | -| 挂机检测 | 挂机检测踢出 | 已完成 | -| 自定义菜单 | 可以自己diy指令 | 已完成 | -| 自定义菜单图标 | 可以用自己喜欢的物品当图标 | 已完工 | -| 商城系统| 内置游戏商人可供销售商品 | 已完工 | -| 虚拟货币 | 供签到、游戏商人使用的货币系统 | 已完工 | - - -## 2.安装说明: - -| 前置插件 | 选装类型 | 功能作用 | -| :--- | :--- | :--- | -| [CY_beibao](https://gitee.com/nihaoshidifu/CY_beibao) | 可选,推荐 | 云背包,积分商城可购买背包容量 -| EssentialsX | 可选 | 主流经济系统 -| [sdf1](https://gitee.com/nihaoshidifu/sdf1_plugn) | 可选,推荐 | CDK兑换系统 - - -### 2.1 下载 -您可以从GitHub和Gitee下载到插件,将其丢入服务器plugin目录,重启服务器即可。 - -### 2.2. 配置文件 - -#### 2.2.1 消息.txt -此文件控制着整个插件的所有提示消息,您可以根据自己的需求修改,修改后需要执行 /sdf1_login reload 重载插件。支持[颜色代码](https://zh.minecraft.wiki/w/%E6%A0%BC%E5%BC%8F%E5%8C%96%E4%BB%A3%E7%A0%81)
-同时兼容` `标签,对段落加粗,`\n 和
`进行段落换行 - - -```txt -# ========================================== -# Sdf1_login 消息配置文件 -# ========================================== -# 颜色代码使用 § 符号 -# §0黑色 §1深蓝 §2深绿 §3深青 -# §4深红 §5深紫 §6金色 §7灰色 -# §8深灰 §9蓝色 §a绿色 §b浅蓝 -# §c红色 §d粉色 §e黄色 §f白色 -# §l粗体 §n下划线 §o斜体 §r重置 -# -# 占位符用 {xxx} 表示,运行时自动替换 -# 支持 粗体 标签 -# 支持 \n 或
换行 -# 修改后执行 /sdf1_login reload 重载 -# ========================================== - -# ---- 登录注册相关 ---- -# login_timeout: 登录超时被踢出时的提示 -login_timeout=§c登录超时,请重新登录 -# not_registered: 未注册玩家收到的提示 -not_registered=§e您尚未注册,请使用 /reg <密码> -# not_logged_in: 已注册但未登录的提示 -not_logged_in=§e您尚未登录,请使用 /login <密码> -# already_registered: 已注册玩家再次注册时的提示 -already_registered=§c您已注册过账号 -# already_logged_in: 已登录玩家再次登录时的提示 -already_logged_in=§c您已登录 -# reg_success: 注册成功提示 -reg_success=§a注册成功!欢迎来到服务器 -# login_success: 登录成功提示 -login_success=§a登录成功! -# login_failed: 密码错误提示 -login_failed=§c密码错误,请重试 - -# ---- 密码相关 ---- -# password_wrong: 旧密码验证失败 -password_wrong=§c密码错误 -# password_changed: 密码修改成功 -password_changed=§a密码修改成功! -# password_format_error: 密码格式不符合要求 -password_format_error=§c密码需6位以上,含大小写字母和数字 -# password_same: 新密码与旧密码相同 -password_same=§c新密码不能与旧密码相同 -# reset_no_email: 找回密码时未绑定邮箱 -reset_no_email=§c您未绑定邮箱,无法找回密码 -# reset_sent: 临时密码已发送到邮箱 -reset_sent=§a临时密码已发送到您的邮箱,请查收 - -# ---- 签到积分相关 ---- -# checkin_already: 今日已签到 -checkin_already=§c您今日已签到 -# checkin_success: 签到成功,{points}=获得积分 {streak}=连续天数 {multi}=倍率 -checkin_success=§a签到成功!获得 {points} 积分,连续 {streak} 天,倍率 x{multi} -# points_insufficient: 积分不足 -points_insufficient=§c积分不足,当前: {points} -# points_purchase_success: 积分购买成功 -points_purchase_success=§a购买成功!消耗 {count} 积分 - -# ---- 挂机检测相关 ---- -# afk_set_enabled: 挂机检测开启 -afk_set_enabled=§a挂机检测已开启 -# afk_set_disabled: 挂机检测关闭 -afk_set_disabled=§c挂机检测已关闭 -# afk_kick: 因挂机被踢出 -afk_kick=§c因挂机过久被踢出 - -# ---- 邮箱相关 ---- -# email_set: 邮箱设置成功 -email_set=§a邮箱设置成功:{email} -# email_format_error: 邮箱格式错误 -email_format_error=§c邮箱格式不正确 - -# ---- 邀请相关 ---- -# invite_code_generated: 邀请码生成 -invite_code_generated=§a邀请码:{code} -# invite_used: 使用邀请码成功 -invite_used=§a邀请成功,邀请人:{player} -# invite_self: 使用自己的邀请码 -invite_self=§c不能使用自己的邀请码 -# invite_not_found: 邀请码不存在 -invite_not_found=§c邀请码不存在 -# invite_referral_bonus: 被邀请人签到后邀请人获得奖励 -invite_referral_bonus=§a邀请奖励!获得 {points} 积分 - -# ---- 新人礼包相关 ---- -# gift_not_ready: 礼包条件未满足 -gift_not_ready=§c礼包条件未满足 -# gift_claimed: 礼包领取成功 -gift_claimed=§a礼包领取成功! - -# ---- 审批与IP限制 ---- -# need_approval: 注册需审批 -need_approval=§e注册需审批 (工单#{id}) -# ip_max_accounts: IP注册已达上限 -ip_max_accounts=§cIP注册已达上限 - -# ---- 管理员操作 ---- -# admin_delete_confirm: 管理员删除账号确认提示 -admin_delete_confirm=§c请在聊天中输入玩家名确认删除 - -# ---- 聊天过滤 ---- -# chat_muted: 禁言玩家尝试聊天 -chat_muted=§c§l[Sdf1_chat] §f你已被禁言,无法聊天 -# chat_url_blocked: 发送违规链接被拦截 -# {url}=检测到的链接地址 -chat_url_blocked=§c§l[Sdf1_chat] §f检测到违规第三方链接: §e{url} -# chat_url_violation: 当前违规次数 -# {count}=当前违规次数 -chat_url_violation=§7当前违规次数: §e{count} -# chat_url_admin_notify: 通知管理员,{player}=玩家名 {url}=链接 {count}=次数 -chat_url_admin_notify=§e§l[Sdf1_chat] §f{player} 发送违规链接: {url} (第{count}次) -# chat_url_broadcast: 全服通报,{player}=玩家名 -chat_url_broadcast=§c§l[Sdf1_chat] §e{player} §f因发送违规链接被处罚 - -# ---- 通用 ---- -# 未知参数: 未知命令或权限不足时的提示 -未知参数=§c未知参数或权限不足 -# only_player: 仅玩家可用的命令 -# only_player=§c此命令仅玩家可用 - -``` - -### 2.2.2 挂机白名单.txt -当挂机踢出开启时,该配置下的玩家不受挂机提出影响 - -### 2.2.3 SMTP设置.txt - -此文件控制着SMTP邮件服务器的配置,用于找回密码时发送邮件。
可通过GUI进行配置,也可以手动修改此配置文件。修改后,重载插件即可生效 - -```txt -# ===== SMTP 邮件服务器配置 ===== -smtp地址=smtp.example.com -smtp端口=465 -smtp账号=mail@example.com -smtp密码=example.com -发件人名称=Sdf1_login -smtp加密=true -验证码接收邮箱=example@example.com - -``` - -### 2.2.4 插件设置.txt - -此配置管控这整个插件,包括玩家能注册多少个账号、默认的管理员面板密码多少、tag包含什么视为管理员等 - -```txt -# ===== Sdf1_login 插件设置 ===== -管理标签=admin -管理密码=qweasd -每IP最大账号数=3 -审批模式=manual -自动审批延迟分钟=30 -登录超时秒数=60 -挂机踢出=false -挂机超时秒数=300 -服务商积分倍率=2.0 -基础经济奖励=100 -经济奖励每评分=50.0 -报单人积分奖励=10 -# ---- 签到盲盒奖励 ---- -签到固定金额=0 -签到最小金额=10 -签到最大金额=100 -签到给积分=true -签到积分数量=10 - - -``` - -### 2.2.5 chat.txt -该文件控制着聊天过滤,当玩家发送违规链接时,会自动根据配置规则进行处理。
-可用的处罚规则: -- warn: 警告 -- mute: 禁言 -- kick: 踢出 -- ban: 封禁 -- banip:封禁IP - -```txt -# Sdf1_chat 聊天过滤配置 -通知管理员: true -全服通报: true -禁言时长: 300 - -白名单: -*.minecraft.net -baidu.com -github.com - -处罚规则: -1:warn:0 -2:warn:0 -3:mute:300 -5:mute:600 -8:kick:0 -10:ban:0 - -白名单玩家: - -``` -### 2.2.6 radio_config.txt -```txt -# ============ Sdf1 Radio Config ============ -# -# resource-pack-url -# 填外部地址(HTTP或HTTPS均可) -# 例: https://example.com/radiopack.zip -# 留空 = 使用内置HTTP服务 -# -# http-port -# 内置HTTP端口(仅resource-pack-url为空时生效) -# -# ============ Settings =============== - -resource-pack-url=https://*.ypshidifu.cn/|*.mcserver.ccwu.cc -http-port=443 -``` -- radio_config这里可以使用通配符域名,当使用通配符时,系统随机生成url,拒绝过资源包的玩家,每次都会收到提示。并且自1.2版本起,拒绝资源包=放弃游玩服务器。如果您无需广播功能,可以不放ogg文件进入`./plugin/radio/ogg`。这样服务端不会自动生成资源包,也不会强制下载了 - -### 2.2.7 菜单.txt -- 自定义菜单配置文件 - -```json -# 服务器菜单 -# 使用类json格式 -// 支持使用://Java单行注释、#yml单行注释、/* */块注释,也支持的块注释 - -{ - 标题:主城传送 - 指令:/dom tp 主城区 # 这里如果是xxx.txt则表示进入二级目录 - 权限类型:玩家 - 图标:COMPASS -} - -{ - 标题:随机传送 - 指令:/rtp - 权限类型:玩家 - 图标:ENDER_PEARL -} - -{ - 标题:擂台比武 - 指令:/dom tp PVP - 权限类型:玩家 - 图标:netherite_spear -} - -{ - 标题:领地操作 - 指令:/dom - 权限类型:玩家 - 图标:paper -} -{ - 标题:逛商店 - 指令:/dom tp shop - 权限类型:玩家 - 图标:honeycomb -} - -{ - 标题:传送开关 - 指令:/tptoggle - 权限类型:玩家 - 图标:redstone_torch -} - -{ - 标题:返回上一个位置 - 指令:/back - 权限类型:玩家 - 图标:red_bed -} -{ - 标题:前往小黑塔 - 指令:/dom tp 小黑塔 - 权限类型:玩家 - 图标:dragon_head -} -{ - 标题:云背包 - 指令:/cy - 权限类型:玩家 - 图标:potato -} -{ - 标题:自杀 - 指令:/suicide - 权限类型:玩家 - 图标:wooden_axe -} -{ - 标题:游戏商人 - 指令:/shop - 权限类型:玩家 - 图标:shield - 格子:28 -} -``` -### 2.2.8 商品配置 -- 配置此处的文件,负责展示游戏内所销售的商品。
-- 一个文件名,对应一个商品分类,使用md文件格式 -``` -# 建筑材料 - -| ID | 品名 | 材质 | 购入价 | 售出价 | 库存 | 本小时销量 | 总销量 | -| --- | --- | --- | --- | --- | --- | --- | --- | -| STONE | 石头 | STONE | 10 | -1 | -1 | 0 | 0 | -| COBBLESTONE | 圆石 | COBBLESTONE | 5 | -1 | -1 | 0 | 26 | -| OAK_PLANKS | 橡木木板 | OAK_PLANKS | 8 | 3 | -1 | 0 | 0 | -| BRICKS | 砖块 | BRICKS | 12 | -1 | -1 | 0 | 0 | -| GLASS | 玻璃 | GLASS | 15 | 6 | -1 | 0 | 0 | -| QUARTZ_BLOCK | 石英块 | QUARTZ_BLOCK | 20 | 8 | -1 | 0 | 0 | -| AMETHYST_BLOCK | 紫水晶 | AMETHYST_BLOCK | 10 | 0 | -1 | 0 | 999 | -| OCHRE_FROGLIGHT | 赭黄蛙鸣灯 | OCHRE_FROGLIGHT | 100 | 0 | -1 | 0 | 1000 | - -``` - -## 2.3 商城部分介绍 -1. 自助退款,买多买错,不用慌,一件退单很安心
-2. 购物车,买的多怕买错,先加购物车,后续统一结算
-3. 害怕订单找不到?一键查看历史订单
- ![](img/shop4.png) -4. 玩家要耍赖皮,拿着小票来找麻烦怎么办?退款订单自动贴标,拒绝白嫖 -![](img/shop7.png)
-![](img/shop6.png) -5. 想查看玩家消费记录?小手一点,明明白白
-![](img/shop5.png) - - - - -## 2.4 打印流水部分介绍 -1. 管理员怀疑某个玩家刷钱了,怎么办?/printer 玩家名,一检查流水,很安心。很多人都刷了怎么办?/printer不加参数,一键导出全服流水,查他个底朝天
-2. 忘记删除怎么办?插件每分钟自动检测,旧文件超过24小时,自动删 - - - -## 3. 指令参数及介绍 - -- Sdf1_login 主指令
-- Sdf1_login reload 重载配置文件
-- reg 注册账号
-- login 登录账号
-- l 登录账号
-- sdf1_login del 删除玩家账号
-- sdf1_login radio 停止播放广播
-- sdf1_login radio reload 重载广播并重新打包资源包
-- menu 开/关 是否开启菜单
-- printer 打印流水数据,支持不带参数&带玩家名为参数
-- sdf1_login printer 作用同printer
-- sdf1_login shop 商城 -- protect 区域防护 - - - -## 4. 自定义菜单物品 -- 打开主界面,选择"我的"进入
-![](img/menu1.png) -- 将你的自定义物品与雪球交换
-![](img/menu2.png) -- 随后系统会给你重新发放菜单,并且此时图标就是你的自定义物品了 - -## 5. pvp战神榜 -- 通过此功能,你可以在服务器里制作大服务器同款的击杀排行榜,而且能精准定位指定区域
-![](img/pvp1.png) -- 并且有着网游同款的击杀通报
-![](img/pvp3.png) -- 播报逻辑是:
-- 3杀
-- 5杀
-- 5杀以上每杀一次都是一条播报
-- 终结者通报,触发连杀的玩家被终结也会有全服通报 - -## 6. 区域防护 -- 通过此功能,您可在服务器里需要防护的地方(比如展示框不想被玩家随便破坏、主城不准玩家袭击等)加以防护。
-- 还能够保护指定区域的玩家不受任何伤害。
- -| 指令 | 参数1| 参数2 | 参数3 | 结果 | -| :---: | :---: | :---: | :---: | :---: | -| protect | 无 | 无 | 无 | 帮助信息 | -| protect | add | 区域名 | 玩家名 | 加白指定玩家到区域 | -| protect | add | 玩家名 | 无| 加白指定玩家为全局 | -| protect | additem | 区域名 | 物品名 | 加黑指定物品到区域 | -| protect | additem | 物品名 | 无 | 加黑指定物品到全局 | -| protect | addname | 区域名 | 名字 | 加白指定生物名字到区域和平模式白名单 | -| protect | addplayer | 区域名 | 玩家名字 | 加白指定玩家到区域强制模式豁免名单 | -| protect | remove | 玩家名字 | 无 | 移除玩家为全白 | -| protect | remove | 区域名 | 玩家名 | 移除玩家为区白 | -| protect | removeitem | 物品名 | 无 | 删除指定物品为全黑 -| protect | removeitem | 区域名 | 物品名 | 删除指定物品为区黑 -| protect | removename | 区域名 | 名字 | 删除指定名字为区域和平模式白名单 | -| protect | removeplayer | 区域名 | 名字 | 删除指定玩家为强制模式豁免名单 | -| protect | 参数不分顺序 | 区名可以写这里也可以写后面 | 玩家名可以写这里也可以写前面 | 完美的宝宝化 | - -示范: -- `/protect add 末地保护区 players` 添加players玩家到末地保护区
-- `/protect removeitem 主城区 TNT` 从主城删了TNT这个黑名单物品
-- `/protect 筑城区 addwhite zhangsan` 把zhangsan加白到主城区的模式豁免名单。
-- 所有参数`任意排序`,`区域名字`随便填写,强大的`兜底`会自动识别"宝宝画板",你可以放心、大胆的乱写 - - -| 参数 | 效果 | -| :---: | :---: | -| 和平模式 | 所有敌对生物全部进虚空 | -| 禁止袭击 | 立即清理玩家身上的袭击效果,传灾厄村民进虚空 | -| 清除XX效果 | 立即清除玩家身上的效果 -| 给与xx效果 | 根据配置文件,给予玩家指定等级、时长的效果 - -## 生物属性对照表 -| 中文名称 | 类型 | 附加属性 | -| :---: | :----: | :----: | -| 溺尸 | 近战/远程 | 亡灵、水生 | -| 骷髅 | 远程 | 亡灵 | -| 流浪者 | 远程 | 亡灵、寒冷群系变种 | -| 沼骸 | 远程 | 亡灵、沼泽变种 | -| 焦骸 | 远程 | 亡灵、沙漠变种、免疫阳光 | -| 掠夺者 | 远程 | 灾厄村民、袭击成员 | -| 卫道士 | 近战 | 灾厄村民、袭击成员 | -| 幻术师 | 远程 | 灾厄村民、Java专属、隐身分身 | -| 旋风人 | 远程 | 试炼密室生成、免疫弹射物、强击退 | -| 监守者 | 近战/声波远程 | 深暗之域、附带盲视效果 | -| 远古守卫者 | 近战/激光远程 | 海底神殿、水下、附加挖掘疲劳 | -| 守卫者 | 近战/激光远程 | 海底神殿、水下 | - - -配置文件解析:
-- 支持注释格式:`Java的//单行注释`、`yml的#单行注释`、`HTML的注释`和`Java/* */块注释` -```txt -# 区域: 主城区 -起点: world,-130,-713 -终点: world,-427,-560 -高度范围: 0-255 - -没收物品: 蘑菇,毒蘑菇 #没收哪些物品(末地推荐这两个) - // 禁止放置方块 #保护区内,非区白/全白玩家禁止放置方块 -// 禁止破坏方块 # 同上 -禁止PVP # 玩家之间不能PVP -禁止摔伤 # 摔落无法造成任何伤害 -禁止饥饿 # 不会消耗饱食度 -禁止一切伤害 # 任何怪物都无法造成伤害 -# 禁止丢弃物品 -# 禁止末影珍珠 -# 禁止使用弓箭 -# 禁止骑乘 -// 和平模式 // 所有的敌对生物全部传送进虚空 -禁止袭击 -禁止交互展示框 # 核心,任何玩家不能触碰展示框。区白/全白玩家除外 -// 清除所有效果 # 清理所有负面效果 -效果 村庄英雄 255 999999 # 给予玩家效果。格式为: 效果 效果名字 等级(最高255) 时间 -效果 生命提升 255 999999 -效果 抗性提升 255 999999 -效果 抗火 255 999999 -# 禁止药水 -禁止爆炸 -禁止使用物品: TNT -# 效果: 夜视 1 999 -进入提示: 欢迎来到保护区 # 是否开启进入提示,即自定义提示 -离开提示: 已离开保护区 - 没收提示: 你的违禁品已被没收 - 通报批评: §c§l{player} §f在{area}违规携带违禁品,被没收 §e{count}§f件! # 通报违禁品。{player} 玩家名 {count}数量 {area}区域 -# 惩罚命令: ban {player} 60s 违规 - - -``` - -## 运行截图 - -主界面 -![](img/login.png) - -邀请数据 -![](img/login2.png) - -积分商店 -![](img/login3.png) - -任务中心 -![](img/login4.png) - -垃圾箱 -![](img/login5.png) - -管理员面板 -![](img/login6.png) - -管理面板-用户管理 -![](img/login7.png) - - -工单中心 -![](img/login8.png) - -工单管理 -![](img/login9.png) - -我的工单 -![](img/login10.png) - - -提交工单 -![](img/login11.png) - -服务商列表 -![](img/login12.png) - -服务商抢单大厅 -![](img/login13.png) - -服务商-接单界面 -![](img/login14.png) - -管理员-工单详情页面 -![](img/login15.png) - -商城主界面 -![](img/shop1.png) - -商城购物车 -![](img/cart1.png) - -商城退单 -![](img/shop2.png) - - -## 4. 下载地址 -- GitHub:[https://github.com/ypsdf1/sdf1_login](https://github.com/ypsdf1/sdf1_login) -- Gitee:[https://gitee.com/nihaoshidifu/sdf1_login](https://gitee.com/nihaoshidifu/sdf1_login) diff --git a/backup/img/cart1.png b/backup/img/cart1.png deleted file mode 100644 index ff9a1c6..0000000 Binary files a/backup/img/cart1.png and /dev/null differ diff --git a/backup/img/login.png b/backup/img/login.png deleted file mode 100644 index b9433b3..0000000 Binary files a/backup/img/login.png and /dev/null differ diff --git a/backup/img/login1.png b/backup/img/login1.png deleted file mode 100644 index 4f54df9..0000000 Binary files a/backup/img/login1.png and /dev/null differ diff --git a/backup/img/login10.png b/backup/img/login10.png deleted file mode 100644 index 6b083c2..0000000 Binary files a/backup/img/login10.png and /dev/null differ diff --git a/backup/img/login11.png b/backup/img/login11.png deleted file mode 100644 index c1220c1..0000000 Binary files a/backup/img/login11.png and /dev/null differ diff --git a/backup/img/login12.png b/backup/img/login12.png deleted file mode 100644 index b561550..0000000 Binary files a/backup/img/login12.png and /dev/null differ diff --git a/backup/img/login13.png b/backup/img/login13.png deleted file mode 100644 index 1255d6b..0000000 Binary files a/backup/img/login13.png and /dev/null differ diff --git a/backup/img/login14.png b/backup/img/login14.png deleted file mode 100644 index 7b4f091..0000000 Binary files a/backup/img/login14.png and /dev/null differ diff --git a/backup/img/login15.png b/backup/img/login15.png deleted file mode 100644 index a3d00a4..0000000 Binary files a/backup/img/login15.png and /dev/null differ diff --git a/backup/img/login2.png b/backup/img/login2.png deleted file mode 100644 index 03dc32c..0000000 Binary files a/backup/img/login2.png and /dev/null differ diff --git a/backup/img/login3.png b/backup/img/login3.png deleted file mode 100644 index b12a88b..0000000 Binary files a/backup/img/login3.png and /dev/null differ diff --git a/backup/img/login4.png b/backup/img/login4.png deleted file mode 100644 index ed32c55..0000000 Binary files a/backup/img/login4.png and /dev/null differ diff --git a/backup/img/login5.png b/backup/img/login5.png deleted file mode 100644 index fa8fd67..0000000 Binary files a/backup/img/login5.png and /dev/null differ diff --git a/backup/img/login6.png b/backup/img/login6.png deleted file mode 100644 index 956d0bb..0000000 Binary files a/backup/img/login6.png and /dev/null differ diff --git a/backup/img/login7.png b/backup/img/login7.png deleted file mode 100644 index 15fc70e..0000000 Binary files a/backup/img/login7.png and /dev/null differ diff --git a/backup/img/login8.png b/backup/img/login8.png deleted file mode 100644 index 57189c2..0000000 Binary files a/backup/img/login8.png and /dev/null differ diff --git a/backup/img/login9.png b/backup/img/login9.png deleted file mode 100644 index fbd43f7..0000000 Binary files a/backup/img/login9.png and /dev/null differ diff --git a/backup/img/menu1.png b/backup/img/menu1.png deleted file mode 100644 index b056be9..0000000 Binary files a/backup/img/menu1.png and /dev/null differ diff --git a/backup/img/menu2.png b/backup/img/menu2.png deleted file mode 100644 index 888cbf2..0000000 Binary files a/backup/img/menu2.png and /dev/null differ diff --git a/backup/img/pvp1.png b/backup/img/pvp1.png deleted file mode 100644 index 9efe88b..0000000 Binary files a/backup/img/pvp1.png and /dev/null differ diff --git a/backup/img/pvp3.png b/backup/img/pvp3.png deleted file mode 100644 index 3b56cc9..0000000 Binary files a/backup/img/pvp3.png and /dev/null differ diff --git a/backup/img/shop1.png b/backup/img/shop1.png deleted file mode 100644 index eb0ea8e..0000000 Binary files a/backup/img/shop1.png and /dev/null differ diff --git a/backup/img/shop2.png b/backup/img/shop2.png deleted file mode 100644 index a083df0..0000000 Binary files a/backup/img/shop2.png and /dev/null differ diff --git a/backup/img/shop4.png b/backup/img/shop4.png deleted file mode 100644 index fe59548..0000000 Binary files a/backup/img/shop4.png and /dev/null differ diff --git a/backup/img/shop5.png b/backup/img/shop5.png deleted file mode 100644 index 9511132..0000000 Binary files a/backup/img/shop5.png and /dev/null differ diff --git a/backup/img/shop6.png b/backup/img/shop6.png deleted file mode 100644 index e69fedd..0000000 Binary files a/backup/img/shop6.png and /dev/null differ diff --git a/backup/img/shop7.png b/backup/img/shop7.png deleted file mode 100644 index 30a9e8d..0000000 Binary files a/backup/img/shop7.png and /dev/null differ diff --git a/backup/pom.xml b/backup/pom.xml deleted file mode 100644 index 9d5c185..0000000 --- a/backup/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - sdf1 - Sdf1_login - 1.0 - jar - - - 21 - UTF-8 - - - - - papermc - https://repo.papermc.io/repository/maven-public/ - - - jitpack.io - https://jitpack.io - - - - - - io.papermc.paper - paper-api - 1.21.4-R0.1-SNAPSHOT - provided - - - - com.github.MilkBowl - VaultAPI - 1.7.1 - provided - - - org.xerial - sqlite-jdbc - 3.46.0.0 - provided - - - - - - Sdf1_login - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 21 - 21 - - - - - diff --git a/backup/src/main/java/Sdf1_login/AFKManager.java b/backup/src/main/java/Sdf1_login/AFKManager.java deleted file mode 100644 index f714c1b..0000000 --- a/backup/src/main/java/Sdf1_login/AFKManager.java +++ /dev/null @@ -1,197 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; - -public class AFKManager { - - // 玩家最后活动时间(空闲才开始计时) - private final Map lastAction = - new HashMap<>(); - // 每个玩家当前的警告阶段 - private final Map lastStage = - new HashMap<>(); - // 是否已进入挂机检测(false=还在活动,不计时) - private final Map afkTracking = - new HashMap<>(); - - private final ConfigManager config; - private final JavaPlugin plugin; - - public AFKManager(ConfigManager config, - JavaPlugin plugin) { - this.config = config; - this.plugin = plugin; - } - - public void startCheck() { - new BukkitRunnable() { - public void run() { - tick(); - } - }.runTaskTimer(plugin, 20L, 20L); - } - - private void tick() { - if (!config.afkEnabled) return; - long now = System.currentTimeMillis(); - - for (Player p : Bukkit.getOnlinePlayers()) { - String name = p.getName(); - UUID uuid = p.getUniqueId(); - - if (!isLoggedIn(name)) continue; - if (config.getAfkWhitelist().contains(name)) - continue; - - // 确保有记录 - if (!lastAction.containsKey(uuid)) { - lastAction.put(uuid, now); - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - continue; - } - - long last = lastAction.get(uuid); - long idle = (now - last) / 1000; - int timeout = config.afkTimeout; - - // 还在活动范围内:不进入挂机检测 - if (idle < 60) { - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - continue; - } - - // 超过60秒无活动:开始挂机检测 - afkTracking.put(uuid, true); - long remain = timeout - idle; - - // 已超时:踢出 - if (remain <= 0) { - p.kickPlayer("§c§l你已挂机 " - + fmtSec(idle) + ",被踢出"); - lastAction.remove(uuid); - lastStage.remove(uuid); - afkTracking.remove(uuid); - continue; - } - - int stage = lastStage.getOrDefault(uuid, 0); - - // 阶段3:最后10秒 - if (remain <= 10) { - if (stage < 3) { - lastStage.put(uuid, 3); - } - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + remain + " 秒踢出", - 0, 22, 3); - playSound(p); - continue; - } - - // 阶段2:1分钟~10秒 - if (remain <= 60) { - if (stage < 2) { - lastStage.put(uuid, 2); - p.sendMessage( - "§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" - + fmtSec(idle) - + "§f,还剩 §c" - + fmtSec(remain) - + "§f踢出"); - } - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + fmtSec(remain) + " 踢出", - 5, 25, 5); - playSound(p); - continue; - } - - // 阶段1:3分钟提醒 - if (remain <= 180 && stage < 1) { - lastStage.put(uuid, 1); - p.sendMessage( - "§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" - + fmtSec(idle) - + "§f,还剩 §c" - + fmtSec(remain) - + "§f踢出"); - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§f还剩 " + fmtSec(remain) + " 踢出", - 10, 60, 20); - for (int i = 0; i < 3; i++) { - final int delay = i * 10; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - if (p.isOnline()) playSound(p); - }, delay); - } - } - - if (remain > 180) { - lastStage.remove(uuid); - } - } - } - - /** - * 玩家有活动时调用(移动/交互/放置/挖掘等) - * 重置计时,退出挂机检测 - */ - public void recordAction(UUID uuid) { - lastAction.put(uuid, System.currentTimeMillis()); - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - } - - /** - * 玩家下线时调用 - * 彻底停止计时,清理所有数据 - */ - public void remove(UUID uuid) { - lastAction.remove(uuid); - lastStage.remove(uuid); - afkTracking.remove(uuid); - } - - /** - * 获取玩家当前是否在挂机检测中 - */ - public boolean isAfkTracking(UUID uuid) { - return afkTracking.getOrDefault(uuid, false); - } - - private boolean isLoggedIn(String name) { - return ((Main) plugin).getLoggedIn() - .contains(name); - } - - private void playSound(Player p) { - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_CHIME, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_BASS, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_PLING, - 1.0f, 0.79f); - } - - private String fmtSec(long sec) { - if (sec < 60) return sec + "秒"; - long m = sec / 60; - long s = sec % 60; - return m + "分" + (s > 0 ? s + "秒" : ""); - } -} diff --git a/backup/src/main/java/Sdf1_login/AccountRequestManager.java b/backup/src/main/java/Sdf1_login/AccountRequestManager.java deleted file mode 100644 index ae5eaad..0000000 --- a/backup/src/main/java/Sdf1_login/AccountRequestManager.java +++ /dev/null @@ -1,146 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class AccountRequestManager { - - public enum ApprovalMode { AUTO, MANUAL, HYBRID } - - public static class Request { - public final int id; - public final String requester; - public final String target; - public final String ip; - public final long time; - public String status; - public String denyReason; - - public Request(int id, String requester, String target, String ip) { - this.id = id; - this.requester = requester; - this.target = target; - this.ip = ip; - this.time = System.currentTimeMillis(); - this.status = "PENDING"; - this.denyReason = ""; - } - } - - private final Main plugin; - private final Map requests = new ConcurrentHashMap<>(); - private final Map pendingByPlayer = new ConcurrentHashMap<>(); - private int nextId = 1; - private ApprovalMode mode = ApprovalMode.MANUAL; - private int autoDelayMinutes = 60; - - public AccountRequestManager(Main plugin) { - this.plugin = plugin; - } - - public ApprovalMode getMode() { return mode; } - - public void setMode(String m) { - try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); } - catch (Exception e) { this.mode = ApprovalMode.MANUAL; } - } - - public void setAutoDelayMinutes(int min) { - this.autoDelayMinutes = min; - } - - public Request createRequest(String requester, String target, String ip) { - int id = nextId++; - Request req = new Request(id, requester, target, ip); - requests.put(id, req); - pendingByPlayer.put(requester, id); - - if (mode == ApprovalMode.AUTO) { - scheduleAutoApprove(id); - } else if (mode == ApprovalMode.HYBRID) { - String targetIp = (String) plugin.getDb().getField(target, "ip_address"); - if (ip != null && ip.equals(targetIp)) { - scheduleAutoApprove(id); - } - } - return req; - } - - private void scheduleAutoApprove(int id) { - long delayTicks = (long) autoDelayMinutes * 60L * 20L; - Request req = requests.get(id); - if (req == null) return; - String requesterName = req.requester; - String targetName = req.target; - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过"); - - Bukkit.getScheduler().runTaskLater(plugin, () -> { - Request r = requests.get(id); - if (r != null && "PENDING".equals(r.status)) { - approveRequest(id); - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过"); - Player requester = Bukkit.getPlayer(requesterName); - if (requester != null && requester.isOnline()) { - requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!"); - } - } - }, delayTicks); - } - - public boolean approveRequest(int id) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "APPROVED"; - plugin.getDb().deleteUser(req.target); - pendingByPlayer.remove(req.requester); - // 清背包+传送出生点+踢出 - Player target = Bukkit.getPlayer(req.target); - if (target != null && target.isOnline()) { - target.getInventory().clear(); - target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]); - target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]); - target.teleport(target.getWorld().getSpawnLocation()); - target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册"); - } - return true; - } - - - public boolean denyRequest(int id, String reason) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "DENIED"; - req.denyReason = reason; - pendingByPlayer.remove(req.requester); - return true; - } - - public List getPendingRequests() { - List list = new ArrayList<>(); - for (Request r : requests.values()) { - if ("PENDING".equals(r.status)) list.add(r); - } - return list; - } - - public Request getRequest(int id) { - return requests.get(id); - } - - public boolean hasPending(String player) { - return pendingByPlayer.containsKey(player); - } - - public void cancelPending(String player) { - Integer id = pendingByPlayer.remove(player); - if (id != null) { - Request r = requests.get(id); - if (r != null) r.status = "CANCELLED"; - } - } -} diff --git a/backup/src/main/java/Sdf1_login/AreaProtection.java b/backup/src/main/java/Sdf1_login/AreaProtection.java deleted file mode 100644 index bc53d53..0000000 --- a/backup/src/main/java/Sdf1_login/AreaProtection.java +++ /dev/null @@ -1,4907 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.event.block.BlockBurnEvent; -import org.bukkit.event.block.BlockIgniteEvent; -import org.bukkit.event.block.BlockSpreadEvent; -import org.bukkit.entity.Entity; -import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import java.sql.*; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.GameMode; -import org.bukkit.event.EventPriority; - - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; -import java.util.Iterator; -import java.util.Set; -import java.util.HashSet; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.event.entity.EntitySpawnEvent; -import org.bukkit.entity.Item; - - - - -public class AreaProtection implements Listener { - - private final Main plugin; - public List giveEffects = new ArrayList<>(); - private final Map> playerAreas - = new HashMap<>(); - private final Map areas = - new ConcurrentHashMap<>(); - private Connection dbConnection; - - // 全局白名单(玩家) - private final Set globalPlayerWhitelist = - ConcurrentHashMap.newKeySet(); - // 区域白名单 key=区域名 value=玩家名集合 - private final Map> areaPlayerWhitelist = - new ConcurrentHashMap<>(); - // 全局黑名单(物品ID) - private final Set globalItemBlacklist = - ConcurrentHashMap.newKeySet(); - // 区域黑名单 - private final Map> areaItemBlacklist = - new ConcurrentHashMap<>(); - - // 选地 - private static final Material WAND = Material.BREEZE_ROD; - - private final Map pos1 = - new ConcurrentHashMap<>(); - private final Map pos2 = - new ConcurrentHashMap<>(); - private final Set selecting = - ConcurrentHashMap.newKeySet(); - public boolean denyRaid = false; - // ===== 统一文件夹 ===== - private final File rootDir; - private final File whitelistDir; - private final File globalWhitelistFile; - private final File oldDbFile; - private final File newDbFile; - - // 边框显示 - private final Map displayTaskIds = - new ConcurrentHashMap<>(); - private final Map> globalWhitelist - = new HashMap<>(); - private final Map> areaWhitelist - = new HashMap<>(); - private final Map protectedEntities - = new ConcurrentHashMap<>(); - // 区域给予的效果记录(贴标) - private final Map> playerAppliedEffects = new HashMap<>(); - // 延时清理任务 - private static final long MOVE_DEBOUNCE_MS = 100; - // 新:全部改为线程安全 - private final Map playerCurrentArea - = new ConcurrentHashMap<>(); - private final Map> playerMarkedEffects - = new ConcurrentHashMap<>(); - private final Map pendingClearTask - = new ConcurrentHashMap<>(); - private final Map pendingClearArea - = new ConcurrentHashMap<>(); - private final Map lastMoveProcess - = new ConcurrentHashMap<>(); - // 防止传送时重复处理 - private final Set teleporting - = ConcurrentHashMap.newKeySet(); - // 待清理的效果列表(与延时任务配合) - private final Map> pendingClearEffects - = new ConcurrentHashMap<>(); - - - -// ==================== 强制游戏模式 ==================== - - /** - * 监听玩家尝试切换游戏模式 - * 如果在强制游戏模式区域且不在豁免名单中,则取消切换 - */ - // 防止递归的标记 - private final Set modeSwitching - = ConcurrentHashMap.newKeySet(); - @EventHandler(priority = EventPriority.HIGHEST) - public void onGameModeChange( - PlayerGameModeChangeEvent event) { - if (event.isCancelled()) return; - - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - if (modeSwitching.contains(uid)) return; - - String areaName = playerCurrentArea.get(uid); - if (areaName == null) return; - - AreaConfig ac = areas.get(areaName); - if (ac == null) return; - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) return; - if (isExemptFromGameMode(p, areaName)) return; - - event.setCancelled(true); - - modeSwitching.add(uid); - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } finally { - modeSwitching.remove(uid); - } - - // ★ 切换后发一次中文提示 - p.sendMessage(formatAreaMsg( - "&c该区域强制 " + ac.enforceGameMode - + " 模式,无法切换")); - } - - public Set getAlreadyForced() { - return alreadyForced; - } - - - // ==================== 定时强制游戏模式 ==================== - - private BukkitTask enforceTask; - - /** - * 启动定时强制任务 - * 每秒检查一次所有在线玩家 - */ - private BukkitTask enforceTask1; - - // 记录上次强制模式的玩家(避免重复发消息) - private final Set alreadyForced - = ConcurrentHashMap.newKeySet(); - - public void startEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - } - enforceTask = new BukkitRunnable() { - @Override - public void run() { - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String areaName = - playerCurrentArea.get(uid); - if (areaName == null) { - // 不在任何区域,清除标记 - alreadyForced.remove(uid); - continue; - } - - AreaConfig ac = areas.get(areaName); - if (ac == null) continue; - if (ac.enforceGameMode == null - || ac.enforceGameMode - .isEmpty()) continue; - if (isExemptFromGameMode( - p, areaName)) { - alreadyForced.remove(uid); - continue; - } - - GameMode target = - GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - modeSwitching.add(uid); - try { - p.setGameMode(target); - } finally { - modeSwitching.remove(uid); - } - - // ★ 只在玩家已被强制过时才发消息 - // 首次不发,后续才提醒 - if (alreadyForced.contains(uid)) { - p.sendMessage(formatAreaMsg( - "&c该区域强制 " - + ac.enforceGameMode - + " 模式,无法切换")); - } - alreadyForced.add(uid); - } else { - // 模式一致,清除标记 - alreadyForced.remove(uid); - } - } - } - }.runTaskTimer(plugin, 20L, 20L); - } - - public void stopEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - enforceTask = null; - } - } - - - - /** - * 检查玩家是否豁免于游戏模式强制 - * 豁免条件:全局白名单 / 区域白名单 / 模式排除名单 - */ - private boolean isExemptFromGameMode( - Player p, String areaName) { - String name = p.getName(); - - /* if (p.isOp()) { - plugin.getLogger().info( - "[防护-调试] 豁免原因: OP, 玩家=" + name); - return true; - }*/ - if (globalPlayerWhitelist.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 全局白名单, 玩家=" - + name);*/ - return true; - } - Set aw = - areaPlayerWhitelist.get(areaName); - if (aw != null && aw.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 区域白名单, 玩家=" - + name + " 区域=" + areaName);*/ - return true; - } - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 模式排除名单, 玩家=" - + name);*/ - return true; - } - /* plugin.getLogger().info( - "[防护-调试] 无豁免, 玩家=" + name);*/ - return false; - } - - /** - * 玩家进入区域时,如果该区域强制游戏模式, - * 则立即设置(排除豁免玩家) - */ - private void applyEnforcedGameMode(Player p, - String areaName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: ac=null, area=" - + areaName);*/ - return; - } - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: 未配置强制模式, area=" - + areaName);*/ - return; - } - - boolean exempt = isExemptFromGameMode( - p, areaName); - /* plugin.getLogger().info( - "[防护-调试] applyMode: 玩家=" + p.getName() - + " 区域=" + areaName - + " 强制=" + ac.enforceGameMode - + " 当前=" + p.getGameMode() - + " 豁免=" + exempt - + " OP=" + p.isOp() - + " 全白=" + globalPlayerWhitelist - .contains(p.getName()));*/ - - if (exempt) return; - - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - /* plugin.getLogger().info( - "[防护] 强制切换 " + p.getName() - + " → " + ac.enforceGameMode);*/ - } - } catch (Exception e) { - /* plugin.getLogger().warning( - "[防护] 强制模式失败: " - + e.getMessage());*/ - } - } - - public AreaProtection(Main plugin) { - this.plugin = plugin; - - // 统一根目录 - rootDir = new File(plugin.getDataFolder(), "区域防护"); - if (!rootDir.exists()) rootDir.mkdirs(); - - // 白名单子目录 - whitelistDir = new File(rootDir, "whitelists"); - if (!whitelistDir.exists()) whitelistDir.mkdirs(); - globalWhitelistFile = new File( - whitelistDir, "全局白名单.txt"); - - // 数据库迁移 - oldDbFile = new File( - plugin.getDataFolder(), "area_effects.db"); - newDbFile = new File(rootDir, "area_effects.db"); - migrateDatabase(); - - // 初始化 - initDatabase(); - writeDefaultConfig(); - loadAllAreas(); - loadWhitelists(); - recoverPendingEffects(); - } - public void recoverPendingEffects() { - try { - // 1. 遍历所有在线玩家(而不是遍历DB记录) - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String playerName = p.getName(); - - // 2. 获取玩家当前所在区域 - AreaConfig currentAc = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - - // 3. 获取该玩家在DB中的所有历史记录 - List recordedAreas = - getPlayerAreas(uid); - - // 4. 清理不在当前区域的旧记录 - for (String recordedArea : recordedAreas) { - if (currentAc == null - || !currentAc.name - .equals(recordedArea)) { - // 玩家不在这个区域了 → 清DB - removePlayerEffects( - uid, recordedArea); - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在" + recordedArea - + ", 清理旧记录"); - } - } - - // 5. 如果在区域中 → 恢复/给予效果 - if (currentAc != null) { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 在" + currentAc.name - + ", 恢复效果"); - applyRegionEffects(p, uid, - currentAc.name, currentAc); - } else { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在任何区域, 跳过"); - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 恢复效果失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - // 取消延时清理任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 清标记 - playerMarkedEffects.remove(uid); - playerCurrentArea.remove(uid); - } - - -// ==================== 数据库迁移 ==================== - - /** - * 如果旧位置有 db 文件,移到新位置 - * 如果新位置已有,不动 - * 如果旧位置也没有,在新位置创建 - */ - - private void migrateDatabase() { - if (newDbFile.exists()) { - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - oldDbFile.delete(); - } - return; - } - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - try { - java.nio.file.Files.move( - oldDbFile.toPath(), - newDbFile.toPath(), - java.nio.file.StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 迁移数据库失败: " - + e.getMessage()); - } - } - } - - - // 保存玩家进入区域时给予的效果 - private void savePlayerEffects(UUID uid, String playerName, - String areaName, List effects) { - try { - // 先删除该区域旧记录,避免重复 - PreparedStatement del = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - del.setString(1, uid.toString()); - del.setString(2, areaName); - del.executeUpdate(); - del.close(); - - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)"); - long now = System.currentTimeMillis(); - for (PotionEffectType type : effects) { - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, type.getName()); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, now); - ps.addBatch(); - } - ps.executeBatch(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 保存效果失败: " + e.getMessage()); - } - } - - // 查询玩家在某个区域获得的效果 - private List getPlayerEffects( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - PotionEffectType type = PotionEffectType - .getByName(rs.getString("effect_type")); - if (type != null) list.add(type); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - // 删除玩家在某个区域的效果记录 - private void removePlayerEffects(UUID uid, String areaName) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除效果失败: " + e.getMessage()); - } - } - - // 删除玩家所有效果记录 - private void removeAllPlayerEffects(UUID uid) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除全部效果失败: " + e.getMessage()); - } - } - - // 获取玩家所有记录的区域 - private List getPlayerAreas(UUID uid) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT DISTINCT area_name " - + "FROM player_effects WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("area_name")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询区域失败: " + e.getMessage()); - } - return list; - } - - // 查询所有待处理的效果(跨服重启也能恢复) - private List getAllPendingEffects() { - List list = new ArrayList<>(); - try { - Statement stmt = dbConnection.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT uuid, player_name, area_name, " - + "effect_type FROM player_effects"); - while (rs.next()) { - list.add(new String[]{ - rs.getString("uuid"), - rs.getString("player_name"), - rs.getString("area_name"), - rs.getString("effect_type")}); - } - rs.close(); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询全部效果失败: " + e.getMessage()); - } - return list; - } - - - private void writeDefaultConfig() { - File f = new File(rootDir, "末地保护区.txt"); - if (f.exists()) return; - - String endWorld = "world_the_end"; - for (org.bukkit.World w : Bukkit.getWorlds()) { - if (w.getEnvironment() - == org.bukkit.World.Environment.THE_END) { - endWorld = w.getName(); - break; - } - } - - final String ew = endWorld; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 末地保护区"); - pw.println("起点: " + ew + ",-30000000,-30000000"); - pw.println("终点: " + ew + ",30000000,30000000"); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("没收物品: BROWN_MUSHROOM,RED_MUSHROOM,MUSHROOM_STEW"); - pw.println("没收提示: 你携带了违禁品「蘑菇」,已被全部没收!"); - pw.println("通报批评: {player} 在末地违规携带蘑菇!"); - pw.println(); - pw.println("禁止放置方块"); - pw.println("禁止破坏方块"); - pw.println("禁止PVP"); - pw.println("禁止袭击"); - pw.println(); - pw.println("效果: 夜视 1 999"); - pw.println("进入提示: 末地保护区 - 请遵守规则"); - pw.println("离开提示: 已离开末地保护区"); - pw.close(); - plugin.getLogger().info("[防护] 生成末地配置, 世界: " + ew); - } catch (IOException ignored) { - } - } - - public void reload() { - loadAllAreas(); - loadWhitelists(); - // 强制输出醒目的中文日志 - plugin.getLogger().info("========================================"); - plugin.getLogger().info("[防护] 已重载 " + areas.size() + " 个区域"); - plugin.getLogger().info("[防护] 已重载白名单"); - plugin.getLogger().info("========================================"); - } - -// ==================== 消息格式化 ==================== - - /** - * 格式化区域消息 - * 1. & → §(颜色符号转换) - * 2.
和 \n → 换行 - * 3. 内容 → §l内容§r(加粗) - * 4. 自动添加 [区域防护] 前缀 - */ - private String formatAreaMsg(String raw) { - if (raw == null || raw.isEmpty()) return ""; - - String msg = raw; - - // & → § - for (int i = 0; i <= 9; i++) { - msg = msg.replace("&" + i, "§" + i); - } - for (char c : "abcdefklnor".toCharArray()) { - msg = msg.replace("&" + c, "§" + c); - } - - // 换行 - msg = msg.replace("
", "\n"); - msg = msg.replace("\\n", "\n"); - - // HTML → Minecraft格式(栈式解析,支持嵌套) - msg = convertHtmlTags(msg); - - // 前缀 - msg = "§c§l【区域防护】§r " + msg; - - return msg; - } - - /** - * 栈式HTML标签解析 - * 处理 text rest 这样的嵌套标签 - * 内层关闭时重新应用外层格式 - */ - private String convertHtmlTags(String msg) { - StringBuilder sb = new StringBuilder(); - java.util.Deque stack = - new ArrayDeque<>(); - int i = 0; - - while (i < msg.length()) { - if (msg.charAt(i) == '<' - && i + 1 < msg.length()) { - if (msg.charAt(i + 1) == '/') { - // 关闭标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 2, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0 && !stack.isEmpty() - && stack.peek() == code) { - stack.pop(); - if (!stack.isEmpty()) { - // 重置后重新应用外层格式 - sb.append("§r"); - for (char c : stack) { - sb.append("§").append(c); - } - } else { - sb.append("§r"); - } - } - i = end + 1; - } else { - // 开放标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 1, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0) { - stack.push(code); - sb.append("§").append(code); - } - i = end + 1; - } - } else { - sb.append(msg.charAt(i)); - i++; - } - } - return sb.toString(); - } - - private char htmlToMc(String tag) { - switch (tag) { - case "b": return 'l'; - case "i": return 'o'; - case "u": return 'n'; - case "s": return 'm'; - default: return 0; - } - } - -// =============== 加载 ==================== - - public void loadAllAreas() { - areas.clear(); - File[] files = rootDir.listFiles( - (File d, String n) -> n.endsWith(".txt")); - if (files == null) return; - for (File f : files) { - try { - String name = f.getName() - .replace(".txt", ""); - AreaConfig ac = parseArea(name, f); - areas.put(name, ac); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载失败: " + f.getName()); - } - } - } - - - private void initDatabase() { - try { - dbConnection = DriverManager.getConnection( - "jdbc:sqlite:" + newDbFile.getAbsolutePath()); - Statement stmt = dbConnection.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS player_effects (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "uuid TEXT NOT NULL," - + "player_name TEXT," - + "area_name TEXT NOT NULL," - + "effect_type TEXT NOT NULL," - + "effect_level INTEGER DEFAULT 0," - + "effect_duration INTEGER DEFAULT 999," - + "enter_time INTEGER NOT NULL)"); - stmt.executeUpdate( - "CREATE INDEX IF NOT EXISTS idx_uuid " - + "ON player_effects(uuid)"); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().severe( - "[防护] 数据库初始化失败: " - + e.getMessage()); - } - } - - -// ==================== 效果名称中英文映射 ==================== - - /** - * 将中文效果名转为 Bukkit 能识别的英文名 - */ - private String resolveEffectName(String name) { - if (name == null) return null; - String n = name.trim(); - // 如果已经是英文或命名空间格式,直接用 - if (n.contains(":") - || n.equals(n.toUpperCase())) { - return n; - } - // 中文 → 英文映射 - switch (n) { - case "伤害吸收": return "ABSORPTION"; - case "生命提升": return "HEALTH_BOOST"; - case "抗性提升": return "DAMAGE_RESISTANCE"; - case "抗火": return "FIRE_RESISTANCE"; - case "夜视": return "NIGHT_VISION"; - case "力量": return "INCREASE_DAMAGE"; - case "速度": return "SPEED"; - case "急迫": return "FAST_DIGGING"; - case "跳跃提升": return "JUMP"; - case "再生": return "REGENERATION"; - case "生命恢复": return "REGENERATION"; - case "治疗": return "HEAL"; - case "瞬间治疗": return "HEAL"; - case "伤害药水": return "HARM"; - case "瞬间伤害": return "HARM"; - case "虚弱": return "WEAKNESS"; - case "中毒": return "POISON"; - case "缓慢": return "SLOW"; - case "挖掘疲劳": return "SLOW_DIGGING"; - case "失明": return "BLINDNESS"; - case "隐身": return "INVISIBILITY"; - case "发光": return "GLOWING"; - // 村庄英雄 - case "村庄英雄": return "HERO_OF_THE_VILLAGE"; - case "幸运": return "LUCK"; - case "不幸": return "UNLUCK"; - case "水下呼吸": return "WATER_BREATHING"; - case "水下速掘": return "DOLPHINS_GRACE"; - case "缓降": return "LEVITATION"; - case "漂浮": return "LEVITATION"; - case "饱和": return "SATURATION"; - // 无效效果 - case "伤害": return "HARM"; - case "治疗药水": return "HEAL"; - default: - // 尝试直接用英文匹配 - return n; - } - } - - /** - * 全量容错:把全角字符统一转半角, - * 去掉零宽字符/不可见字符, - * 让用户怎么写都能正确解析。 - */ - private String normalizeLine(String input) { - if (input == null || input.isEmpty()) return ""; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - - // 跳过零宽字符和不可见字符 - if (c == '\u200B' // 零宽空格 - || c == '\u200C' // 零宽非连接 - || c == '\u200D' // 零宽连接 - || c == '\uFEFF' // BOM - || c == '\u00A0' // 不间断空格 - || c == '\u2028' // 行分隔符 - || c == '\u2029' // 段分隔符 - || c == '\u200E' // LTR标记 - || c == '\u200F' // RTL标记 - || c == '\u2060' // 字连接符 - || (c >= '\u2000' && c <= '\u200A')) { // 各种空格 - continue; - } - - // 全角→半角映射 - if (c >= '\uFF01' && c <= '\uFF5E') { - c = (char) (c - 0xFEE0); - } - - // 全角空格→半角空格 - if (c == '\u3000') { - c = ' '; - } - - sb.append(c); - } - - return sb.toString(); - } - - - private AreaConfig parseArea(String name, File f) - throws IOException { - AreaConfig ac = new AreaConfig(); - ac.name = name; - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean inBlockComment = false; - boolean inHtmlComment = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty()) continue; -// ===== 全量容错预处理:全角→半角,去零宽字符 ===== - line = normalizeLine(line); - if (line.isEmpty()) continue; -// ===== 暴力调试:输出所有包含关键词的行 ===== - if (line.indexOf("游戏") >= 0 - || line.indexOf("模式") >= 0 - || line.indexOf("生存") >= 0 - || line.indexOf("创造") >= 0 - || line.indexOf("排除") >= 0 - || line.indexOf("豁免") >= 0 - || line.indexOf("强制") >= 0 - || line.indexOf("GameMode") >= 0 - || line.indexOf("SURVIVAL") >= 0 - || line.indexOf("CREATIVE") >= 0) { - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < line.length(); i++) { - hex.append(String.format("U+%04X ", (int) line.charAt(i))); - } - /* plugin.getLogger().info("[暴力调试] 行=[" + line - + "] 长度=" + line.length() - + " 字符码=" + hex.toString());*/ - } -// ===== 兜底扫描:确保和平白名单一定能解析 ===== - if (line.indexOf("白名单") >= 0 - || line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - int ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf('='); - if (ci < 0) ci = line.indexOf(' '); - - // 和平白名单 - if (line.indexOf("和平") >= 0 - && line.indexOf("白名单") >= 0 - && ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.peaceWhitelist.add(nm); - } - plugin.getLogger().info("[解析兜底] 和平白名单=" - + ac.peaceWhitelist); - } - - // 排除名单 - if (line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - if (ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.modeExempt.add(nm); - } - plugin.getLogger().info("[解析兜底] 排除名单=" - + ac.modeExempt); - } - } - } -// ===== 兜底结束 ===== - - if (inBlockComment) { - int endIdx = line.indexOf("*/"); - if (endIdx >= 0) { - inBlockComment = false; - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("/*")) { - int endIdx = line.indexOf("*/", 2); - if (endIdx >= 0) { - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - inBlockComment = true; - continue; - } - } - if (inHtmlComment) { - int endIdx = line.indexOf("-->"); - if (endIdx >= 0) { - inHtmlComment = false; - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("", 4); - if (endIdx >= 0) { - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - inHtmlComment = true; - continue; - } - } - if (line.startsWith("#") || line.startsWith("//")) { - continue; - } - int hashIdx = line.indexOf("#"); - if (hashIdx > 0) { - line = line.substring(0, hashIdx).trim(); - } - int slashIdx = line.indexOf("//"); - if (slashIdx > 0) { - line = line.substring(0, slashIdx).trim(); - } - if (line.isEmpty()) continue; - - if (line.startsWith("起点:")) { - String[] pp = line.substring("起点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x1 = Integer.parseInt(pp[1].trim()); - ac.z1 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("终点:")) { - String[] pp = line.substring("终点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x2 = Integer.parseInt(pp[1].trim()); - ac.z2 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("高度范围:")) { - String[] pp = line.substring("高度范围:".length()) - .trim().split("-"); - if (pp.length == 2) { - ac.yMin = Integer.parseInt(pp[0].trim()); - ac.yMax = Integer.parseInt(pp[1].trim()); - } - } else if (line.startsWith("没收物品:")) { - for (String s : line.substring("没收物品:".length()) - .trim().split("[,,]")) - ac.confiscateItems.add( - s.trim().toUpperCase()); - } else if (line.startsWith("禁止使用物品:")) { - for (String s : line.substring("禁止使用物品:".length()) - .trim().split("[,,]")) - ac.denyUseItems.add( - s.trim().toUpperCase()); - } else if (line.equals("禁止放置方块")) { - ac.denyBlockPlace = true; - } else if (line.equals("禁止破坏方块")) { - ac.denyBlockBreak = true; - } else if (line.equals("禁止PVP")) { - ac.denyPVP = true; - } else if (line.equals("禁止摔伤")) { - ac.denyFallDamage = true; - } else if (line.equals("禁止饥饿")) { - ac.denyHunger = true; - } else if (line.equals("禁止一切伤害")) { - ac.denyAllDamage = true; - } else if (line.equals("禁止丢弃物品")) { - ac.denyDrop = true; - } else if (line.equals("禁止末影珍珠")) { - ac.denyEnderPearl = true; - } else if (line.equals("禁止使用弓箭")) { - ac.denyBow = true; - } else if (line.equals("禁止骑乘")) { - ac.denyMount = true; - } else if (line.equals("禁止袭击")) { - ac.denyRaid = true; - } else if (line.equals("禁止药水")) { - ac.denyPotion = true; - } else if (line.equals("禁止爆炸")) { - ac.denyExplosion = true; - } else if (line.equals("禁止燃烧") - || line.equals("禁止火焰蔓延")) { - ac.denyFireSpread = true; - } else if (line.equals("清除所有效果") - || line.equals("清理所有效果")) { - ac.clearAllBadEffects = true; - } else if (line.startsWith("清除") - || line.startsWith("清理")) { - String effName = line.substring(2).trim(); - if (effName.endsWith("效果")) - effName = effName.substring(0, - effName.length() - 2); - if (!effName.isEmpty()) - ac.clearEffects.add(effName); - } else if (line.contains("和平模式") - && !line.contains("白名单") - && !line.contains("定时") - && !line.contains("开关")) { - ac.peaceMode = true; - plugin.getLogger().info("[解析] 和平模式已识别"); - - } else if (line.startsWith("效果")) { - String rest = line.substring(2).trim(); - // 去掉前导冒号、空格 - while (rest.length() > 0 - && (rest.charAt(0) == ':' - || rest.charAt(0) == ':' - || rest.charAt(0) == ' ' - || rest.charAt(0) == '\t')) { - rest = rest.substring(1); - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - // 第一个是效果名(可能是中文) - String effName = parts[0]; - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) {} - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) {} - } - - // ★ 用映射方法转为英文名 - String resolved = resolveEffectName(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → " + resolved - + " 等级=" + effLv - + " 时长=" + effDur); - - ac.giveEffects.add(new String[]{ - resolved, - String.valueOf(effLv), - String.valueOf(effDur)}); - // ★ 加日志确认解析结果 - PotionEffectType checkType = - resolveEffectType(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → type=" - + (checkType != null - ? checkType.getName() - : "NULL(失败)") - + " 等级=" + effLv - + " 时长=" + effDur); - - - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) { - } - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) { - } - } - ac.giveEffects.add(new String[]{ - parts[0], - String.valueOf(effLv), - String.valueOf(effDur)}); - } - } else if (line.startsWith("通报批评:")) { - ac.announceTemplate = line.substring( - "通报批评:".length()).trim(); - ac.enableAnnounce = true; - } else if (line.startsWith("没收提示:")) { - ac.confiscateMsg = line.substring( - "没收提示:".length()).trim(); - } else if (line.startsWith("进入提示:")) { - String raw = line.substring("进入提示:".length()).trim(); - ac.enterMsg = raw; - - // ★ 调试:打印原文和Unicode - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★进入提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("离开提示:")) { - String raw = line.substring("离开提示:".length()).trim(); - ac.leaveMsg = raw; - - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★离开提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("惩罚命令:")) { - ac.punishCommands.add(line.substring( - "惩罚命令:".length()).trim()); - } else if (line.startsWith("强制游戏模式:") - || line.startsWith("游戏模式:")) { - String prefix = line.startsWith("强制游戏模式:") - ? "强制游戏模式:" : "游戏模式:"; - String modeStr = line.substring(prefix.length()) - .trim().toUpperCase(); - // 容错:支持多种写法 - if (modeStr.equals("CREATIVE") || modeStr.equals("创造") - || modeStr.equals("1")) { - ac.enforceGameMode = "CREATIVE"; - } else if (modeStr.equals("SURVIVAL") || modeStr.equals("生存") - || modeStr.equals("0")) { - ac.enforceGameMode = "SURVIVAL"; - } else if (modeStr.equals("ADVENTURE") || modeStr.equals("冒险") - || modeStr.equals("2")) { - ac.enforceGameMode = "ADVENTURE"; - } else if (modeStr.equals("SPECTATOR") || modeStr.equals("旁观") - || modeStr.equals("spectator") - || modeStr.equals("3")) { - ac.enforceGameMode = "SPECTATOR"; - } - } else if (line.contains("排除名单") - || line.contains("模式排除") - || line.contains("模式豁免")) { - int colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf('='); - if (colonIdx >= 0) { - String rest = line.substring(colonIdx + 1).trim(); - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playerName = s.trim(); - if (!playerName.isEmpty()) { - ac.modeExempt.add(playerName); - } - } - } - - String rest = line.substring(/*idx*/ + 4).trim(); - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playername = s.trim(); - if (!name.isEmpty()) ac.modeExempt.add(name); - } - } - - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String wlName = s.trim(); - if (!wlName.isEmpty()) { - ac.peaceWhitelist.add(wlName); - } - } - } - - - - } else if (line.equals("禁止交互展示框") - || line.equals("禁止展示框")) { - ac.denyItemFrame = true; - - } - } - br.close(); - return ac; - } -// ===== 和平白名单操作 ===== - - // 添加生物名字到区域和平白名单 - public boolean addPeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.add(creatureName); - savePeaceWhitelist(ac); - return true; - } - - // 从区域和平白名单移除生物名字 - public boolean removePeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.remove(creatureName); - savePeaceWhitelist(ac); - return true; - } - -// ===== 模式排除名单操作 ===== - - - @EventHandler - public void onCreatureSpawn(CreatureSpawnEvent e) { - Entity entity = e.getEntity(); - if (!isHostile(entity.getType().name())) return; - - Location loc = entity.getLocation(); - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - - if (ac == null || !ac.peaceMode) return; - - protectedEntities.put( - entity.getUniqueId(), - System.currentTimeMillis() + 5000); - - /* plugin.getLogger().info("[和平] " - + entity.getType().name() - + " 获得5秒保护期" - + " 区域=" + ac.name - + " 白名单=" + ac.peaceWhitelist - + " 白名单数量=" + ac.peaceWhitelist.size());*/ - } - - - public void cleanupProtectedEntities() { - long now = System.currentTimeMillis(); - int cleaned = 0; - Iterator> it = - protectedEntities.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (now > entry.getValue()) { - it.remove(); - cleaned++; - } - } - if (cleaned > 0) { - /* plugin.getLogger().info("[和平模式] 清理过期保护: " - + cleaned + "个 剩余:" - + protectedEntities.size() + "个");*/ - } - } - - - public void banHostilesWithWhitelist(Player p, AreaConfig ac) { - long now = System.currentTimeMillis(); - // 看看附近到底有什么实体 - List nearby = p.getNearbyEntities(48, 48, 48); - /* plugin.getLogger().info("[和平] " + p.getName() - + " 附近实体总数=" + nearby.size());*/ - for (Entity ent : nearby) { - String typeName = ent.getType().name(); - /* plugin.getLogger().info("[和平] 实体: " - + typeName + " isHostile=" + isHostile(typeName));*/ - } - int scanned = 0; - int waiting = 0; - int saved = 0; - int banished = 0; - - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (!isHostile(ent.getType().name())) continue; - scanned++; - - UUID entUid = ent.getUniqueId(); - Long protectUntil = protectedEntities.get(entUid); - String customName = ent.getCustomName(); - - // 有保护期记录 - if (protectUntil != null) { - long remain = protectUntil - now; - - if (remain > 0) { - // 保护期内,精确剩余秒数 - int secondsLeft = (int) Math.ceil(remain / 1000.0); - waiting++; - - // 保护期内也检查名字(玩家可能刚贴上) - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - protectedEntities.remove(entUid); - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 白名单匹配,永久保留");*/ - continue; - } - - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 保护期剩余约" + secondsLeft + "秒");*/ - continue; - } - - // 保护期到了,最终检查 - protectedEntities.remove(entUid); - /* plugin.getLogger().info("[和平] " - + ent.getType().name() + " 5秒到期");*/ - - // 到期时再查一次白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 到期但白名单匹配,保留");*/ - continue; - } - - // 没名字或不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名=[" + customName + "] 传送虚空");*/ - continue; - } - - // 没有保护期记录(非刷怪蛋生成的或记录被清了) - // 也检查一下白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 无保护期但白名单匹配,保留");*/ - continue; - } - - // 没有保护期且不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 无保护期,命名=[" + customName + "] 传送虚空");*/ - } - - /* plugin.getLogger().info("[和平] " - + " 扫描=" + scanned - + " 等待=" + waiting - + " 保留=" + saved - + " 传送=" + banished - + " 白名单=" + ac.peaceWhitelist);*/ - } - - private static final Set HOSTILE_TYPES = new HashSet<>(Arrays.asList( - "ZOMBIE", "ZOMBIE_VILLAGER", "HUSK", "DROWNED", - "SKELETON", "STRAY", "BOGGED", "WITHER_SKELETON", - "CREEPER", "SPIDER", "CAVE_SPIDER", "ENDERMITE", - "SILVERFISH", "BLAZE", "GHAST", "BIG_GHAST", - "MAGMA_CUBE", "SLIME", "WITCH", - "GUARDIAN", "ELDER_GUARDIAN", - "PILLAGER", "VINDICATOR", "EVOKER", "RAVAGER", - "ILLUSIONER", "VEX", - "PHANTOM", "SHULKER", "WARDEN", "BREEZE", - "PIGLIN", "PIGLIN_BRUTE", "ZOGLIN", - "ZOMBIFIED_PIGLIN", "ENDER_DRAGON" - )); - - private boolean isHostile(String typeName) { - return HOSTILE_TYPES.contains(typeName); - } - - // ==================== 白名单持久化 ==================== - - /** - * 保存白名单到文件 - * 路径:区域防护/whitelists/全局白名单.txt - * 路径:区域防护/whitelists/区域名.txt - */ - public void saveWhitelists() { - try { - // 1. 保存全局白名单 - PrintWriter pwGlobal = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(globalWhitelistFile), - StandardCharsets.UTF_8)); - for (String name : globalPlayerWhitelist) { - pwGlobal.println(name); - } - pwGlobal.close(); - - // 2. 删除旧的区域白名单文件(防止残留) - File[] oldFiles = whitelistDir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (oldFiles != null) { - for (File f : oldFiles) f.delete(); - } - - // 3. 保存各区域白名单 - for (Map.Entry> entry - : areaPlayerWhitelist.entrySet()) { - String name = entry.getKey(); - File areaFile = new File( - whitelistDir, name + ".txt"); - PrintWriter pwArea = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(areaFile), - StandardCharsets.UTF_8)); - for (String playerName : entry.getValue()) { - pwArea.println(playerName); - } - pwArea.close(); - } - - plugin.getLogger().info("[防护] 白名单已保存"); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 保存白名单失败: " - + e.getMessage()); - } - } - - - - // ==================== 区域检测 ==================== - - public AreaConfig getArea(String worldName, - int x, int y, int z) { - for (AreaConfig ac : areas.values()) { - if (ac.world.equalsIgnoreCase(worldName) - && inRect(x, z, ac) - && y >= ac.yMin - && y <= ac.yMax) { - return ac; - } - } - return null; - } - - private boolean inRect(int x, int z, AreaConfig ac) { - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - return x >= minX && x <= maxX - && z >= minZ && z <= maxZ; - } - - // ==================== 白名单检查 ==================== - - private boolean isPlayerWhitelisted(String player, - AreaConfig ac) { - if (globalPlayerWhitelist.contains( - player.toLowerCase())) - return true; - Set areaList = - areaPlayerWhitelist.get(ac.name); - return areaList != null - && areaList.contains(player.toLowerCase()); - } - - private boolean isItemBlacklisted(String itemId, - AreaConfig ac) { - if (globalItemBlacklist.contains( - itemId.toUpperCase())) - return true; - Set areaList = - areaItemBlacklist.get(ac.name); - return areaList != null - && areaList.contains(itemId.toUpperCase()); - } - - // ==================== 没收 ==================== - - private int confiscateItem(Player p, String itemTypeId) { - Material mat = Material.matchMaterial(itemTypeId); - if (mat == null) return 0; - ItemStack[] contents = p.getInventory().getContents(); - int removed = 0; - for (int i = 0; i < contents.length; i++) { - if (contents[i] != null - && contents[i].getType() == mat) { - removed += contents[i].getAmount(); - contents[i] = null; - } - } - p.getInventory().setContents(contents); - return removed; - } - - // ==================== 边框显示 ==================== - private void showBorder(Player p, AreaConfig ac, boolean temp) { - cancelBorder(p); - int taskId = Bukkit.getScheduler() - .runTaskTimer(plugin, new Runnable() { - @Override - public void run() { - if (!p.isOnline()) return; - Location loc = p.getLocation(); - int px = loc.getBlockX(); - int py = loc.getBlockY(); - int pz = loc.getBlockZ(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - for (int dy = 5; dy <= 30; dy += 5) { - for (int x = minX; x <= maxX; x += 4) { - for (int z = minZ; z <= maxZ; z += 4) { - if (x == minX || x == maxX - || z == minZ || z == maxZ) { - p.spawnParticle(Particle.END_ROD, - x + 0.5, py + dy, z + 0.5, - 1, 0, 0, 0, 0); - } - } - } - } - } - }, 0L, 20L).getTaskId(); - displayTaskIds.put(p.getUniqueId(), taskId); // taskId 是 int - if (temp) { - Bukkit.getScheduler().runTaskLater(plugin, - () -> cancelBorder(p), 300L); - } - } - - - private void cancelBorder(Player p) { - Integer taskId = displayTaskIds.remove(p.getUniqueId()); - if (taskId != null) { - Bukkit.getScheduler().cancelTask(taskId); - } - } - - public void onPlayerQuit(String playerName) { - playerCurrentArea.remove(playerName); - } - - public void clearAllPlayerAreas() { - playerCurrentArea.clear(); - } - private void saveModeExempt(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("排除名单") - || trimmed.contains("模式排除") - || trimmed.contains("模式豁免")) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.modeExempt.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存模式排除名单失败: " + e.getMessage()); - } - } - - // ==================== 事件监听 ==================== - @EventHandler - public void onPlayerMove(PlayerMoveEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - Location to = event.getTo(); - if (to == null) return; - - Location from = event.getFrom(); - if (from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ()) { - return; - } - - // 防抖 - Long lastTime = lastMoveProcess.get(uid); - long now = System.currentTimeMillis(); - if (lastTime != null - && now - lastTime < MOVE_DEBOUNCE_MS) { - return; - } - lastMoveProcess.put(uid, now); - - // 检测区域 - String oldArea = playerCurrentArea.get(uid); - String newArea = null; - String forcedMode = null; - - for (Map.Entry entry - : areas.entrySet()) { - AreaConfig ac = entry.getValue(); - if (!ac.world.equals(to.getWorld().getName())) - continue; - int px = to.getBlockX(); - int pz = to.getBlockZ(); - int py = to.getBlockY(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - if (px >= minX && px <= maxX - && pz >= minZ && pz <= maxZ - && py >= ac.yMin && py <= ac.yMax) { - newArea = entry.getKey(); - forcedMode = ac.enforceGameMode; - break; - } - } - - /* plugin.getLogger().info( - "[防护-移动] " + p.getName() - + " 旧=[" + oldArea - + "] 新=[" + newArea + "]" - + " areas.size=" + areas.size() - + " world=" + to.getWorld().getName());*/ - - // ===== 2. 区域没变 ===== - if (Objects.equals(oldArea, newArea)) { - if (newArea != null) { - AreaConfig ac = areas.get(newArea); - if (ac != null) { - // ★ 每次移动都清除指定效果 - clearBadEffects(p, ac); - - // ★ 每次移动都强制游戏模式 - if (ac.enforceGameMode != null - && !ac.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - } - return; - } - - // 离开旧区域 - if (oldArea != null) { - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★开始离开处理: - + oldArea);*/ - - AreaConfig oldAc = areas.get(oldArea); - - // 离开提示 - if (oldAc != null && oldAc.leaveMsg != null - && !oldAc.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(oldAc.leaveMsg)); - } - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★收标: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - plugin.getLogger().info( - "[防护-移动-离开] 标记项: " + t.getName()); - } - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - plugin.getLogger().info( - "[防护-移动-离开] 取消旧延时"); - } - - // 创建延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = oldArea; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★创建延时任务: " - + toClear.size() + "个");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-移动-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); - - plugin.getLogger().info( - "[防护-移动-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size()); - - if (online == null || !online.isOnline()) { - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-移动-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-移动-清理] + cleared" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - } - } - - - // 进入新区域 - if (newArea != null) { - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - if (oldP != null) { - diffEffects(p, uid, oldP, newArea); - } - } - - AreaConfig newAc = areas.get(newArea); - if (newAc != null) { - if (newAc.enterMsg != null - && !newAc.enterMsg.isEmpty()) { - p.sendMessage( - formatAreaMsg(newAc.enterMsg)); - } - applyRegionEffects(p, uid, newArea, newAc); - // 清除效果 - clearBadEffects(p, newAc); - - } -// ===== 清除指定效果 ===== - if (newAc.clearEffects != null - && !newAc.clearEffects.isEmpty()) { - for (String effName : newAc.clearEffects) { - PotionEffectType type = - resolveEffectType(effName); - if (type != null && p.getPotionEffect(type) != null) { - p.removePotionEffect(type); - plugin.getLogger().info( - "[防护] ★清除效果: " + effName - + " 玩家=" + p.getName()); - } - } - } - -// ===== 清除所有效果 ===== - if (newAc.clearAllBadEffects) { - for (PotionEffect eff : p.getActivePotionEffects()) { - p.removePotionEffect(eff.getType()); - } - plugin.getLogger().info( - "[防护] ★清除所有效果, 玩家=" + p.getName()); - } - - if (forcedMode != null - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(forcedMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - if (newArea != null) { - playerCurrentArea.put(uid, newArea); - } else { - playerCurrentArea.remove(uid); - } - } -// ===== 清理袭击生物 ===== - - // 需要清理的袭击生物类型 - private static final Set RAID_MOBS = new HashSet<>(Arrays.asList( - "VEX", - "EVOKER", - "PILLAGER", - "VINDICATOR", - "WITCH", - "RAVAGER", - "PILLAGER" - )); - - @EventHandler - public void onEntitySpawn(EntitySpawnEvent event) { - Entity entity = event.getEntity(); - Location loc = entity.getLocation(); - - // 检查是否在防护区域内 - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - if (ac == null) return; - - // 禁止袭击:取消袭击事件 - if (ac.denyRaid) { - // 清理袭击生物 - String typeName = entity.getType().name(); - if (RAID_MOBS.contains(typeName)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] ★清理袭击生物: " + typeName - + " 区域=" + ac.name); - } - } - - // 没收物品检测 - if (ac.confiscateItems != null - && !ac.confiscateItems.isEmpty()) { - if (entity instanceof Item) { - Item item = (Item) entity; - String itemType = item.getItemStack() - .getType().name(); - if (ac.confiscateItems.contains(itemType)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] 没收违禁掉落物: " - + itemType); - } - } - } - } - - - /** - * 进入区域时发放效果 - * 流程:查DB → 有记录跳过写入 → 无记录写入DB - * → 给效果 → 贴标 - */ - private void applyRegionEffects(Player p, UUID uid, - String areaName, AreaConfig ac) { - /* plugin.getLogger().info( - "[防护-贴标] ★★★方法被调用! 玩家=" - + p.getName() + " 区域=" + areaName - + " ac=" + (ac != null) - + " 效果配置数=" - + (ac != null ? ac.giveEffects.size() : "null"));*/ - - if (ac == null || ac.giveEffects.isEmpty()) return; - if (ac.giveEffects.isEmpty()) return; - // 查DB:该玩家在此区域是否已有效果记录 - List dbEffects = - getPlayerEffectNames(uid, areaName); - - if (dbEffects.isEmpty()) { - // 无记录 → 写入DB - for (String[] eff : ac.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t == null) continue; - saveSingleEffect(uid, p.getName(), - areaName, t.getName()); - } - plugin.getLogger().info( - "[防护] DB写入, 玩家=" + p.getName() - + " 区域=" + areaName); - } else { - plugin.getLogger().info( - "[防护] DB已有记录, 跳过写入, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - - // 给效果 + 贴标 - List applied = new ArrayList<>(); - for (String[] eff : ac.giveEffects) { - try { - PotionEffectType type = - resolveEffectType(eff[0]); - if (type == null) continue; - int lv = Integer.parseInt(eff[1]) - 1; - int dur = Integer.parseInt(eff[2]) * 20; - - PotionEffect existing = - p.getPotionEffect(type); - if (existing != null - && existing.getAmplifier() >= lv) { - continue; - } - p.addPotionEffect( - new PotionEffect(type, dur, lv)); - applied.add(type); - } catch (Exception ignored) { - } - } - - // 贴标 - // 新代码(合并,不覆盖): - List existingMarks = - playerMarkedEffects.get(uid); - if (existingMarks != null && !existingMarks.isEmpty()) { - // 已有标记,合并新增的 - for (PotionEffectType t : applied) { - if (!existingMarks.contains(t)) { - existingMarks.add(t); - } - } - /* plugin.getLogger().info( - "[防护-贴标] 合并标记: " - + existingMarks.size() - + " 个, 玩家=" + p.getName());*/ - } else { - // 无标记,新建 - playerMarkedEffects.put(uid, applied); - /* plugin.getLogger().info( - "[防护-贴标] 新建标记: " - + applied.size() - + " 个, 玩家=" + p.getName());*/ - } - } - - /** - * 离开区域时,延时15秒清理效果 - * 流程:收标 → 15秒后清效果 → 确认清空 → 清DB - */ - private void scheduleEffectClear(Player p, UUID uid, - String areaName) { - // 收标 - List marked = - playerAppliedEffects.remove(uid); - - plugin.getLogger().info( - "[防护] 收标 " + (marked != null ? marked.size() : 0) - + " 个, 玩家=" + p.getName() - + " 区域=" + areaName); - - // 启动15秒延时 - pendingClearArea.put(uid, areaName); - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - - // 清除效果 - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - } - - // 确认清空后清DB - removePlayerEffects(uid, areaName); - plugin.getLogger().info( - "[防护] 15秒已到, 清除 " - + (marked != null ? marked.size() : 0) - + " 个效果 + DB, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - }.runTaskLater(plugin, 300L); // 15秒 = 300 tick - - pendingClearTask.put(uid, task); - } - public Set getAllAreaNames() { - return areas.keySet(); - } - - /** - * 15秒内进了新区域: - * 清除旧区域有、新区域没有的效果 - */ - private void diffEffects(Player p, UUID uid, - String oldAreaName, String newAreaName) { - AreaConfig oldAc = areas.get(oldAreaName); - AreaConfig newAc = areas.get(newAreaName); - if (oldAc == null || newAc == null) return; - - // ★ 从 pendingClearEffects 获取旧标记 - List oldMarked = - pendingClearEffects.remove(uid); - - if (oldMarked == null || oldMarked.isEmpty()) { - plugin.getLogger().info( - "[防护-跨区域] 无待清理效果"); - return; - } - - // 新区域的效果集合 - Set newTypes = new HashSet<>(); - if (newAc != null) { - for (String[] eff : newAc.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t != null) newTypes.add(t); - } - } - - int cleared = 0; - for (PotionEffectType type : oldMarked) { - if (!newTypes.contains(type)) { - p.removePotionEffect(type); - cleared++; - plugin.getLogger().info( - "[防护-跨区域] 清除: " - + type.getName()); - } else { - plugin.getLogger().info( - "[防护-跨区域] 保留: " - + type.getName() - + " (新区域也有)"); - } - } - - removePlayerEffects(uid, oldAreaName); - plugin.getLogger().info( - "[防护-跨区域] 完成: 清除" - + cleared + "/" + oldMarked.size() - + " 个, " + oldAreaName - + " → " + newAreaName); - } - -// ===== DB 辅助方法(简化版)===== - - /** - * 查询玩家在某个区域的效果记录(返回英文名列表) - */ - private List getPlayerEffectNames( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid=? AND area_name=?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("effect_type")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - /** - * 写入单条效果记录 - */ - private void saveSingleEffect(UUID uid, - String playerName, String areaName, - String effectType) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?,?,?,?,?,?,?)"); - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, effectType); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 写入效果失败: " + e.getMessage()); - } - } - - // ==================== 违禁品没收 ==================== - - /** - * 检查玩家是否豁免于没收 - * 全局白名单 / 区域白名单 / modeExempt / OP 都跳过 - */ - private boolean isExemptFromConfiscation( - Player p, String areaName) { - String name = p.getName(); - - if (globalPlayerWhitelist.contains(name)) - return true; - - Set areaWl = - areaPlayerWhitelist.get(areaName); - if (areaWl != null && areaWl.contains(name)) - return true; - - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) - return true; - - return false; - } - - /** - * 没收玩家背包中的违禁品 - */ - private void confiscateItems(Player p, - AreaConfig ac) { - boolean confiscated = false; - - for (int i = 0; - i < p.getInventory().getSize(); i++) { - ItemStack item = p.getInventory().getItem(i); - if (item == null) continue; - - String typeName = item.getType().name(); - if (ac.confiscateItems - .contains(typeName)) { - p.getInventory().setItem(i, null); - confiscated = true; - } - } - - if (confiscated) { - String msg = ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty() - ? ac.confiscateMsg - : "§c你的违禁品已被没收"; - p.sendMessage(msg); - - // 通报批评 - if (ac.enableAnnounce - && ac.announceTemplate != null) { - String text = ac.announceTemplate - .replace("{player}", - p.getName()); - Bukkit.broadcastMessage(text); - } - } - } - - - - private String getChineseGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: return "生存模式"; - case CREATIVE: return "创造模式"; - case ADVENTURE: return "冒险模式"; - case SPECTATOR: return "旁观模式"; - default: return mode.name(); - } - } - - private void handleEnter(Player p, AreaConfig ac) { - UUID uid = p.getUniqueId(); - - // 清除负面效果 - clearBadEffects(p, ac); - - // 给予效果 - List given = new ArrayList<>(); - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - given.add(type); - } - } - - removePlayerEffects(uid, ac.name); - if (!given.isEmpty()) { - // savePlayerEffects(uid, p.getName(), ac.name, given); - } - - // 白名单检查(最先执行) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 游戏模式切换(只对非白名单玩家) - if (ac.enforceGameMode != null) { - boolean exempt = isPlayerExemptFromModeChange(p, ac); - if (!exempt) { - GameMode target = GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - p.sendMessage("§e§l[区域防护] §f游戏模式已切换为: " - + getChineseGameMode(target)); - } - } - } - - // 和平模式/袭击检查 - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - handleConfiscate(p, ac); - } - - private boolean isPlayerExemptFromModeChange( - Player p, AreaConfig ac) { - // if (p.isOp()) return true; - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return true; - } - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return true; - } - if (ac.modeExempt.contains(p.getName())) { - return true; - } - return false; - } - - private void banRangedHostiles(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (isRangedHostile(ent.getType().name())) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - /** - * 按配置清除效果 - * denyRaid → 清除袭击相关 - * denyAllEffects → 清除所有负面+中性 - * denyAllEffects > denyRaid(全清优先) - */ - private void clearBadEffects(Player p, AreaConfig ac) { - // 和平模式 > 清除所有效果 > 禁止单个 - if (ac.peaceMode || ac.clearAllBadEffects) { - PotionEffectType[] allBad = { - PotionEffectType.SLOWNESS, - PotionEffectType.MINING_FATIGUE, - PotionEffectType.INSTANT_DAMAGE, - PotionEffectType.NAUSEA, - PotionEffectType.BLINDNESS, - PotionEffectType.HUNGER, - PotionEffectType.WEAKNESS, - PotionEffectType.POISON, - PotionEffectType.WITHER, - PotionEffectType.LEVITATION, - PotionEffectType.UNLUCK, - PotionEffectType.DARKNESS, - PotionEffectType.WIND_CHARGED, - PotionEffectType.WEAVING, - PotionEffectType.OOZING, - PotionEffectType.INFESTED, - PotionEffectType.BAD_OMEN, - PotionEffectType.TRIAL_OMEN, - PotionEffectType.RAID_OMEN - }; - for (PotionEffectType type : allBad) { - p.removePotionEffect(type); - } - return; - } - - // 禁止袭击 → 自动关联清除 - if (ac.denyRaid) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - p.removePotionEffect(PotionEffectType.RAID_OMEN); - } - - // 配置文件单个清除 - for (String effName : ac.clearEffects) { - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.removePotionEffect(type); - } - } - } - - - private PotionEffectType resolveEffectType(String name) { - if (name == null || name.isEmpty()) return null; - - // 去掉"效果"后缀 - String clean = name; - if (clean.endsWith("效果")) { - clean = clean.substring(0, clean.length() - 2); - } - - // 硬编码中文名→英文ID映射(不依赖任何外部Map) - switch (clean) { - // 负面 - case "缓慢": - return resolveEffectType("slowness"); - case "挖掘疲劳": - return resolveEffectType("mining_fatigue"); - case "瞬间伤害": - return resolveEffectType("instant_damage"); - case "反胃": - return resolveEffectType("nausea"); - case "失明": - return resolveEffectType("blindness"); - case "饥饿": - return resolveEffectType("hunger"); - case "虚弱": - return resolveEffectType("weakness"); - case "中毒": - return resolveEffectType("poison"); - case "凋零": - return resolveEffectType("wither"); - case "飘浮": - return resolveEffectType("levitation"); - case "霉运": - return resolveEffectType("unluck"); - case "黑暗": - return resolveEffectType("darkness"); - case "蓄风": - return resolveEffectType("wind_charged"); - case "盘丝": - return resolveEffectType("weaving"); - case "渗浆": - return resolveEffectType("oozing"); - case "寄生": - return resolveEffectType("infested"); - // 中性 - case "不祥之兆": - case "不祥征兆": - return resolveEffectType("bad_omen"); - case "袭击之兆": - case "袭击征兆": - return resolveEffectType("raid_omen"); - case "试炼之兆": - case "试炼征兆": - return resolveEffectType("trial_omen"); - // 正面 - case "迅捷": - return resolveEffectType("speed"); - case "急迫": - return resolveEffectType("haste"); - case "力量": - return resolveEffectType("strength"); - case "瞬间治疗": - return resolveEffectType("instant_health"); - case "跳跃提升": - return resolveEffectType("jump_boost"); - case "生命恢复": - return resolveEffectType("regeneration"); - case "抗性提升": - return resolveEffectType("resistance"); - case "抗火": - return resolveEffectType("fire_resistance"); - case "水下呼吸": - return resolveEffectType("water_breathing"); - case "隐身": - return resolveEffectType("invisibility"); - case "夜视": - return resolveEffectType("night_vision"); - case "发光": - return resolveEffectType("glowing"); - case "生命提升": - return resolveEffectType("health_boost"); - case "伤害吸收": - return resolveEffectType("absorption"); - case "饱和": - return resolveEffectType("saturation"); - case "幸运": - return resolveEffectType("luck"); - case "村庄英雄": - return resolveEffectType("hero_of_the_village"); - case "缓降": - return resolveEffectType("slow_falling"); - case "潮涌能量": - return resolveEffectType("conduit_power"); - case "海豚的恩惠": - return resolveEffectType("dolphins_grace"); - case "致命中毒": - return resolveEffectType("poison"); - default: - break; - } - - -// 兜底:直接用原版英文ID查(★ 注意是 PotionEffectType.getByName,不是 resolveEffectType) - PotionEffectType fallback = PotionEffectType.getByName(clean); - if (fallback != null) return fallback; - fallback = PotionEffectType.getByName(clean.toLowerCase()); - if (fallback != null) return fallback; - - plugin.getLogger().warning("[防护] 无法识别效果: " + name); - return null; - - } - - - private static final Map EFFECT_MAP - = new HashMap<>(); - - - private void clearRaidEffects(Player p) { - boolean cleared = false; - try { - if (p.hasPotionEffect(PotionEffectType.BAD_OMEN)) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - try { - if (p.hasPotionEffect(PotionEffectType.RAID_OMEN)) { - p.removePotionEffect(PotionEffectType.RAID_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - if (cleared) { - p.sendMessage("§c§l[区域防护] §f已清除袭击相关效果"); - } - } - - public Map> - getPendingClearEffects() { - return pendingClearEffects; - } - - public void onPlayerOffline(UUID uid, String playerName, - String areaName) { - // 清除该玩家在该区域的所有效果 - List given = getPlayerEffects(uid, areaName); - for (PotionEffectType type : given) { - // 只清除我们给予的效果 - Player p = Bukkit.getPlayer(uid); - if (p != null) { - p.removePotionEffect(type); - } - } - // 清除DB记录 - removePlayerEffects(uid, areaName); - plugin.getLogger().info("[防护] " + playerName - + " 下线,已清除" + given.size() - + "个效果并清理DB"); - } - - @EventHandler - public void onTeleport(PlayerTeleportEvent e) { - Location from = e.getFrom(); - Location to = e.getTo(); - if (to == null) return; - - if (from.getWorld() == to.getWorld() - && from.distance(to) < 5) return; - - Player p = e.getPlayer(); - UUID uid = p.getUniqueId(); - - // 标记传送中 - teleporting.add(uid); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - teleporting.remove(uid); - }, 5L); - - AreaConfig fromArea = getArea( - from.getWorld().getName(), - from.getBlockX(), from.getBlockY(), - from.getBlockZ()); - AreaConfig toArea = getArea( - to.getWorld().getName(), - to.getBlockX(), to.getBlockY(), - to.getBlockZ()); - - String fromName = fromArea != null - ? fromArea.name : null; - String toName = toArea != null - ? toArea.name : null; - - /* plugin.getLogger().info( - "[防护-传送] ★★★进入onTeleport: " - + p.getName() - + " [" + fromName + "] → [" + toName + "]" - + " teleporting=" + teleporting.contains(uid));*/ - - if (Objects.equals(fromName, toName)) { - /* plugin.getLogger().info( - "[防护-传送] 区域相同, 跳过");*/ - return; - } - - // ===== 离开旧区域 ===== - if (fromName != null) { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★开始离开处理: " - + fromName);*/ - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★收标结果: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - /* plugin.getLogger().info( - "[防护-传送-离开] 标记项: " - + t.getName());*/ - } - } else { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★标记为null! " - + "尝试其他key查找...");*/ - // 遍历查找可能的标记 - for (Map.Entry> entry - : playerMarkedEffects.entrySet()) { - if (entry.getKey().equals(uid)) { - /* plugin.getLogger().info( - "[防护-传送-离开] 找到标记! 效果数=" - + entry.getValue().size());*/ - } - } - /* plugin.getLogger().info( - "[防护-传送-离开] playerMarkedEffects大小=" - + playerMarkedEffects.size());*/ - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 创建15秒延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = fromName; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★创建延时任务: " - + toClear.size() + "个效果");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-传送-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); -/* - plugin.getLogger().info( - "[防护-传送-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size() - + " 区域=" + clearArea);*/ - - if (online == null - || !online.isOnline()) { - plugin.getLogger().info( - "[防护-传送-清理] 玩家不在线, 清DB"); - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-传送-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-传送-清理] ★★★完成: 清除" - + cleared + "/" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - - } - - // 离开提示 - if (fromArea != null && fromArea.leaveMsg != null - && !fromArea.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(fromArea.leaveMsg)); - } - } - - // ===== 进入新区域 ===== - if (toName != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] ★★★开始进入处理: " - + toName);*/ - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - plugin.getLogger().info( - "[防护-传送-进入] 取消旧延时, 旧区域=" - + oldP); - if (oldP != null) { - diffEffects(p, uid, oldP, toName); - } - } - - // 进入提示 - if (toArea != null && toArea.enterMsg != null - && !toArea.enterMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(toArea.enterMsg)); - } - - // 发放效果 - if (toArea != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] 调用applyRegionEffects");*/ - applyRegionEffects(p, uid, toName, toArea); -// 清除效果 - clearBadEffects(p, toArea); - - if (toArea.confiscateItems != null - && !toArea.confiscateItems.isEmpty() - && !isExemptFromConfiscation( - p, toName)) { - confiscateItems(p, toArea); - } - } - - // 强制模式 - if (toArea != null && toArea.enforceGameMode != null - && !toArea.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, toName)) { - GameMode target = GameMode.valueOf( - toArea.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - // 更新记录 - if (toName != null) { - playerCurrentArea.put(uid, toName); - } else { - playerCurrentArea.remove(uid); - } - } - - - public Map getPendingClearTasks() { - return pendingClearTask; - } - - public Map> - getPlayerMarkedEffects() { - return playerMarkedEffects; - } - - public void handleInside(Player p, AreaConfig ac) { - clearBadEffects(p, ac); - - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - // 全白或区白 → 跳过所有限制性规则(包括没收) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - handleConfiscate(p, ac); - } - - public void cleanupExpiredProtections() { - protectedEntities.entrySet().removeIf( - entry -> System.currentTimeMillis() > entry.getValue()); - } - - // 禁止袭击:只驱逐袭击怪物 - private void banRaidMobs(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(32, 32, 32)) { - String t = ent.getType().name(); - if (t.equals("PILLAGER") - || t.equals("VINDICATOR") - || t.equals("EVOKER") - || t.equals("RAVAGER") - || t.equals("VEX") - || t.equals("WITCH") - || t.equals("ILLUSIONER") - || t.equals("ZOMBIE_VILLAGER")) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(voidLoc.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - public Set getAreaNames() { - return areas.keySet(); - } - - public Collection getPendingTasks() { - return pendingClearTask.values(); - } - - - // 判断是否为远程攻击敌对生物 - private boolean isRangedHostile(String typeName) { - switch (typeName) { - // ===== 骷髅系 ===== - case "SKELETON": // 骷髅(弓箭) - case "STRAY": // 流浪者(迟缓之箭) - case "BOGGED": // 虫蚀骷髅(毒箭) - case "WITHER_SKELETON": // 凋灵骷髅(近战但算灾厄) - - // ===== 灾厄村民 ===== - case "PILLAGER": // 掠夺者(弩) - case "ILLUSIONER": // 幻术师(弓箭+失明) - case "EVOKER": // 幻魔者(唤魔) - case "VINDICATOR": // 卫道士(斧头近战) - case "VEX": // 恼鬼(剑近战,但会穿墙飞行) - - // ===== 法术/投射 ===== - case "BLAZE": // 烈焰人(火球) - case "WITCH": // 女巫(药水) - - // ===== 飞行/爆炸 ===== - case "GHAST": // 恶魂(火球) - case "PHANTOM": // 幻翼(俯冲) - case "BIG_GHAST": // 大恶魂(1.21新增) - - // ===== 水下 ===== - case "GUARDIAN": // 守卫者(激光) - case "ELDER_GUARDIAN": // 远古守卫者(激光) - - // ===== 远程特殊 ===== - case "SHULKER": // 潜影贝(潜影弹) - case "BREEZE": // 旋风人(风弹) - - // ===== 蜘蛛系(远程毒液攻击) ===== - case "SPIDER": // 蜘蛛(白天远程吐丝) - case "CAVE_SPIDER": // 洞穴蜘蛛(毒) - - // ===== 1.21新增 ===== - case "WIND_CHARGED": // 蓄风怪(风弹) - case "WEAVING": // 盘丝怪(吐丝) - case "OOZING": // 渗浆怪(史莱姆) - case "INFESTED": // 寄生怪(蠹虫) - - // ===== 盔甲/变种(部分版本) ===== - case "ARMORED_SKELETON": // 盔甲骷髅 - case "ARCHER_SKELETON": // 弓箭骷髅(部分版本) - case "SNOW_GOLEM": // 雪傀儡(不是敌对但远程) - - // ===== 亡灵骑乘 ===== - case "HUSK": // 尸壳(近战但属于灾厄系) - case "DROWNED": // 溺尸(三叉戟远程) - case "ZOMBIE_VILLAGER": // 僵尸村民 - case "ZOMBIE_PIGMAN": // 僵尸猪灵 - case "ZOMBIFIED_PIGLIN": // 僵尸猪灵(1.16+) - case "PIGLIN_BRUTE": // 猪灵蛮兵 - - // ===== 猪灵(弓箭) ===== - case "PIGLIN": // 猪灵(金弩远程) - - // ===== 末影系 ===== - case "ENDER_DRAGON": // 末影龙(远程吐息) - case "WARDEN": // 监守者(远程音波) - - // ===== 其他 ===== - case "CREPER": // 苦力怕(爆炸不算远程但危险) - case "CREEPER": // 苦力怕 - - return true; - default: - return false; - } - } - - - - private void applyEffects(Player p, AreaConfig ac) { - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - - PotionEffectType type = resolveEffectType(effName); - plugin.getLogger().info("[防护调试] 给予: " - + effName + " -> " + type); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - plugin.getLogger().info("[防护调试] 已给予: " - + effName + " 等级" + level - + " 持续" + duration + "秒"); - } - } - } - - - @EventHandler - public void onBlockPlace(BlockPlaceEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockPlace - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止放置方块"); - } - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockBreak - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏方块"); - } - } - - - @EventHandler - public void onDamage(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - // 保护性规则:对所有人无条件生效 - if (ac.denyAllDamage) { e.setCancelled(true); return; } - if (ac.denyFallDamage - && e.getCause() == EntityDamageEvent.DamageCause.FALL) - e.setCancelled(true); - } - - - @EventHandler - public void onPVP(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyPVP - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - - @EventHandler - public void onFood(FoodLevelChangeEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyHunger) - e.setCancelled(true); - } - - - - @EventHandler - public void onDrop(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyDrop - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止丢弃物品"); - } - } - - // 替换整个 onInteract 方法 - @EventHandler - public void onInteract(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (hand == null) return; - - // ===== 选区工具 ===== - if (hand.getType() == Material.BREEZE_ROD) { - ItemMeta meta = hand.getItemMeta(); - if (meta != null && meta.getDisplayName() != null - && meta.getDisplayName().contains("区域选择")) { - Action action = e.getAction(); - boolean isLeft = (action == Action.LEFT_CLICK_AIR - || action == Action.LEFT_CLICK_BLOCK); - boolean isRight = (action == Action.RIGHT_CLICK_AIR - || action == Action.RIGHT_CLICK_BLOCK); - if (isLeft || isRight) { - e.setCancelled(true); - Block block = e.getClickedBlock(); - if (block == null) { - try { block = p.getTargetBlockExact(5); } - catch (Exception ignored) {} - } - if (block == null) return; - UUID uid = p.getUniqueId(); - if (isLeft) { - pos1.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置1: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } else { - pos2.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置2: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } - } - return; - } - } - - // ===== 放置实体类物品时不检查(展示框、矿车、船、地图等)===== - Material mat = hand.getType(); - if (mat == Material.ITEM_FRAME - || mat == Material.GLOW_ITEM_FRAME - || mat == Material.MINECART - || mat == Material.TNT_MINECART - || mat == Material.CHEST_MINECART - || mat == Material.HOPPER_MINECART - || mat == Material.OAK_BOAT - || mat == Material.SPRUCE_BOAT - || mat == Material.BIRCH_BOAT - || mat == Material.JUNGLE_BOAT - || mat == Material.ACACIA_BOAT - || mat == Material.DARK_OAK_BOAT - || mat == Material.MANGROVE_BOAT - || mat == Material.CHERRY_BOAT - || mat == Material.BAMBOO_RAFT - || mat == Material.MAP - || mat == Material.ARMOR_STAND) { - return; - } - - // ===== 区域规则检查 ===== - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - - // 末影珍珠禁止 - if (ac.denyEnderPearl - && hand.getType() == Material.ENDER_PEARL) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用末影珍珠"); - return; - } - - // 弓箭禁止 - if (ac.denyBow - && (hand.getType() == Material.BOW - || hand.getType() == Material.CROSSBOW)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用弓箭"); - return; - } - - // 白名单跳过 - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 禁止使用物品 - String typeId = hand.getType().name(); - if (ac.denyUseItems.contains(typeId)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用此物品"); - return; - } - - // 没收检查 - handleConfiscate(p, ac); - } - - - - @EventHandler - public void onExplosion(EntityExplodeEvent e) { - Iterator it = e.blockList().iterator(); - while (it.hasNext()) { - Block b = it.next(); - AreaConfig ac = getArea( - b.getWorld().getName(), - b.getX(), b.getY(), b.getZ()); - if (ac != null && ac.denyExplosion) - it.remove(); - } - } - public void loadWhitelists() { - globalPlayerWhitelist.clear(); - areaPlayerWhitelist.clear(); - - if (!whitelistDir.exists()) { - whitelistDir.mkdirs(); - return; - } - - try { - // 加载全局白名单 - if (globalWhitelistFile.exists()) { - List lines = - java.nio.file.Files.readAllLines( - globalWhitelistFile.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - globalPlayerWhitelist.add(name); - } - } - } - - // 加载各区域白名单 - File[] files = whitelistDir.listFiles( - (File d, String n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (files != null) { - for (File f : files) { - String areaName = f.getName() - .replace(".txt", ""); - Set set = - ConcurrentHashMap.newKeySet(); - List lines = - java.nio.file.Files.readAllLines( - f.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - set.add(name); - } - } - if (!set.isEmpty()) { - areaPlayerWhitelist.put( - areaName, set); - } - } - } - - plugin.getLogger().info( - "[防护] 白名单加载完成,全局: " - + globalPlayerWhitelist.size() - + ",区域数: " - + areaPlayerWhitelist.size()); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载白名单失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onPotionSplash(PotionSplashEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - AreaConfig ac = getArea( - shooter.getWorld().getName(), - shooter.getLocation().getBlockX(), - shooter.getLocation().getBlockY(), - shooter.getLocation().getBlockZ()); - if (ac != null && ac.denyPotion - && !isPlayerWhitelisted(shooter.getName(), ac)) { - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止使用药水"); - } - } - - /** - * 解析区域名:先精准匹配,再模糊匹配 - * @param input 用户输入的区域名 - * @return 匹配到的区域名,无匹配返回null - */ - public String resolveAreaName(String input) { - if (input == null || input.isEmpty()) return null; - if (input.equalsIgnoreCase("global")) return "global"; - - // 第一步:精准匹配 - for (String name : areas.keySet()) { - if (name.equalsIgnoreCase(input)) { - return name; - } - } - - // 第二步:包含匹配(用户只打了部分名字) - for (String name : areas.keySet()) { - if (name.contains(input) || input.contains(name)) { - return name; - } - } - - // 第三步:编辑距离模糊匹配(著称→主城) - String bestMatch = null; - int bestDistance = Integer.MAX_VALUE; - for (String name : areas.keySet()) { - int dist = levenshteinDistance( - input.toLowerCase(), name.toLowerCase()); - if (dist < bestDistance) { - bestDistance = dist; - bestMatch = name; - } - } - - // 容错阈值:编辑距离 <= 名字长度的一半 - if (bestMatch != null) { - int threshold = Math.max(1, bestMatch.length() / 2); - if (bestDistance <= threshold) { - return bestMatch; - } - } - - return null; - } - - /** - * 判断字符串是否是区域名(精准或模糊) - */ - private boolean isAreaName(String name) { - if (name.equalsIgnoreCase("global")) return false; - return resolveAreaName(name) != null; - } - - /** - * 获取模糊匹配的实际区域名(用于提示用户) - */ - private String getResolvedName(String input) { - return resolveAreaName(input); - } - - /** - * 编辑距离算法(莱文斯坦距离) - */ - private int levenshteinDistance(String s1, String s2) { - int[][] dp = new int[s1.length() + 1][s2.length() + 1]; - for (int i = 0; i <= s1.length(); i++) dp[i][0] = i; - for (int j = 0; j <= s2.length(); j++) dp[0][j] = j; - for (int i = 1; i <= s1.length(); i++) { - for (int j = 1; j <= s2.length(); j++) { - if (s1.charAt(i - 1) == s2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], - Math.min(dp[i - 1][j], dp[i][j - 1])); - } - } - } - return dp[s1.length()][s2.length()]; - } - - /** - * 智能重排参数:找出子命令位置,重排为标准顺序 - * 标准顺序:/protect 子命令 参数1 参数2 ... - */ - private String[] smartReorderArgs(String[] original) { - if (original.length <= 1) return original; - - Set knownSubs = new HashSet<>(Arrays.asList( - "add", "remove", "additem", "removeitem", - "addname", "removename", "listname", - "addwhite", "removewhite", "listwhite", - "list", "listitem", "创建", "删除", "重载", - "工具", "expand", "contraction", - "on", "off", "tempon", "modeexempt" - )); - - String first = original[0].toLowerCase(); - if (knownSubs.contains(first)) { - return original; - } - - int subIndex = -1; - for (int i = 0; i < original.length; i++) { - if (knownSubs.contains(original[i].toLowerCase())) { - subIndex = i; - break; - } - } - - if (subIndex < 0) { - return original; - } - - String[] reordered = new String[original.length]; - reordered[0] = original[subIndex]; - int idx = 1; - for (int i = 0; i < original.length; i++) { - if (i == subIndex) continue; - reordered[idx] = original[i]; - idx++; - } - - return reordered; - } - - // ==================== 管理命令 ==================== - public boolean handleCommand(CommandSender sender, - String[] args) { - args = smartReorderArgs(args); - if (args.length == 0) { - showHelp(sender); - return true; - } - String sub = args[0].toLowerCase(); -// ===== 诊断命令 ===== - if (sub.equals("sdf1debug")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - sender.sendMessage("§e===== 调试信息 ====="); - sender.sendMessage("§7当前区域: " - + playerCurrentArea.get(uid)); - sender.sendMessage("§7标记效果数: " - + (playerMarkedEffects.containsKey(uid) - ? playerMarkedEffects.get(uid).size() - : "无标记")); - sender.sendMessage("§7待清理任务: " - + pendingClearTask.containsKey(uid)); - sender.sendMessage("§7区域数: " + areas.size()); - - if (playerMarkedEffects.containsKey(uid)) { - List marked = - playerMarkedEffects.get(uid); - for (PotionEffectType type : marked) { - sender.sendMessage("§7 标记: " - + type.getName() - + " 身上有=" - + (p.getPotionEffect(type) != null)); - } - } - return true; - } - -// ===== 强制清理命令(测试用)===== - if (sub.equals("testclear")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - List marked = - playerMarkedEffects.remove(uid); - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - sender.sendMessage("§a已强制清理 " - + marked.size() + " 个效果"); - } else { - sender.sendMessage("§c无标记效果可清理"); - } - - // 取消延时任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - sender.sendMessage("§a已取消延时任务"); - } - - removePlayerEffects(uid, - playerCurrentArea.getOrDefault(uid, "")); - sender.sendMessage("§aDB已清理"); - return true; - } - - // ===== 工具 ===== - if (sub.equals("工具") || sub.equals("wand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - ItemStack tool = new ItemStack(WAND); - ItemMeta meta = tool.getItemMeta(); - meta.setDisplayName("§a§l区域选择工具"); - meta.setLore(Arrays.asList( - "§7左键: 位置1", "§7右键: 位置2")); - tool.setItemMeta(meta); - p.getInventory().addItem(tool); - p.sendMessage("§a§l[防护] §f已获取选择工具"); - return true; - } - - // ===== 创建 ===== - if (sub.equals("创建") || sub.equals("create")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 创建 <名>"); - return true; - } - Player p = (Player) sender; - UUID u = p.getUniqueId(); - if (!pos1.containsKey(u) - || !pos2.containsKey(u)) { - p.sendMessage("§c请先用工具选点"); - return true; - } - String areaName = args[1]; - File f = new File(rootDir, areaName + ".txt"); - if (f.exists()) { - p.sendMessage("§c区域已存在"); - return true; - } - Location l1 = pos1.get(u); - Location l2 = pos2.get(u); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 区域: " + areaName); - pw.println("起点: " + l1.getWorld().getName() - + "," + l1.getBlockX() - + "," + l1.getBlockZ()); - pw.println("终点: " + l2.getWorld().getName() - + "," + l2.getBlockX() - + "," + l2.getBlockZ()); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("# 禁止放置方块"); - pw.println("# 禁止破坏方块"); - pw.println("# 禁止PVP"); - pw.println("# 效果: 夜视 1 999"); - pw.println("# 进入提示: 欢迎来到保护区"); - pw.println("# 离开提示: 已离开保护区"); - pw.close(); - p.sendMessage("§a§l[防护] §f区域 " - + areaName + " 已创建"); - loadAllAreas(); - } catch (IOException ex) { - p.sendMessage("§c创建失败"); - } - return true; - } - - // ===== 和平白名单 ===== - if (sub.equals("addname")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别: " - + args[1] + " 或 " + args[2]); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 和平白名单"); - return true; - } - sender.sendMessage("§c用法: /protect addname " - + "<区域> <名字>"); - return true; - } - if (sub.equals("removename")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " + parsed[0]); - return true; - } - sender.sendMessage("§c用法: /protect removename " - + "<区域> <名字>"); - return true; - } - if (sub.equals("listname")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listname <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 和平白名单:"); - if (ac.peaceWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.peaceWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 模式排除 ===== - if (sub.equals("addwhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect addwhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("removewhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect removewhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("listwhite")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listwhite <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 模式排除名单:"); - if (ac.modeExempt.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.modeExempt) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 列表 ===== - if (sub.equals("列表") || sub.equals("list")) { - if (args.length == 2) { - if (args[1].equalsIgnoreCase("global")) { - sender.sendMessage("§a全局白名单:"); - if (globalPlayerWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n - : globalPlayerWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - String an = args[1]; - Set wl = - areaPlayerWhitelist.get(an); - sender.sendMessage("§a" + an + " 白名单:"); - if (wl == null || wl.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : wl) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - if (areas.isEmpty()) { - sender.sendMessage("§7暂无区域"); - } else { - sender.sendMessage("§e§l区域列表:"); - for (Map.Entry en - : areas.entrySet()) { - sender.sendMessage("§a " + en.getKey() - + " §7规则:" - + en.getValue().ruleCount() - + "条"); - } - } - return true; - } - - // ===== 重载 ===== - if (sub.equals("重载") || sub.equals("reload")) { - reload(); - sender.sendMessage("§a已重载 " - + areas.size() + " 个区域"); - return true; - } - - // ===== 物品黑名单 ===== - if (sub.equals("additem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + args[1]); - return true; - } - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + itemName); - return true; - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - } else { - areaItemBlacklist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()) - .add(itemName); - sender.sendMessage("§a已加黑: " - + itemName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect additem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("removeitem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (!globalItemBlacklist - .contains(itemName)) { - sender.sendMessage( - "§c全局黑名单中没有: " - + itemName); - return true; - } - globalItemBlacklist.remove(itemName); - sender.sendMessage("§a已移除: " + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.remove(itemName); - } else { - Set list = - areaItemBlacklist.get(areaName); - if (list != null) { - list.remove(itemName); - } - } - sender.sendMessage("§a已移除: " - + itemName); - return true; - } - sender.sendMessage("§c用法: /protect removeitem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("listitem")) { - if (args.length < 2) { - sender.sendMessage( - "§a全局物品黑名单:"); - if (globalItemBlacklist.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item - : globalItemBlacklist) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - Set list = - areaItemBlacklist.get(args[1]); - sender.sendMessage("§a" + args[1] - + " 物品黑名单:"); - if (list == null || list.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item : list) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - - // ===== expand 扩建 ===== - if (sub.equals("expand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ -= amount; - else if (dir.equals("南")) maxZ += amount; - else if (dir.equals("东")) maxX += amount; - else if (dir.equals("西")) minX -= amount; - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f扩建 §e" - + amount + "§f格"); - return true; - } - - // ===== contract 收缩 ===== - if (sub.equals("contraction") - || sub.equals("contract")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ += amount; - else if (dir.equals("南")) maxZ -= amount; - else if (dir.equals("东")) maxX -= amount; - else if (dir.equals("西")) minX += amount; - if (minX > maxX || minZ > maxZ) { - p.sendMessage("§c收缩过度"); - return true; - } - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f收缩 §e" - + amount + "§f格"); - return true; - } - - // ===== 删除 ===== - if (sub.equals("删除") || sub.equals("delete")) { - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 删除 <名>"); - return true; - } - File f = new File(rootDir, args[1] + ".txt"); - if (f.exists()) { - f.delete(); - areas.remove(args[1]); - sender.sendMessage("§a已删除: " + args[1]); - } else { - sender.sendMessage("§c不存在: " + args[1]); - } - return true; - } - - // ===== add 加白名单 ===== - if (sub.equals("add")) { - if (args.length == 2) { - String name = args[1]; - if (globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 已在全局白名单中"); - return true; - } - globalPlayerWhitelist.add(name); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " - + playerName); - } else { - Set wl = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()); - wl.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已加白: " - + playerName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect add " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== remove 移除白名单 ===== - if (sub.equals("remove")) { - if (args.length == 2) { - String name = args[1]; - if (!globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 不在全局白名单中"); - return true; - } - globalPlayerWhitelist.remove(name); - saveWhitelists(); - sender.sendMessage("§a已从全局白名单移除: " - + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.remove(playerName); - saveWhitelists(); - sender.sendMessage("§a已移除: " - + playerName); - } else { - Set wl = - areaPlayerWhitelist.get(areaName); - if (wl != null) { - wl.remove(playerName); - } - saveWhitelists(); - sender.sendMessage("§a已从 " + areaName - + " 移除: " + playerName); - } - return true; - } - sender.sendMessage("§c用法: /protect remove " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== on/off/tempon 边框 ===== - if (sub.equals("on")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, false); - p.sendMessage("§a§l[防护] §f已显示 " - + ac.name + " 边框"); - return true; - } - if (sub.equals("off")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - cancelBorder((Player) sender); - ((Player) sender).sendMessage( - "§a§l[防护] §f已关闭边框显示"); - return true; - } - if (sub.equals("tempon")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, true); - p.sendMessage("§a§l[防护] §f临时显示 " - + ac.name + " 边框(15秒)"); - return true; - } - - showHelp(sender); - return true; - } - - - private String[] parseAreaAndTarget(String arg1, String arg2) { - String r1 = resolveAreaName(arg1); - if (r1 != null) { - return new String[]{r1, arg2}; - } - String r2 = resolveAreaName(arg2); - if (r2 != null) { - return new String[]{r2, arg1}; - } - return null; - } - - private boolean playerExists(String playerName) { - // 在线玩家 - for (Player online : Bukkit.getOnlinePlayers()) { - if (online.getName().equalsIgnoreCase(playerName)) return true; - } - // 离线玩家 - @SuppressWarnings("deprecation") - OfflinePlayer op = Bukkit.getOfflinePlayer(playerName); - return op.hasPlayedBefore(); - } - - - private boolean validateArea(CommandSender sender, String areaName) { - if (!areas.containsKey(areaName)) { - sender.sendMessage("§c区域不存在: " + areaName); - return false; - } - return true; - } - - - private String matname(Material mat) { - String name = mat.name(); - String[] parts = name.split("_"); - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (sb.length() > 0) sb.append(" "); - sb.append(part.substring(0, 1).toUpperCase()) - .append(part.substring(1).toLowerCase()); - } - return sb.toString(); - } - private String yawToDir(float yaw) { - // MC中: 0=南, 90=西, 180=北, -90/270=东 - yaw = yaw % 360; - if (yaw < 0) yaw += 360; - if (yaw >= 315 || yaw < 45) return "南"; - if (yaw >= 45 && yaw < 135) return "西"; - if (yaw >= 135 && yaw < 225) return "北"; - return "东"; - } - private void saveAreaConfig(AreaConfig ac) { - File f = new File(rootDir, ac.name + ".txt"); - if (!f.exists()) return; - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - if (line.trim().startsWith("起点:")) { - lines.add("起点: " + ac.world - + "," + ac.x1 + "," + ac.z1); - } else if (line.trim().startsWith("终点:")) { - lines.add("终点: " + ac.world - + "," + ac.x2 + "," + ac.z2); - } else { - lines.add(line); - } - } - br.close(); - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - for (String l : lines) pw.println(l); - pw.close(); - } catch (IOException ignored) {} - } - - private void showHelp(CommandSender s) { - s.sendMessage("§e§l==== 区域防护 ===="); - s.sendMessage("§a/protect 工具 §7选地工具"); - s.sendMessage("§a/protect 创建 <名> §7创建区域"); - s.sendMessage("§a/protect 列表 §7区域列表"); - s.sendMessage("§a/protect 重载 §7重载配置"); - s.sendMessage("§a/protect 删除 <名> §7删除区域"); - s.sendMessage("§a/protect add [区域] <玩家> §7玩家加白"); - s.sendMessage("§a/protect remove [区域] <玩家> §7玩家删白"); - s.sendMessage("§a/protect additem [区域] <物品> §7物品加黑"); - s.sendMessage("§a/protect removeitem [区域] <物品> §7物品删黑"); - s.sendMessage("§a/protect on §7显示边框"); - s.sendMessage("§a/protect off §7关闭边框"); - s.sendMessage("§a/protect tempon §7临时边框(15秒)"); - s.sendMessage("§a/protect list §7列出全局白名单"); - s.sendMessage("§a/protect list <区域> §7列出区域白名单"); - s.sendMessage("§a/protect listitem §7列出全局物品黑名单"); - s.sendMessage("§a/protect listitem <区域> §7列出区域物品黑名单"); - - } -// ==================== 白名单管理公共方法 ==================== - - // ==================== 白名单操作 ==================== - - public boolean isPlayerGlobalWhitelisted( - String playerName) { - return globalPlayerWhitelist.contains(playerName); - } - - public boolean addPlayerToGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean isPlayerAreaWhitelisted( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - return wl != null && wl.contains(playerName); - } - - public boolean addPlayerToAreaWhitelist( - String areaName, String playerName) { - Set set = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap.newKeySet()); - boolean ok = set.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromAreaWhitelist( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - boolean ok = wl != null && wl.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - - - public int handleConfiscate(Player p, AreaConfig ac) { - if (isPlayerWhitelisted(p.getName(), ac)) return 0; - Set allItems = new HashSet<>(ac.confiscateItems); - allItems.addAll(globalItemBlacklist); - Set areaList = areaItemBlacklist.get(ac.name); - if (areaList != null) allItems.addAll(areaList); - - if (allItems.isEmpty()) return 0; - - int totalRemoved = 0; - String confiscatedNames = ""; - - for (String itemId : allItems) { - // 过滤掉区域名和无效ID - Material mat = Material.matchMaterial(itemId); - if (mat == null) { - plugin.getLogger().warning( - "[防护] 跳过无效物品: " + itemId); - continue; - } - ItemStack[] all = p.getInventory().getContents(); - int count = 0; - for (int i = 0; i < all.length; i++) { - if (all[i] != null && all[i].getType() == mat) { - count += all[i].getAmount(); - all[i] = null; - } - } - p.getInventory().setContents(all); - - ItemStack off = p.getInventory().getItemInOffHand(); - if (off != null && off.getType() == mat) { - count += off.getAmount(); - p.getInventory().setItemInOffHand(null); - } - - if (count > 0) { - totalRemoved += count; - if (!confiscatedNames.isEmpty()) - confiscatedNames += "§7、§e"; - confiscatedNames += "§e" + matname(mat) - + " x" + count; - } - } - - if (totalRemoved > 0) { - String msg = (ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty()) - ? ac.confiscateMsg - : "你携带了违禁品,已被没收"; - p.sendMessage("§c§l[区域防护] §f" + msg); - p.sendMessage("§7没收物品: " + confiscatedNames); - - if (ac.enableAnnounce - && ac.announceTemplate != null - && !ac.announceTemplate.isEmpty()) { - String ann = ac.announceTemplate - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)) - .replace("{items}", confiscatedNames); - Bukkit.broadcastMessage("§c§l[区域防护] §f" + ann); - } - for (String cmd : ac.punishCommands) { - String finalCmd = cmd - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)); - Bukkit.getScheduler().runTask(plugin, - () -> Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), finalCmd)); - } - } - return totalRemoved; - } - public void clearBlacklists() { - globalItemBlacklist.clear(); - areaItemBlacklist.clear(); - plugin.getLogger().info("[防护] 已清空所有黑名单"); - } - - @EventHandler - public void onEntityDamage( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑 ===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - // 展示框被点击(旋转/放入/取出) - @EventHandler - public void onItemFrameInteract( - PlayerInteractAtEntityEvent e) { - Entity clicked = e.getRightClicked(); - String typeName = clicked.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - Player p = e.getPlayer(); - - // 全局白放行 - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(clicked); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - // 非白名单 → 拦截 - // 记录当前旋转角度 - final Rotation savedRotation; - final Entity frame = clicked; - if (clicked instanceof org.bukkit.entity.ItemFrame) { - savedRotation = - ((org.bukkit.entity.ItemFrame) clicked) - .getRotation(); - } else { - savedRotation = null; - } - - e.setCancelled(true); - - // 下一tick强制恢复旋转 - if (savedRotation != null) { - final Rotation restore = savedRotation; - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (frame.isValid() - && frame instanceof org.bukkit.entity.ItemFrame) { - ((org.bukkit.entity.ItemFrame) frame) - .setRotation(restore); - } - }, 1L); - } - - p.sendMessage("§c§l[区域防护] §f禁止交互展示框"); - - // 驱逐远程敌对生物 - banRangedHostiles(p, ac); - } - - - - // 展示框保护:检查展示框本身+周围方块所属区域 - - // ===== 展示框攻击拦截(左键/投射物破坏)===== - @EventHandler - public void onEntityDamageByEntity( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - // 展示框被攻击 - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑(原有的,不动)===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - private AreaConfig findFrameArea(Entity frame) { - Location loc = frame.getLocation(); - int bx = loc.getBlockX(); - int by = loc.getBlockY(); - int bz = loc.getBlockZ(); - String world = loc.getWorld().getName(); - - AreaConfig ac = getArea(world, bx, by, bz); - if (ac != null && ac.denyItemFrame) return ac; - - int[][] offsets = { - {0, 0, 1}, {0, 0, -1}, - {1, 0, 0}, {-1, 0, 0}, - {0, 1, 0}, {0, -1, 0} - }; - for (int[] off : offsets) { - ac = getArea(world, - bx + off[0], by + off[1], bz + off[2]); - if (ac != null && ac.denyItemFrame) return ac; - } - return null; - } - - // ===== 投射物击中展示框(弓箭/弩)===== - @EventHandler - public void onProjectileHit( - org.bukkit.event.entity.ProjectileHitEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - Entity hit = e.getHitEntity(); - if (hit == null) return; - String typeName = hit.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(shooter.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(hit); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(shooter.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - } - - - // ===== 区域外火焰不能蔓延进区域 ===== - @EventHandler - public void onBlockSpread(BlockSpreadEvent e) { - Block block = e.getBlock(); - AreaConfig tgtArea = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (tgtArea != null && tgtArea.denyFireSpread) { - e.setCancelled(true); - } - } - - // ===== 区域内禁止点燃方块 ===== - @EventHandler - public void onIgnite(BlockIgniteEvent e) { - Player p = e.getPlayer(); - if (p == null) return; // 非玩家点燃的(闪电等) - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f此区域禁止点燃物品"); - } - } - - // ===== 区域内方块着火(TNT、岩浆等)也阻止 ===== - @EventHandler - public void onBlockBurn(BlockBurnEvent e) { - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) - e.setCancelled(true); - } - - - private void savePeaceWhitelist(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("和平") - && trimmed.contains("白名单")) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.peaceWhitelist.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存和平白名单失败: " + e.getMessage()); - } - } - - - @EventHandler - public void onPaintingPlace( - org.bukkit.event.player.PlayerBucketEmptyEvent e) { - // 暂不处理 - } - - - // ==================== 数据类 ==================== - - public static class AreaConfig { - public String name = ""; - public String world = ""; - public int x1, z1, x2, z2; - public int yMin = 0, yMax = 255; - public List confiscateItems = new ArrayList<>(); - public List denyUseItems = new ArrayList<>(); - public List clearEffects = new ArrayList<>(); // 清除单个效果名 - public boolean clearAllBadEffects = false; // 清除所有负面+中性 - public List giveEffects = new ArrayList<>(); // [效果名, 等级, 秒数] - public List punishCommands = new ArrayList<>(); - public boolean denyBlockPlace = false; - public boolean denyBlockBreak = false; - public boolean denyPVP = false; - public boolean denyFallDamage = false; - public boolean denyHunger = false; - public boolean denyAllDamage = false; - public boolean denyMount = false; - public boolean denyDrop = false; - public boolean denyEnderPearl = false; - public boolean denyBow = false; - public boolean denyPotion = false; - public boolean denyExplosion = false; - public boolean enableAnnounce = false; - public String announceTemplate = ""; - public String confiscateMsg = ""; - public String enterMsg = ""; - public String leaveMsg = ""; - public boolean denyRaid = false; - public boolean denyFireSpread = false; - public boolean denyAllEffects = false;; - public boolean peaceMode = false; - public boolean denyItemFrame = false; - public Set peaceWhitelist = new HashSet<>(); - public String enforceGameMode = null; // 强制游戏模式 - public Set modeExempt = new HashSet<>(); // 模式排除名单 - - - - - private static final Map EFFECT_MAP - = new HashMap<>(); - static { - // 负面效果 - EFFECT_MAP.put("缓慢", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("凋零", PotionEffectType.WITHER); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("霉运", PotionEffectType.UNLUCK); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("蓄风", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生", PotionEffectType.INFESTED); - // 中性效果 - EFFECT_MAP.put("不祥之兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("不祥征兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("袭击征兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("试炼征兆", PotionEffectType.TRIAL_OMEN); - // 正面效果 - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); - EFFECT_MAP.put("瞬间治疗", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("跳跃提升", PotionEffectType.JUMP_BOOST); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性提升", PotionEffectType.RESISTANCE); - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("生命提升", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("村庄英雄", PotionEffectType.HERO_OF_THE_VILLAGE); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); - EFFECT_MAP.put("潮涌能量", PotionEffectType.CONDUIT_POWER); - EFFECT_MAP.put("海豚的恩惠", PotionEffectType.DOLPHINS_GRACE); - - - // 带效果二字 - // 补充带"效果"后缀的别名 - EFFECT_MAP.put("黑暗效果", PotionEffectType.DARKNESS); - EFFECT_MAP.put("不祥之兆效果", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆效果", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆效果", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("缓慢效果", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("中毒效果", PotionEffectType.POISON); - EFFECT_MAP.put("凋零效果", PotionEffectType.WITHER); - EFFECT_MAP.put("失明效果", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿效果", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱效果", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("反胃效果", PotionEffectType.NAUSEA); - EFFECT_MAP.put("飘浮效果", PotionEffectType.LEVITATION); - EFFECT_MAP.put("发光效果", PotionEffectType.GLOWING); - EFFECT_MAP.put("霉运效果", PotionEffectType.UNLUCK); - EFFECT_MAP.put("蓄风效果", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝效果", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆效果", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生效果", PotionEffectType.INFESTED); - EFFECT_MAP.put("挖掘疲劳效果", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害效果", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("村庄英雄效果", PotionEffectType.HERO_OF_THE_VILLAGE); - } - - - public int ruleCount() { - int c = 0; - if (!confiscateItems.isEmpty()) c++; - if (denyBlockPlace) c++; - if (denyBlockBreak) c++; - if (denyPVP) c++; - if (denyFallDamage) c++; - if (denyHunger) c++; - if (denyAllDamage) c++; - if (denyDrop) c++; - if (denyMount) c++; - if (denyEnderPearl) c++; - if (denyBow) c++; - if (denyRaid) c++; - if (denyPotion) c++; - if (denyExplosion) c++; - if (denyFireSpread) c++; - if (denyAllEffects) c++; - if (!clearEffects.isEmpty()) c++; - if (clearAllBadEffects) c++; - if (!giveEffects.isEmpty()) c++; - if (enableAnnounce) c++; - if (!punishCommands.isEmpty()) c++; - if (!denyUseItems.isEmpty()) c++; - if (peaceMode) c++; - if (denyItemFrame) c++; - if (!peaceWhitelist.isEmpty()) c++; - - return c; - } - } - } \ No newline at end of file diff --git a/backup/src/main/java/Sdf1_login/BondManager.java b/backup/src/main/java/Sdf1_login/BondManager.java deleted file mode 100644 index ea78152..0000000 --- a/backup/src/main/java/Sdf1_login/BondManager.java +++ /dev/null @@ -1,697 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import java.io.File; -import java.sql.*; -import java.util.*; - -public class BondManager { - - private final Main plugin; - private Connection db; - - // ===== 交易类型常量 ===== - public static final String TX_ADMIN_GIVE = "admin_give"; - public static final String TX_ADMIN_DEDUCT = "admin_deduct"; - public static final String TX_TRANSFER_OUT = "transfer_out"; - public static final String TX_TRANSFER_IN = "transfer_in"; - public static final String TX_REDEEM = "redeem"; - public static final String TX_SHOP_BUY = "shop_buy"; - public static final String TX_DAILY_SIGN = "daily_sign"; - public static final String TX_FREEZE = "freeze"; - public static final String TX_UNFREEZE = "unfreeze"; - - - public static final int MAX_WATER_DAYS = 14; - public static final int ADMIN_MAX_WATER_DAYS = 30; - - public BondManager(Main plugin) { - this.plugin = plugin; - initDB(); - } - - // ======================== 数据库初始化 ======================== - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), "bond.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 债券余额(含冻结状态) - st.execute("CREATE TABLE IF NOT EXISTS bonds (" - + "player_name TEXT PRIMARY KEY," - + "amount INTEGER DEFAULT 0," - + "status TEXT DEFAULT 'normal')"); - - // 兼容旧表 - try { - st.execute("ALTER TABLE bonds " - + "ADD COLUMN status " - + "TEXT DEFAULT 'normal'"); - } catch (SQLException ignored) {} - - // 旧流水(保留兼容) - st.execute("CREATE TABLE IF NOT EXISTS bond_log (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "amount INTEGER NOT NULL," - + "reason TEXT DEFAULT ''," - + "time INTEGER DEFAULT 0)"); - - // 新交易流水表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "bond_transaction (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "type TEXT NOT NULL," - + "amount INTEGER NOT NULL," - + "target_player TEXT DEFAULT ''," - + "operator TEXT DEFAULT ''," - + "reason TEXT DEFAULT ''," - + "balance_after INTEGER DEFAULT 0," - + "time INTEGER DEFAULT 0)"); - - try { - st.execute("CREATE INDEX " - + "IF NOT EXISTS idx_tx_pt " - + "ON bond_transaction(" - + "player_name, time)"); - } catch (SQLException ignored) {} - try { - st.execute("ALTER TABLE bond_transaction " - + "ADD COLUMN balance_before " - + "INTEGER DEFAULT 0"); - } catch (SQLException ignored) {} - - - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[Bond] DB init failed: " - + e.getMessage(), e); - } - } - - // ======================== 账户状态 ======================== - - public boolean isFrozen(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT status FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String s = rs.getString("status"); - rs.close(); ps.close(); - return "frozen".equals(s); - } - rs.close(); ps.close(); - } catch (SQLException ignored) {} - return false; - } - - public String getAccountStatus(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT status FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String s = rs.getString("status"); - rs.close(); ps.close(); - return s != null ? s : "normal"; - } - rs.close(); ps.close(); - } catch (SQLException ignored) {} - return "normal"; - } - - public void freezeAccount(String player) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET status='frozen' " - + "WHERE player_name=?"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - } - - public void unfreezeAccount(String player) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET status='normal' " - + "WHERE player_name=?"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - } - - private void ensureAccount(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR IGNORE INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,0,'normal')"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException ignored) {} - } - - // ======================== 余额查询 ======================== - - public int getBonds(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT amount FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - int v = 0; - if (rs.next()) v = rs.getInt("amount"); - rs.close(); ps.close(); - return v; - } catch (SQLException e) { return 0; } - } - - /** 该玩家是否在 bond 表中有真实记录 */ - public boolean hasBondAccount(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM bonds " - + "WHERE player_name=? " - + "AND amount > 0"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - boolean exists = rs.next(); - rs.close(); ps.close(); - return exists; - } catch (SQLException e) { - return false; - } - } - - // ======================== 增加债券 ======================== - - public int addBonds(String player, int amount) { - return addBondsInternal(player, amount, - TX_ADMIN_GIVE, "", "", - "管理员发放"); - } - - public int addBonds(String player, int amount, - String type, - String targetPlayer, - String operator, - String reason) { - if (isFrozen(player)) return -1; - return addBondsInternal(player, amount, - type, targetPlayer, operator, reason); - } - - private int addBondsInternal(String player, - int amount, - String type, - String targetPlayer, - String operator, - String reason) { - int before = getBonds(player); - int after = before + amount; - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status)" - + " VALUES (?,?, 'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, after); - ps.setInt(3, after); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - logTransaction(player, type, amount, - targetPlayer, operator, reason, - before, after); - logLegacy(player, amount); - return after; - } - - // ======================== 扣除债券 ======================== - - - - private boolean deductBondsInternal(String player, - int amount, - String type, - String targetPlayer, - String operator, - String reason) { - int before = getBonds(player); - if (before < amount) return false; - int after = before - amount; - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET amount=? " - + "WHERE player_name=?"); - ps.setInt(1, after); - ps.setString(2, player); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - logTransaction(player, type, amount, - targetPlayer, operator, reason, - before, after); - logLegacy(player, -amount); - return true; - } - - // ======================== 流水写入 ======================== - - private void logTransaction(String player, - String type, - int amount, - String targetPlayer, - String operator, - String reason, - int balanceBefore, - int balanceAfter) { - try { - // 合并模式:shop_buy 30秒内同商品合并 - if ("shop_buy".equals(type) - && reason != null - && !reason.isEmpty()) { - long mergeWindow = - System.currentTimeMillis() - 30000L; - PreparedStatement chk = - db.prepareStatement( - "SELECT id, amount " - + "FROM bond_transaction " - + "WHERE player_name=? " - + "AND type=? " - + "AND reason=? " - + "AND time > ? " - + "ORDER BY time DESC " - + "LIMIT 1"); - chk.setString(1, player); - chk.setString(2, type); - chk.setString(3, reason); - chk.setLong(4, mergeWindow); - ResultSet rs = chk.executeQuery(); - if (rs.next()) { - int existId = rs.getInt("id"); - int existAmt = rs.getInt("amount"); - rs.close(); - chk.close(); - PreparedStatement upd = - db.prepareStatement( - "UPDATE bond_transaction " - + "SET amount=?, " - + "balance_after=?, " - + "time=? " - + "WHERE id=?"); - upd.setInt(1, existAmt + amount); - upd.setInt(2, balanceAfter); - upd.setLong(3, - System.currentTimeMillis()); - upd.setInt(4, existId); - upd.executeUpdate(); - upd.close(); - return; - } - rs.close(); - chk.close(); - } - - // 原始插入 - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bond_transaction " - + "(player_name, type, amount," - + " target_player, operator," - + " reason, balance_before," - + " balance_after, time)" - + " VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, player); - ps.setString(2, type); - ps.setInt(3, amount); - ps.setString(4, - targetPlayer != null ? targetPlayer : ""); - ps.setString(5, - operator != null ? operator : ""); - ps.setString(6, - reason != null ? reason : ""); - ps.setInt(7, balanceBefore); - ps.setInt(8, balanceAfter); - ps.setLong(9, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - // ======================== 扣除债券 ======================== - - /** 完整版:含冻结检查 + 流水记录 */ - public boolean deductBonds(String player, int amount, - String type, - String targetPlayer, - String operator, - String reason) { - if (isFrozen(player)) return false; - return deductBondsInternal(player, amount, - type, targetPlayer, operator, reason); - } - - public boolean checkoutCart(String player, - String[] itemNames, - int[] subtotals) { - int total = 0; - for (int s : subtotals) total += s; - if (isFrozen(player)) return false; - - int before = getBonds(player); - if (before < total) return false; - int after = before - total; - - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,?,'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, after); - ps.setInt(3, after); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - - int running = before; - for (int i = 0; i < itemNames.length; i++) { - int next = running - subtotals[i]; - logTransaction(player, "shop_buy", subtotals[i], - "", "商店系统", - "购物车购买-" + itemNames[i], - running, next); - running = next; - } - - logLegacy(player, -total); - return true; - } - - public boolean setBonds(String player, int amount) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,?, 'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, amount); - ps.setInt(3, amount); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return true; - } - - // ======================== 转账 ======================== - - public TransferResult transfer(String from, - String to, - int amount) { - if (from.equalsIgnoreCase(to)) - return new TransferResult(false, - "不能给自己转账"); - if (isFrozen(from)) - return new TransferResult(false, - "你的账户已被冻结"); - if (isFrozen(to)) - return new TransferResult(false, - to + " 的账户已被冻结"); - if (isSameIP(from, to)) - return new TransferResult(false, - "不能与同IP玩家转账"); - - if (!deductBondsInternal(from, amount, - TX_TRANSFER_OUT, to, from, - "转账给" + to)) - return new TransferResult(false, "债券不足"); - - addBondsInternal(to, amount, - TX_TRANSFER_IN, from, from, - "收到来自" + from + "的转账"); - - return new TransferResult(true, "转账成功"); - } - - // ======================== 同IP检测 ======================== - - public boolean isSameIP(String p1, String p2) { - String ip1 = getPlayerIPAny(p1); - String ip2 = getPlayerIPAny(p2); - if (ip1 == null || ip2 == null) return false; - return ip1.equals(ip2); - } - - /** 获取同IP下的所有其他在线玩家 */ - public List getSameIPPlayers(String player) { - String ip = getPlayerIPAny(player); - if (ip == null) return Collections.emptyList(); - List list = new ArrayList<>(); - for (Player p : - plugin.getServer().getOnlinePlayers()) { - String pIp = plugin.getPlayerIP(p); - if (pIp != null && pIp.equals(ip) - && !p.getName() - .equalsIgnoreCase(player)) { - list.add(p.getName()); - } - } - return list; - } - - private String getPlayerIPAny(String player) { - // 在线:直接取 - Player p = plugin.getServer() - .getPlayerExact(player); - if (p != null) { - String ip = plugin.getPlayerIP(p); - if (ip != null) return ip; - } - // 离线:查主库 - try { - Object v = plugin.getDb() - .getField(player, "last_login_ip"); - if (v != null) return v.toString(); - } catch (Exception ignored) {} - try { - Object v = plugin.getDb() - .getField(player, "register_ip"); - if (v != null) return v.toString(); - } catch (Exception ignored) {} - return null; - } - - // ======================== 流水查询 ======================== - - public List> getTransactions( - String player, int days) { - days = Math.min(days, MAX_WATER_DAYS); - long cutoff = System.currentTimeMillis() - - (long) days * 24 * 60 * 60 * 1000; - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "AND time>=? " - + "ORDER BY time DESC"); - ps.setString(1, player); - ps.setLong(2, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("operator", - rs.getString("operator")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return list; - } -// 放在 getTransactions 方法后面: - - /** - * 获取该玩家全部流水(不限时间,供打印使用) - */ - public List> - getAllTransactions(String player) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "ORDER BY time ASC"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("player_name", - rs.getString("player_name")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("operator", - rs.getString("operator")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } -// 放在 getAllTransactions 方法后面: - - /** - * 获取所有有流水记录的玩家名(去重) - */ - public List getAllPlayerNames() { - List list = new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT DISTINCT player_name " - + "FROM bond_transaction " - + "ORDER BY player_name ASC"); - while (rs.next()) { - list.add(rs.getString("player_name")); - } - rs.close(); st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTransferTransactions( - String player, int days) { - days = Math.min(days, MAX_WATER_DAYS); - long cutoff = System.currentTimeMillis() - - (long) days * 24 * 60 * 60 * 1000; - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "AND time>=? " - + "AND (type=? OR type=?) " - + "ORDER BY time DESC"); - ps.setString(1, player); - ps.setLong(2, cutoff); - ps.setString(3, TX_TRANSFER_OUT); - ps.setString(4, TX_TRANSFER_IN); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return list; - } - - - private void logLegacy(String player, int amount) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bond_log " - + "(player_name,amount,time) " - + "VALUES (?,?,?)"); - ps.setString(1, player); - ps.setInt(2, amount); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); ps.close(); - } catch (SQLException ignored) {} - } - - public Map getLog( - String player, int limit) { - Map info = new LinkedHashMap<>(); - info.put("balance", getBonds(player)); - return info; - } - - // ======================== 转账结果 ======================== - - public static class TransferResult { - public final boolean success; - public final String message; - public TransferResult(boolean success, - String message) { - this.success = success; - this.message = message; - } - } -} diff --git a/backup/src/main/java/Sdf1_login/BondPrinter.java b/backup/src/main/java/Sdf1_login/BondPrinter.java deleted file mode 100644 index 674ec3c..0000000 --- a/backup/src/main/java/Sdf1_login/BondPrinter.java +++ /dev/null @@ -1,378 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -public class BondPrinter { - - private final Main plugin; - private final File outputDir; - - private static final SimpleDateFormat FILE_SDF = - new SimpleDateFormat( - "yyyy-MM-dd_HH-mm-ss"); - private static final SimpleDateFormat CELL_SDF = - new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - private static final LinkedHashMap - TYPE_NAMES = new LinkedHashMap<>(); - - static { - TYPE_NAMES.put("admin_give", "管理员发放"); - TYPE_NAMES.put("admin_deduct", "管理员扣除"); - TYPE_NAMES.put("transfer_out", "转出"); - TYPE_NAMES.put("transfer_in", "转入"); - TYPE_NAMES.put("redeem", "CDK兑换"); - TYPE_NAMES.put("shop_buy", "商城购买"); - TYPE_NAMES.put("shop_sell", "商城出售"); // ← 补上 - TYPE_NAMES.put("daily_sign", "签到奖励"); - TYPE_NAMES.put("freeze", "冻结"); - TYPE_NAMES.put("unfreeze", "解冻"); - - } - - public BondPrinter(Main plugin) { - this.plugin = plugin; - this.outputDir = new File( - plugin.getDataFolder(), "printer"); - if (!this.outputDir.exists()) { - this.outputDir.mkdirs(); - } - } - - // ============ 打印单个玩家 ============ - - public void printPlayer(String player, - CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doPrintPlayer( - player, sender)); - } - - private void doPrintPlayer(String player, - CommandSender sender) { - try { - List> txs = - plugin.getBonds() - .getAllTransactions(player); - - String fileName = player + "_" - + FILE_SDF.format(new Date()) - + ".xls"; - File file = new File(outputDir, fileName); - - deleteOld(player); - - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8)); - try { - writeXmlHeader(pw, - escXml(player)); - writeXmlRow(pw, - new String[]{ - "时间", "类型", - "金额", "对象", - "操作者", "理由", - "操作前余额", - "操作后余额"}); - - for (Map tx : txs) { - writeXmlRow(pw, new String[]{ - txTime(tx), - txTypeName(tx), - String.valueOf( - txInt(tx, - "amount")), - txStr(tx, - "target_player"), - txStr(tx, "operator"), - txStr(tx, "reason"), - String.valueOf( - txInt(tx, - "balance_before")), - String.valueOf( - txInt(tx, - "balance_after")) - }); - } - - writeXmlFooter(pw); - pw.flush(); - } finally { - pw.close(); - } - - if (sender != null) { - sender.sendMessage("§a[打印] §f" - + player + " §a流水已导出"); - sender.sendMessage("§7文件: §f" - + fileName); - sender.sendMessage("§7共 §f" - + txs.size() - + " §7条,§724小时后删除"); - } - } catch (Exception e) { - if (sender != null) { - sender.sendMessage("§c导出失败: " - + e.getMessage()); - } - plugin.getLogger().severe( - "[Printer] " + e.getMessage()); - } - } - - // ============ 打印全服 ============ - - public void printAll(CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doPrintAll(sender)); - } - - private void doPrintAll(CommandSender sender) { - try { - List players = - plugin.getBonds() - .getAllPlayerNames(); - if (players.isEmpty()) { - if (sender != null) { - sender.sendMessage("§c没有流水数据"); - } - return; - } - - String fileName = "全服流水_" - + FILE_SDF.format(new Date()) - + ".xls"; - File file = new File(outputDir, fileName); - - // ===== 收集所有流水 ===== - // key = 时间戳+玩家+金额,用于去重 - // value = 一行数据 - List allRows = new ArrayList<>(); - Set seenKeys = new HashSet<>(); - - for (String player : players) { - List> txs = - plugin.getBonds() - .getAllTransactions(player); - for (Map tx : txs) { - int txId = ((Number) tx.get("id")) - .intValue(); - String dedupKey = - String.valueOf(txId); - if (!seenKeys.add(dedupKey)) { - continue; - } - - String timeStr = txTime(tx); - long timeMs = 0; - Object t = tx.get("time"); - if (t != null) { - timeMs = - ((Number) t).longValue(); - } - - allRows.add(new String[]{ - player, - timeStr, - txTypeName(tx), - String.valueOf( - txInt(tx, "amount")), - txStr(tx, "target_player"), - txStr(tx, "operator"), - txStr(tx, "reason"), - String.valueOf( - txInt(tx, - "balance_before")), - String.valueOf( - txInt(tx, - "balance_after")), - // 额外存时间戳用于排序 - String.valueOf(timeMs) - }); - } - } - - // ===== 按时间戳排序 ===== - allRows.sort((a, b) -> { - long ta = Long.parseLong( - a[a.length - 1]); - long tb = Long.parseLong( - b[b.length - 1]); - return Long.compare(ta, tb); - }); - - // ===== 写入文件 ===== - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8)); - try { - writeXmlHeader(pw, "全服流水"); - writeXmlRow(pw, new String[]{ - "玩家", "时间", "类型", - "金额", "对象", "操作者", - "理由", "操作前余额", - "操作后余额" - }); - - for (String[] row : allRows) { - // 去掉最后的时间戳列 - String[] cells = - new String[row.length - 1]; - System.arraycopy(row, 0, - cells, 0, cells.length); - writeXmlRow(pw, cells); - } - - writeXmlFooter(pw); - pw.flush(); - - if (sender != null) { - sender.sendMessage( - "§a[打印] §f全服流水 §a已导出"); - sender.sendMessage("§7文件: §f" - + fileName); - sender.sendMessage("§7共 §f" - + players.size() - + " 名玩家,§f" - + allRows.size() + " §7条"); - sender.sendMessage( - "§7按时间从早到晚排序"); - sender.sendMessage( - "§724小时后自动删除"); - } - } finally { - pw.close(); - } - } catch (Exception e) { - if (sender != null) { - sender.sendMessage("§c导出失败: " - + e.getMessage()); - } - plugin.getLogger().severe( - "[Printer] " + e.getMessage()); - } - } - - // ============ 清理 ============ - - public void cleanupOldFiles() { - long now = System.currentTimeMillis(); - long maxAge = 24L * 60 * 60 * 1000; - File[] files = outputDir.listFiles( - (d, n) -> n.endsWith(".xls")); - if (files == null) { - return; - } - int count = 0; - for (File f : files) { - if (now - f.lastModified() > maxAge) { - if (f.delete()) { - count++; - } - } - } - if (count > 0) { - plugin.getLogger().info( - "[Printer] 清理 " - + count + " 个过期文件"); - } - } - - private void deleteOld(String player) { - File[] old = outputDir.listFiles( - (d, n) -> n.startsWith(player + "_") - && n.endsWith(".xls")); - if (old != null) { - for (File f : old) { - f.delete(); - } - } - } - - // ============ XML工具 ============ - - private void writeXmlHeader(PrintWriter pw, - String sheetName) { - pw.println(""); - pw.println(""); - pw.println(""); - pw.println(" "); - pw.println(" "); - } - - private void writeXmlRow(PrintWriter pw, - String[] cells) { - pw.println(" "); - for (String cell : cells) { - pw.println(" " - + "" - + escXml(cell != null ? cell : "") - + ""); - } - pw.println(" "); - } - - private void writeXmlFooter(PrintWriter pw) { - pw.println("
"); - pw.println("
"); - pw.println("
"); - } - - private String escXml(String val) { - if (val == null) { - return ""; - } - return val.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """); - } - - private String txTime(Map tx) { - Object t = tx.get("time"); - if (t == null) return ""; - long ms = ((Number) t).longValue(); - return CELL_SDF.format(new Date(ms)); - } - - private String txTypeName( - Map tx) { - String type = (String) tx.get("type"); - return TYPE_NAMES.getOrDefault(type, type); - } - - private int txInt(Map tx, - String key) { - Object v = tx.get(key); - if (v == null) return 0; - return ((Number) v).intValue(); - } - - private String txStr(Map tx, - String key) { - Object v = tx.get(key); - return v != null ? v.toString() : ""; - } -} diff --git a/backup/src/main/java/Sdf1_login/CDKManager.java b/backup/src/main/java/Sdf1_login/CDKManager.java deleted file mode 100644 index e07569f..0000000 --- a/backup/src/main/java/Sdf1_login/CDKManager.java +++ /dev/null @@ -1,293 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.*; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.nio.charset.StandardCharsets; - -public class CDKManager { - - private final Main plugin; - private Connection db; - private final Map pendingInputs - = new ConcurrentHashMap<>(); - - public CDKManager(Main plugin) { - this.plugin = plugin; - initDB(); - } - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), "bond.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS cdk (" - + "code TEXT PRIMARY KEY," - + "amount INTEGER DEFAULT 0," - + "type TEXT DEFAULT 'bond'," - + "used INTEGER DEFAULT 0," - + "used_by TEXT DEFAULT ''," - + "created_time INTEGER DEFAULT 0," - + "used_time INTEGER DEFAULT 0)"); - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[CDK] DB init failed: " - + e.getMessage(), e); - } - } - - // ===== CDK兑换(供SDF1调用) ===== - - /** - * 兑换CDK,返回结果字符串 - * @return "success:金额:余额前:余额后" - * 或 "fail:原因" - */ - public String redeem(String code, String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT amount,type,used FROM cdk " - + "WHERE code=?"); - ps.setString(1, code); - ResultSet rs = ps.executeQuery(); - if (!rs.next()) { - rs.close(); ps.close(); - return "fail:not_found"; - } - int amount = rs.getInt("amount"); - String type = rs.getString("type"); - int used = rs.getInt("used"); - rs.close(); ps.close(); - - if (used == 1) - return "fail:already_used"; - - if (plugin.getBonds() - .isFrozen(player)) - return "fail:frozen"; - - PreparedStatement up = db.prepareStatement( - "UPDATE cdk SET used=1," - + "used_by=?,used_time=? " - + "WHERE code=?"); - up.setString(1, player); - up.setLong(2, - System.currentTimeMillis()); - up.setString(3, code); - up.executeUpdate(); up.close(); - - if ("bond".equals(type)) { - int bef = plugin.getBonds() - .getBonds(player); - plugin.getBonds().addBonds(player, - amount, - BondManager.TX_REDEEM, - "", "CDK系统", - "CDK兑换: " + code); - int aft = plugin.getBonds() - .getBonds(player); - return "success:" + amount - + ":" + bef + ":" + aft; - } - return "fail:unknown_type"; - } catch (SQLException e) { - return "fail:" + e.getMessage(); - } - } - - // ===== 聊天输入系统 ===== - - public void requestInput(Player p, - String type, - String context) { - CDKInput input = new CDKInput(); - input.type = type; - input.context = context; - input.time = System.currentTimeMillis(); - pendingInputs.put(p.getName(), input); - } - - public boolean isListening(String playerName) { - return pendingInputs - .containsKey(playerName); - } - - public void onChat(Player p, String msg) { - CDKInput input = pendingInputs - .remove(p.getName()); - if (input == null) return; - - if (System.currentTimeMillis() - - input.time > 60000) { - p.sendMessage("§c输入超时"); - return; - } - - if ("cdk".equals(input.type)) { - String result = redeem( - msg.trim(), p.getName()); - if (result != null - && result.startsWith("success:")) { - String[] parts = result.split(":"); - int amt = Integer.parseInt(parts[1]); - int bef = Integer.parseInt(parts[2]); - int aft = Integer.parseInt(parts[3]); - p.sendMessage("§e§l[CDK] §f兑换成功!" - + "获得 §c" + amt + "§f债券"); - p.sendMessage("§7余额: §e" + bef - + " §7-> §a" + aft); - } else { - p.sendMessage("§c§l[CDK] §f" - + (result != null - ? result.replace("fail:", "") - : "无效的兑换码")); - } - return; - } - - if ("econ".equals(input.type)) { - handleEconInput(p, msg.trim(), - input.context); - return; - } - - if ("bond".equals(input.type)) { - handleBondInput(p, msg.trim(), - input.context); - } - } - - private void handleEconInput(Player admin, - String msg, - String target) { - net.milkbowl.vault.economy.Economy econ - = null; - try { - var reg = plugin.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault - .economy - .Economy.class); - if (reg != null) - econ = reg.getProvider(); - } catch (Exception ignored) {} - if (econ == null) { - admin.sendMessage("§c经济系统不可用"); - return; - } - if (!msg.startsWith("+") - && !msg.startsWith("-")) { - admin.sendMessage("§c格式: §a+100" - + " §7或 §c-50"); - return; - } - try { - String numStr = - msg.substring(1).trim(); - double amount = - Double.parseDouble(numStr); - if (amount <= 0) { - admin.sendMessage("§c金额必须>0"); - return; - } - if (msg.startsWith("-")) { - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp == null) { - admin.sendMessage("§c" + target - + " 不在线"); - return; - } - if (!econ.has(tp, amount)) { - admin.sendMessage("§c余额不足"); - return; - } - econ.withdrawPlayer(tp, amount); - admin.sendMessage("§a扣款成功"); - } else { - econ.depositPlayer(target, amount); - admin.sendMessage("§a到账成功"); - } - } catch (NumberFormatException e) { - admin.sendMessage("§c格式错误"); - } - } - - private void handleBondInput(Player admin, - String msg, - String target) { - if (!msg.startsWith("+") - && !msg.startsWith("-")) { - admin.sendMessage("§c格式: §a+100" - + " §7或 §c-50"); - return; - } - try { - String sign = msg.substring(0, 1); - String rest = - msg.substring(1).trim(); - String[] parts = - rest.split("\\s+", 2); - int amount = Integer.parseInt(parts[0]); - String reason = parts.length >= 2 - ? parts[1].trim() - : "管理员手动调整"; - if (amount <= 0) { - admin.sendMessage("§c金额必须>0"); - return; - } - String opName = admin.getName(); - String txType = "-".equals(sign) - ? BondManager.TX_ADMIN_DEDUCT - : BondManager.TX_ADMIN_GIVE; - - if ("-".equals(sign)) { - if (plugin.getBonds() - .isFrozen(target)) { - admin.sendMessage("§c账户冻结"); - return; - } - if (plugin.getBonds().deductBonds( - target, amount, txType, - "", opName, reason)) { - admin.sendMessage("§a扣除成功"); - } else { - admin.sendMessage("§c债券不足"); - } - } else { - if (plugin.getBonds() - .isFrozen(target)) { - admin.sendMessage("§c账户冻结"); - return; - } - plugin.getBonds().addBonds(target, - amount, txType, - "", opName, reason); - admin.sendMessage("§a发放成功"); - } - } catch (NumberFormatException e) { - admin.sendMessage("§c格式错误"); - } - } - - public void cancelInput(String player) { - pendingInputs.remove(player); - } - - static class CDKInput { - String type; - String context; - long time; - } -} diff --git a/backup/src/main/java/Sdf1_login/ChatFilterManager.java b/backup/src/main/java/Sdf1_login/ChatFilterManager.java deleted file mode 100644 index 59cb5de..0000000 --- a/backup/src/main/java/Sdf1_login/ChatFilterManager.java +++ /dev/null @@ -1,612 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.*; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ChatFilterManager { - - private final JavaPlugin plugin; - private final List whitelistUrls = - new ArrayList<>(); - private final Set whitelistPlayers = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map - punishmentRules = - new LinkedHashMap<>(); - private final Map - punishmentDurations = - new LinkedHashMap<>(); - private final Map - violationCount = - new ConcurrentHashMap<>(); - private final Map - mutedPlayers = - new ConcurrentHashMap<>(); - private final Map messages = - new LinkedHashMap<>(); - private boolean notifyAdmin = true; - private boolean notifyAll = false; - private int muteDuration = 300; - private boolean enabled = true; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean v) { - enabled = v; - } - - - private static final Pattern URL_PATTERN = - Pattern.compile( - "(?i)(?:https?://\\S+)" - + "|(?:(?:[a-zA-Z0-9]" - + "(?:[a-zA-Z0-9\\-]*" - + "[a-zA-Z0-9])?\\.)+" - + "[a-zA-Z]{2,})" - + "(?:/\\S*)?"); - private static final Pattern DOT_VARIANTS = - Pattern.compile( - "[\uff0e\u3002\u2025\u2026\u00b7]"); - private static final Pattern COLOR_CODES = - Pattern.compile( - "\u00a7[0-9a-fk-orA-FK-OR]"); - - private static final Set FILE_EXT; - static { - Set s = new HashSet<>( - Arrays.asList( - "txt", "yml", "yaml", - "json", "xml", "java", - "class", "jar", "png", - "jpg", "jpeg", "gif", - "bmp", "svg", "ico", - "webp", "mp3", "mp4", - "avi", "mkv", "wav", - "flac", "ogg", "zip", - "rar", "7z", "tar", - "gz", "cfg", "conf", - "ini", "log", "db", - "sql", "sh", "bat", - "html", "htm", "css", - "js", "md", "pdf", - "dll", "so", "exe", - "bin", "c", "cpp", - "py", "rb", "go", - "rs", "kt", "lua", - "php", "asp", "jsp")); - FILE_EXT = Collections.unmodifiableSet(s); - } - - public ChatFilterManager(JavaPlugin plugin) { - this.plugin = plugin; - } - - // ========== 配置加载 ========== - - public void loadConfig() { - whitelistUrls.clear(); - whitelistPlayers.clear(); - punishmentRules.clear(); - punishmentDurations.clear(); - messages.clear(); - - File f = new File( - plugin.getDataFolder(), "chat.txt"); - if (!f.exists()) writeDefaultFile(f); - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("白名单") - || t.equals("白名单:")) { - section = "whitelist"; - continue; - } - if (t.equals("处罚规则") - || t.equals("处罚规则:")) { - section = "punishment"; - continue; - } - if (t.equals("白名单玩家") - || t.equals("白名单玩家:")) { - section = "players"; - continue; - } - if (t.contains(":")) { - String[] kv = t.split(":", 2); - String k = kv[0].trim(); - String v = kv[1].trim(); - if (equals("启用过滤") - || equals("enabled")) { - enabled = parseBool(v); - continue; - } - - if (k.equals("通知管理员")) { - notifyAdmin = parseBool(v); - continue; - } - if (k.equals("全服通报")) { - notifyAll = parseBool(v); - continue; - } - if (k.equals("禁言时长")) { - try { - muteDuration = - Integer.parseInt(v); - } catch (Exception ignored) { - } - continue; - } - } - switch (section) { - case "whitelist": - whitelistUrls.add( - t.toLowerCase()); - break; - case "punishment": - String[] parts = t.split(":"); - if (parts.length >= 2) { - try { - int cnt = - Integer.parseInt( - parts[0] - .trim()); - String type = - parts[1].trim() - .toLowerCase(); - int dur = - parts.length >= 3 - ? Integer.parseInt( - parts[2] - .trim()) - : muteDuration; - punishmentRules.put( - cnt, type); - punishmentDurations.put( - cnt, dur); - } catch (Exception ignored) { - } - } - break; - case "players": - whitelistPlayers.add(t); - break; - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[Sdf1_chat] 配置加载失败: " - + e.getMessage()); - } - - // 从消息.txt读取chat_开头的消息 - loadMessages(); - } - - private void loadMessages() { - File msgFile = new File( - plugin.getDataFolder(), - "消息.txt"); - if (!msgFile.exists()) return; - try (BufferedReader mr = - new BufferedReader( - new InputStreamReader( - new FileInputStream( - msgFile), - StandardCharsets.UTF_8))) { - String mline; - while ((mline = mr.readLine()) != null) { - mline = mline.trim(); - if (mline.isEmpty() - || mline.startsWith("#")) - continue; - // 支持 = 和 : 两种分隔符 - int eqIdx = mline.indexOf('='); - int coIdx = mline.indexOf(':'); - int splitIdx = -1; - if (eqIdx >= 0 && coIdx >= 0) { - splitIdx = Math.min(eqIdx, coIdx); - } else if (eqIdx >= 0) { - splitIdx = eqIdx; - } else if (coIdx >= 0) { - splitIdx = coIdx; - } - if (splitIdx < 0) continue; - String k = mline.substring(0, - splitIdx).trim(); - String v = mline.substring( - splitIdx + 1).trim(); - if (k.startsWith("chat_")) { - messages.put(k, v); - } - } - } catch (Exception ignored) { - } - } - - - private void writeDefaultFile(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("启用过滤: " + enabled); - pw.println("通知管理员: true"); - pw.println("全服通报: false"); - pw.println("禁言时长: 300"); - pw.println(); - pw.println("白名单:"); - pw.println("*.minecraft.net"); - pw.println("baidu.com"); - pw.println("github.com"); - pw.println(); - pw.println("处罚规则:"); - pw.println("1:warn:0"); - pw.println("2:warn:0"); - pw.println("3:mute:300"); - pw.println("5:mute:600"); - pw.println("8:kick:0"); - pw.println("10:ban:0"); - pw.println(); - pw.println("白名单玩家:"); - } catch (IOException ignored) { - } - } - - public void saveConfig() { - File f = new File( - plugin.getDataFolder(), "chat.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("通知管理员: " + notifyAdmin); - pw.println("全服通报: " + notifyAll); - pw.println("禁言时长: " + muteDuration); - pw.println(); - pw.println("白名单:"); - for (String u : whitelistUrls) - pw.println(u); - pw.println(); - pw.println("处罚规则:"); - for (Map.Entry entry - : punishmentRules.entrySet()) { - int dur = punishmentDurations - .getOrDefault(entry.getKey(), - muteDuration); - pw.println(entry.getKey() + ":" - + entry.getValue() - + ":" + dur); - } - pw.println(); - pw.println("白名单玩家:"); - for (String p : whitelistPlayers) - pw.println(p); - } catch (IOException e) { - plugin.getLogger().severe( - "[Sdf1_chat] 保存失败: " - + e.getMessage()); - } - } - - // ========== 消息 ========== - - public String msg(String key, - String... args) { - String template = messages.getOrDefault( - key, key); - for (int i = 0; i < args.length - 1; - i += 2) { - template = template.replace( - "{" + args[i] + "}", - args[i + 1]); - } - return template; - } - - // ========== URL检测 ========== - - private String normalizeMessage(String msg) { - msg = COLOR_CODES.matcher(msg) - .replaceAll(""); - msg = DOT_VARIANTS.matcher(msg) - .replaceAll("."); - return msg; - } - - public List extractUrls( - String message) { - String normalized = - normalizeMessage(message); - List urls = new ArrayList<>(); - Matcher m = URL_PATTERN - .matcher(normalized); - while (m.find()) { - String url = m.group().toLowerCase(); - url = url.replaceAll( - "[\\s.,;!?,。;!?、)》」』】" - + "\\]\\[\\((]+$", - ""); - if (!url.isEmpty()) urls.add(url); - } - return urls; - } - - public boolean isWhitelisted(String url) { - String clean = url - .replaceAll("^https?://", "") - .toLowerCase(); - int colon = clean.indexOf(':'); - int slash = clean.indexOf('/'); - if (colon > 0 && (slash < 0 - || colon < slash)) { - clean = clean.substring(0, colon); - } - slash = clean.indexOf('/'); - if (slash > 0) { - clean = clean.substring(0, slash); - } - for (String entry : whitelistUrls) { - String e = entry.toLowerCase().trim(); - if (e.startsWith("*.")) { - String suffix = e.substring(1); - if (clean.endsWith(suffix) - || clean.equals( - suffix.substring(1))) - return true; - } else { - if (clean.equals(e) - || clean.endsWith( - "." + e)) - return true; - } - } - return false; - } - - public boolean isLikelyDomain(String url) { - int lastDot = url.lastIndexOf('.'); - if (lastDot < 0 - || lastDot >= url.length() - 1) - return false; - String ext = url.substring(lastDot + 1) - .toLowerCase(); - return !FILE_EXT.contains(ext); - } - - // ========== 玩家操作 ========== - - public boolean isMuted(String name) { - Long expiry = mutedPlayers.get(name); - if (expiry == null) return false; - if (System.currentTimeMillis() - >= expiry) { - mutedPlayers.remove(name); - return false; - } - return true; - } - - public String fmtDuration(int seconds) { - if (seconds <= 0) return "0秒"; - int day = seconds / 86400; - int hr = (seconds % 86400) / 3600; - int min = (seconds % 3600) / 60; - int sec = seconds % 60; - StringBuilder sb = new StringBuilder(); - if (day > 0) sb.append(day).append("天"); - if (hr > 0) - sb.append(hr).append("小时"); - if (min > 0) - sb.append(min).append("分"); - if (sec > 0 || sb.length() == 0) - sb.append(sec).append("秒"); - return sb.toString(); - } - - public void applyPunishment(Player player, - int violation) { - String type = "warn"; - int duration = muteDuration; - int maxRule = 0; - for (Map.Entry entry - : punishmentRules.entrySet()) { - if (violation >= entry.getKey() - && entry.getKey() > maxRule) { - maxRule = entry.getKey(); - type = entry.getValue(); - duration = punishmentDurations - .getOrDefault( - entry.getKey(), - muteDuration); - } - } - if (type.equals("mute")) { - if (isMuted(player.getName())) - return; - mutedPlayers.put(player.getName(), - System.currentTimeMillis() - + (long) duration - * 1000L); - } - final String fType = type; - final int fDur = duration; - final int fV = violation; - final String fName = player.getName(); - Bukkit.getScheduler().runTask(plugin, - () -> { - Player p = Bukkit.getPlayer(fName); - if (p == null) return; - switch (fType) { - case "warn": - // 警告不处罚,只提示 - break; - case "mute": - p.sendMessage( - msg("chat_muted")); - break; - case "kick": - p.kickPlayer( - msg("chat_muted")); - break; - case "ban": - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban " + fName - + " §c多次发送违规链接"); - break; - case "banip": - InetSocketAddress addr = - p.getAddress(); - if (addr != null) { - String ip = addr - .getAddress() - .getHostAddress(); - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban-ip " + ip - + " §c发送违规链接"); - } - break; - } - }); - } - - public void unmutePlayer(String name) { - mutedPlayers.remove(name); - } - - public void resetPlayer(String name) { - mutedPlayers.remove(name); - violationCount.remove(name); - } - - public boolean isPlayerWhitelisted( - String name) { - return whitelistPlayers.contains(name); - } - - public boolean isNotifyAdmin() { - return notifyAdmin; - } - - public boolean isNotifyAll() { - return notifyAll; - } - - public int getViolationCount(String name) { - return violationCount - .getOrDefault(name, 0); - } - - public void incrementViolation( - String name) { - violationCount.put(name, - getViolationCount(name) + 1); - } - - public List getWhitelistUrls() { - return whitelistUrls; - } - - public Set getWhitelistPlayers() { - return whitelistPlayers; - } - - public Map - getPunishmentRules() { - return punishmentRules; - } - - public Map - getPunishmentDurations() { - return punishmentDurations; - } - - public Map - getMutedPlayers() { - return mutedPlayers; - } - - public void setNotifyAdmin(boolean v) { - notifyAdmin = v; - } - - public void setNotifyAll(boolean v) { - notifyAll = v; - } - - public void setMuteDuration(int v) { - muteDuration = v; - } - - public void addUrl(String url) { - whitelistUrls.add( - url.toLowerCase().trim()); - saveConfig(); - } - - public boolean removeUrl(String url) { - boolean r = whitelistUrls.remove( - url.toLowerCase().trim()); - saveConfig(); - return r; - } - - public void addWhitelistPlayer( - String name) { - whitelistPlayers.add(name); - saveConfig(); - } - - public boolean removeWhitelistPlayer( - String name) { - boolean r = - whitelistPlayers.remove(name); - saveConfig(); - return r; - } - - public void cleanExpired() { - long now = System.currentTimeMillis(); - mutedPlayers.entrySet().removeIf( - e -> e.getValue() <= now); - } - - private boolean parseBool(String v) { - String s = v.toLowerCase().trim(); - return s.equals("true") - || s.equals("on") - || s.equals("yes") - || s.equals("1") - || s.contains("开启") - || s.contains("启用") - || s.contains("是"); - } -} diff --git a/backup/src/main/java/Sdf1_login/ChatInputManager.java b/backup/src/main/java/Sdf1_login/ChatInputManager.java deleted file mode 100644 index 49f53fd..0000000 --- a/backup/src/main/java/Sdf1_login/ChatInputManager.java +++ /dev/null @@ -1,842 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; - -public class ChatInputManager { - - public enum InputType { - NONE, - CHANGE_PWD_STEP1, - CHANGE_PWD_STEP2, - SET_EMAIL, - SET_EMAIL_VERIFY, - ADMIN_AUTH, - ADMIN_SET_POINTS, - ADMIN_SET_PWD, - ADMIN_DELETE_CONFIRM, - ADMIN_SET_AFK_TIME, - SMTP_CONFIG, - SMTP_VERIFY, - BACK_CHECKIN, - INVITE_INPUT_CODE, - TICKET_TITLE, - TICKET_DESC, - TICKET_SCORE, - TICKET_REJECT, - TICKET_ASSIGN, - TICKET_INVITE_PROVIDER, - TICKET_REMOVE_PROVIDER, - TICKET_COMPLETE, - TICKET_REPORT_ISSUE, - TICKET_REPLY, - TICKET_ADMIN_TYPE, - TICKET_ADMIN_TITLE, - TICKET_ADMIN_DESC, - TICKET_ADMIN_PROVIDER, - BACK_CHECK_ECONOMY_CONFIRM, - USER_MGMT_SEARCH, - - } - - public static class PlayerState { - public InputType type = InputType.NONE; - public String targetPlayer = ""; - public String ticketType = ""; - public String ticketTitle = ""; - public String ticketDesc = ""; - public int ticketId = 0; - public int smtpStep = 0; - public int scoreValue = 0; - - } - - private final Map states = - new HashMap<>(); - private Main plugin; - - public PlayerState getState(Player p) { - return states.computeIfAbsent( - p.getName(), - k -> new PlayerState()); - } - - public void reset(Player p) { - states.remove(p.getName()); - } - public boolean isInFlow(Player p) { - PlayerState state = states.get(p.getName()); - return state != null - && state.type != InputType.NONE; - } - - public void handleInput(Player p, String msg, - Main mainPlugin) { - if (this.plugin == null) - this.plugin = mainPlugin; - PlayerState state = getState(p); - ConfigManager config = - mainPlugin.getConfig2(); - DatabaseManager db = mainPlugin.getDb(); - - switch (state.type) { - - case CHANGE_PWD_STEP1: { - String name = p.getName(); - String salt = (String) db.getField( - name, "password_salt"); - String hash = - PasswordUtils.hash(msg, salt); - boolean mainOk = - db.checkPassword(name, hash); - if (mainOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "main"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - boolean tempOk = - db.checkPasswordOrTemp( - name, hash); - if (tempOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "temp"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - p.sendMessage(config.msg( - "password_wrong")); - return; - } - - case CHANGE_PWD_STEP2: { - String name = p.getName(); - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String oldHash = - (String) db.getField(name, - "password_hash"); - String oldSalt = - (String) db.getField(name, - "password_salt"); - String newSalt = - PasswordUtils.generateSalt(); - String newHash = - PasswordUtils.hash( - msg, newSalt); - if (newHash.equals(oldHash)) { - p.sendMessage(config.msg( - "password_same")); - reset(p); - return; - } - mainPlugin.recordPasswordChange( - name, oldHash, oldSalt); - db.setField(name, - "password_hash", newHash); - db.setField(name, - "password_salt", newSalt); - mainPlugin - .getNeedsPasswordChange() - .remove(name); - p.sendMessage(config.msg( - "password_changed")); - reset(p); - return; - } - - case SET_EMAIL: { - if (!msg.contains("@") - || !msg.contains(".")) { - p.sendMessage(config.msg( - "email_format_error")); - reset(p); - return; - } - String oldEmail = (String) - db.getField(p.getName(), - "email"); - String emailAddr = msg.trim(); - // 已绑定邮箱:验证登录密码 - if (oldEmail != null - && !oldEmail.isEmpty()) { - state.targetPlayer = emailAddr; - state.type = - InputType.SET_EMAIL_VERIFY; - state.ticketTitle = - "need_pwd"; - p.sendMessage( - "§e您已绑定邮箱: " - + oldEmail); - p.sendMessage( - "§e请验证登录密码以确认修改:"); - return; - } - // 未绑定邮箱:直接发验证码 - String code = String.valueOf( - (int) (Math.random() - * 900000 - + 100000)); - state.targetPlayer = emailAddr; - state.ticketTitle = code; - state.type = - InputType.SET_EMAIL_VERIFY; - final String to = emailAddr; - final String c = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - boolean sent = mainPlugin - .getEmail() - .sendVerifyCode( - to, c); - Bukkit.getScheduler() - .runTask(mainPlugin, - () -> { - if (sent) { - fp.sendMessage( - "§a验证码已发送到 " - + to); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败"); - reset(fp); - } - }); - }); - return; - } - - - case SET_EMAIL_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§c邮箱设置已取消"); - reset(p); - return; - } - String expected = - state.ticketTitle; - if (msg.trim().equals(expected)) { - db.setField(p.getName(), - "email", - state.targetPlayer); - p.sendMessage( - "§a邮箱已设置: " - + state.targetPlayer); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case ADMIN_AUTH: { - if (msg.equals( - config.adminPassword)) { - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openAdmin(p)); - } else { - p.sendMessage( - "§c管理密码错误"); - } - reset(p); - return; - } - - case ADMIN_SET_POINTS: { - try { - int pts = Integer.parseInt( - msg.trim()); - db.setField( - state.targetPlayer, - "points", pts); - p.sendMessage("§a已将 " - + state.targetPlayer - + " 积分设为 " + pts); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - String tgt = - state.targetPlayer; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openUserDetail( - p, tgt)); - reset(p); - return; - } - - case ADMIN_SET_PWD: { - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String tgt = state.targetPlayer; - String salt = - PasswordUtils - .generateSalt(); - String hash = - PasswordUtils.hash( - msg, salt); - db.setField(tgt, - "temp_password", hash); - db.setField(tgt, "temp_pw_expire", - System.currentTimeMillis() - + 300000L); - db.setField(tgt, - "temp_pw_used", 0); - p.sendMessage("§a已为 " + tgt - + " 设置临时密码"); - Player tp = - Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) - tp.kickPlayer( - "§c管理员已设置临时密码," - + "请使用临时密码登录"); - // 邮件通知 - String emailAddr = (String) - db.getField(tgt, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String time = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()); - String body = "玩家 " + tgt - + ":\n您的密码已于 " - + time - + " 被管理员重置为临时密码。\n" - + "如果是本人操作请忽略。\n" - + "如非本人操作请立即联系管理员。"; - final String to = emailAddr; - final String fBody = body; - final String fName = tgt; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> - mainPlugin.getEmail() - .sendTempPassword( - to, - fName, - fBody)); - } - reset(p); - return; - } - - case ADMIN_DELETE_CONFIRM: { - if (msg.equals( - state.targetPlayer)) { - db.deleteUser( - state.targetPlayer); - p.sendMessage("§a已删除 " - + state.targetPlayer); - Player tgt = - Bukkit.getPlayer( - state.targetPlayer); - if (tgt != null - && tgt.isOnline()) - tgt.kickPlayer( - "§c账号已被删除"); - } else { - p.sendMessage( - "§c输入不匹配,已取消"); - } - reset(p); - return; - } - - case ADMIN_SET_AFK_TIME: { - try { - int minutes = - Integer.parseInt( - msg.trim()); - config.afkTimeout = - minutes * 60; - config.saveSettings(); - p.sendMessage( - "§a挂机时长已设为 " - + minutes - + " 分钟"); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - reset(p); - return; - } - - case SMTP_CONFIG: { - String m = msg.trim(); - if ("000".equals(m) - || "取消取消取消" - .equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if ("取消".equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - int step = state.smtpStep; - if (step <= 1) { - if (!"0".equals(m)) - config.setSmtp( - "smtp地址", m); - state.smtpStep = 2; - p.sendMessage("§e当前端口: " - + config.getSmtp( - "smtp端口")); - p.sendMessage( - "§e输入端口(0跳过):"); - } else if (step == 2) { - if (!"0".equals(m)) - config.setSmtp( - "smtp端口", m); - state.smtpStep = 3; - p.sendMessage( - "§e输入发件邮箱:"); - } else if (step == 3) { - if (!"0".equals(m)) - config.setSmtp( - "smtp账号", m); - state.smtpStep = 4; - p.sendMessage( - "§e输入SMTP密码(0跳过):"); - } else if (step == 4) { - if (!"0".equals(m)) - config.setSmtp( - "smtp密码", m); - state.smtpStep = 5; - p.sendMessage( - "§e输入接收邮箱(留空发到发件邮箱):"); - } else if (step == 5) { - String recv = m; - if (recv.isEmpty() - || "0".equals(recv)) - recv = config.getSmtp( - "smtp账号"); - config.setSmtp( - "验证码接收邮箱", recv); - if (!config - .canSendSmtpVerify()) { - p.sendMessage( - "§c发送间隔不足1分钟"); - reset(p); - return; - } - String code = config - .generateSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String acct = config.getSmtp( - "smtp账号"); - if (acct.isEmpty() - || addr.isEmpty()) { - p.sendMessage( - "§c地址或账号为空"); - reset(p); - return; - } - config.recordSmtpSendTime(); - final String fRecv = recv; - final String fCode = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> fp.sendMessage( - "§e正在发送验证码...")); - boolean sent = - mainPlugin.getEmail() - .sendVerifyCode( - fRecv, - fCode); - final boolean fSent = sent; - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> { - if (fSent) { - state.type = - InputType - .SMTP_VERIFY; - state.smtpStep = 0; - fp.sendMessage( - "§a验证码已发送到 " - + fRecv); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败,查看控制台"); - reset(fp); - } - }); - }); - } - return; - } - - case SMTP_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if (config.checkSmtpVerifyCode( - msg.trim())) { - config.saveSmtp(); - config.clearSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String port = config.getSmtp( - "smtp端口"); - String acct = config.getSmtp( - "smtp账号"); - String recv = config.getSmtp( - "验证码接收邮箱"); - boolean ssl = config.smtpSsl; - p.sendMessage("§a§l=== SMTP配置完成 ==="); - p.sendMessage("§7地址: §f" + addr); - p.sendMessage("§7端口: §f" + port); - p.sendMessage("§7账号: §f" + acct); - p.sendMessage("§7接收: §f" + recv); - p.sendMessage("§7加密: §f" + ssl); - p.sendMessage("§a§l===================="); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case BACK_CHECKIN: { - String result = - mainPlugin.getCheckIn() - .backCheckIn(p, msg); - if (result == null) { - // 等待选择(NEED_CONFIRM) - return; - } - if (result.startsWith( - "NEED_CONFIRM:")) { - String[] parts = - result.split(":"); - // 存储到Main的Map - mainPlugin.pendingBackCheck.put( - p.getName(), - parts[1] + ":" + parts[2]); - return; - } - if (result.startsWith("§c")) { - p.sendMessage(result); - } else { - p.sendMessage(result); - } - reset(p); - return; - } - - - case INVITE_INPUT_CODE: { - String code = msg.trim(); - String myName = p.getName(); - String myCode = - (String) db.getField( - myName, - "invite_code"); - if (code.isEmpty()) { - p.sendMessage( - "§c邀请码不能为空"); - reset(p); - return; - } - if (code.equals(myCode)) { - p.sendMessage( - "§c不能用自己邀请码"); - reset(p); - return; - } - boolean found = false; - for (Map u : - db.getAllUsers()) { - String uc = (String) - u.getOrDefault( - "invite_code", ""); - if (code.equals(uc)) { - String inv = (String) - u.get("player_name"); - db.setField(myName, - "invited_by", inv); - db.addPoints(inv, 50); - p.sendMessage( - "§a邀请成功!邀请人: " - + inv); - Player ip = - Bukkit.getPlayer(inv); - if (ip != null - && ip.isOnline()) - ip.sendMessage("§a" - + myName - + " 使用了您的邀请码"); - found = true; - break; - } - } - if (!found) - p.sendMessage( - "§c邀请码不存在"); - reset(p); - return; - } - - case TICKET_TITLE: { - state.ticketTitle = msg; - state.type = - InputType.TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return; - } - - case TICKET_DESC: { - String tt = state.ticketType; - String tl = state.ticketTitle; - reset(p); - mainPlugin.getTicket() - .submitTicket( - p, tt, tl, msg); - return; - } - - case TICKET_SCORE: { - int tid = state.ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - reset(p); - mainPlugin.getTicket() - .confirmAndScore( - p, tid, sc); - } catch ( - NumberFormatException e) { - reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return; - } - - case TICKET_REJECT: { - int tid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .rejectTicket( - p, tid, msg); - return; - } - - case TICKET_ASSIGN: { - int tid = state.ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - reset(p); - mainPlugin.getTicket() - .assignToProvider( - p, tid, prov, - price); - final int fTid = tid; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getTicket() - .openDetail( - p, fTid)); - return; - } - - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return; - } - db.addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return; - } - - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.isServiceProvider(tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return; - } - db.removeServiceProvider(tgt); - p.sendMessage("§a已移除 " + tgt); - return; - } - - case TICKET_COMPLETE: { - try { - int tid = Integer.parseInt( - msg.trim()); - mainPlugin.getTicket() - .completeAsProvider( - p, tid); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效工单号"); - } - reset(p); - return; - } - - case TICKET_REPORT_ISSUE: { - String report = msg.trim(); - reset(p); - p.sendMessage("§e异常已上报"); - String tag = config.adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§c§l[工单异常] §f" - + p.getName() - + ": " + report); - } - } - return; - } - - case TICKET_REPLY: { - int replyTid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .replyTicket( - p, replyTid, msg); - return; - } - - case TICKET_ADMIN_TYPE: { - state.ticketType = msg.trim(); - state.type = - InputType - .TICKET_ADMIN_TITLE; - p.sendMessage( - "§e请输入工单标题:"); - return; - } - - case TICKET_ADMIN_TITLE: { - state.ticketTitle = msg; - state.type = - InputType - .TICKET_ADMIN_DESC; - p.sendMessage( - "§e请输入工单描述:"); - return; - } - - case TICKET_ADMIN_DESC: { - state.targetPlayer = msg; - state.type = - InputType - .TICKET_ADMIN_PROVIDER; - p.sendMessage( - "§e输入服务商名和价格:"); - p.sendMessage( - "§7例: playerA 20"); - return; - } - - case TICKET_ADMIN_PROVIDER: { - String[] parts2 = msg.trim() - .split("\\s+"); - String prov2 = parts2[0]; - int price2 = 0; - if (parts2.length >= 2) { - try { - price2 = Integer.parseInt( - parts2[1]); - } catch ( - NumberFormatException e) { - } - } - String t2 = state.ticketType; - String ti2 = state.ticketTitle; - String d2 = state.targetPlayer; - reset(p); - mainPlugin.getTicket() - .adminCreateAndAssign( - p, t2, ti2, - d2, prov2, price2); - return; - } - - default: - reset(p); - return; - } - } -} diff --git a/backup/src/main/java/Sdf1_login/CheckInManager.java b/backup/src/main/java/Sdf1_login/CheckInManager.java deleted file mode 100644 index b594b0f..0000000 --- a/backup/src/main/java/Sdf1_login/CheckInManager.java +++ /dev/null @@ -1,302 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -public class CheckInManager { - - private final Main plugin; - - public CheckInManager(Main plugin) { - this.plugin = plugin; - } - - // ===== 今日是否已签到 ===== - public boolean isCheckedInToday( - String name) { - String last = (String) plugin.getDb() - .getField(name, - "last_checkin_date"); - String today = new SimpleDateFormat( - "yyyy-MM-dd") - .format(new Date()); - return today.equals(last); - } - - // ===== 签到 ===== - // ===== 签到 ===== - public String checkIn(Player p) { - String name = p.getName(); - if (isCheckedInToday(name)) { - return plugin.getConfig2() - .msg("checkin_already"); - } - - Object streakObj = plugin.getDb() - .getField(name, "checkin_streak"); - int streak = streakObj != null - ? ((Number) streakObj).intValue() - : 0; - - String lastDate = (String) plugin.getDb() - .getField(name, "last_checkin_date"); - String today = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date()); - - if (lastDate != null && !lastDate.isEmpty()) { - try { - java.util.Date lastD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(lastDate); - java.util.Date todayD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(today); - long diff = todayD.getTime() - - lastD.getTime(); - if (diff > 86400000L * 2) { - streak = 0; - } else if (diff > 86400000L) { - streak++; - } - } catch (Exception ignored) { - } - } - - if (streak == 0) streak = 1; - else streak++; - - double multi = 1.0; - if (streak >= 21) multi = 1.5; - else if (streak >= 14) multi = 1.3; - else if (streak >= 7) multi = 1.1; - - int basePoints = - plugin.getConfig2().checkinPoints; - int points = (int) (basePoints * multi); - - plugin.getDb().addPoints(name, points); - plugin.getDb().setField(name, - "checkin_streak", streak); - plugin.getDb().setField(name, - "last_checkin_date", today); - - Object totalObj = plugin.getDb() - .getField(name, "total_checkin_days"); - int total = totalObj != null - ? ((Number) totalObj).intValue() - : 0; - plugin.getDb().setField(name, - "total_checkin_days", total + 1); - - // ★ 债券盲盒奖励(6参数版,写入流水)★ - int bondMin = - plugin.getConfig2().checkinBondMin; - int bondMax = - plugin.getConfig2().checkinBondMax; - if (bondMin <= 0) bondMin = 1; - if (bondMax < bondMin) bondMax = bondMin; - int bondReward = bondMin - + new java.util.Random() - .nextInt(bondMax - bondMin + 1); - plugin.getBonds().addBonds(name, bondReward, - "daily_sign", "", "签到系统", - "每日签到连续" + streak + "天"); - - return plugin.getConfig2() - .msg("checkin_success") - .replace("{points}", - String.valueOf(points)) - .replace("{streak}", - String.valueOf(streak)) - .replace("{multi}", - String.valueOf(multi)) - + " §e+" + bondReward + "债券"; - } - - // ===== 获取连续天数 ===== - public int getStreak(String name) { - Object val = plugin.getDb() - .getField(name, - "checkin_streak"); - if (val instanceof Number) { - return ((Number) val) - .intValue(); - } - return 0; - } - // 标准化日期 - private String normalizeDate(String input) { - String s = input.trim().replace("/", "-"); - if (s.matches("\\d{8}")) { - return s.substring(0, 4) + "-" - + s.substring(4, 6) + "-" - + s.substring(6, 8); - } - return s; - } - // ===== 补签入口 ===== - public String backCheckIn( - Player p, String dateStr) { - String name = p.getName(); - String normalized = - normalizeDate(dateStr); - - // 严格日期验证 - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd"); - sdf.setLenient(false); - java.util.Date target; - try { - target = sdf.parse(normalized); - } catch (Exception ex) { - return "§c无效日期: " + dateStr; - } - String reformat = sdf.format(target); - if (!reformat.equals(normalized)) { - return "§c无效日期: " + dateStr; - } - - String today = sdf.format(new Date()); - if (normalized.compareTo(today) >= 0) { - return "§c今天不需要补签"; - } - - String threeDaysAgo = getPastDate(3); - if (normalized.compareTo( - threeDaysAgo) < 0) { - return "§c只能补签3天内" - + "(最多到 " + threeDaysAgo + ")"; - } - - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - boolean alreadyBacked = false; - if (!existing.isEmpty()) { - for (String d : existing.split(",")) { - if (normalizeDate(d.trim()) - .equals(normalized)) { - alreadyBacked = true; - break; - } - } - } - if (alreadyBacked) { - return "§c该日期已补签: " + normalized; - } - - Object normalCheck = - plugin.getDb().getField( - name, "last_checkin_date"); - String lastCheckin = normalCheck != null - ? normalCheck.toString() : ""; - if (lastCheckin.equals(normalized)) { - return "§c该日期已签到: " + normalized; - } - - // ★ 补签消耗:3债券 + 10积分 - // 积分不够时:用 9债券(3倍)代替 ★ - int bondCost = 3; - int pointCost = 10; - int bondAltCost = 9; // 3倍债券替代 - - int myBonds = plugin.getBonds() - .getBonds(name); - Object ptsObj = plugin.getDb() - .getField(name, "points"); - int myPoints = ptsObj != null - ? ((Number) ptsObj).intValue() - : 0; - - - // 方案A:3债券 + 10积分 - boolean canA = myBonds >= bondCost - && myPoints >= pointCost; - // 方案B:9债券(积分不够时) - boolean canB = myBonds >= bondAltCost - && myPoints < pointCost; - - if (!canA && !canB) { - if (myPoints < pointCost - && myBonds < bondAltCost) { - return "§c补签费用不足" - + "(需3债券+10积分," - + "或9债券代替)" - + " 当前: " - + myBonds + "债券 " - + myPoints + "积分"; - } - if (myBonds < bondCost) { - return "§c债券不足(需" - + bondCost + "枚)"; - } - return "§c积分不足(需" - + pointCost + "分)"; - } - - // 执行扣款 - if (canA) { - // 方案A:3债券 + 10积分 - plugin.getBonds().deductBonds( - name, bondCost, - "admin_deduct", "", - "补签系统", "补签扣款"); - plugin.getDb().deductPoints( - name, pointCost); - } else { - // 方案B:9债券代替 - plugin.getBonds().deductBonds( - name, bondAltCost, - "admin_deduct", "", - "补签系统", "补签扣款(债券替代)"); - } - - doBackCheckIn(p, normalized); - - String costMsg = canA - ? bondCost + "债券+" + pointCost + "积分" - : bondAltCost + "债券(替代)"; - return "§a补签成功!日期: " - + normalized - + " §7消耗: " + costMsg; - } - - - - // ===== 执行补签 ===== - // ===== 执行补签 ===== - public void doBackCheckIn( - Player p, String dateStr) { - String name = p.getName(); - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - String updated = existing.isEmpty() - ? dateStr - : existing + "," + dateStr; - plugin.getDb().setField(name, - "backcheck_dates", updated); - int streak = getStreak(name); - plugin.getDb().setField(name, - "checkin_streak", streak + 1); - // ★ 补签不发放奖励,只记录 - } - - // ===== 获取N天前日期 ===== - private String getPastDate( - int daysAgo) { - Calendar cal = - Calendar.getInstance(); - cal.add(Calendar.DAY_OF_MONTH, - -daysAgo); - return new SimpleDateFormat( - "yyyy-MM-dd") - .format(cal.getTime()); - } -} \ No newline at end of file diff --git a/backup/src/main/java/Sdf1_login/CommissionManager.java b/backup/src/main/java/Sdf1_login/CommissionManager.java deleted file mode 100644 index cb24687..0000000 --- a/backup/src/main/java/Sdf1_login/CommissionManager.java +++ /dev/null @@ -1,227 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.logging.Logger; - -public class CommissionManager { - - private final Main main; - private final Logger logger; - private final List rules = - new ArrayList<>(); - - public CommissionManager(Main main) { - this.main = main; - this.logger = main.getLogger(); - loadRules(); - } - - public void loadRules() { - rules.clear(); - File f = new File( - main.getDataFolder(), - "提成规则.txt"); - if (!f.exists()) { - writeDefault(f); - } - try (BufferedReader br = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets - .UTF_8))) { - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() - || line.startsWith("#")) - continue; - // 格式: 邀请人数:提成百分比 - // 如 "5:15" = 邀请5人提成15% - String[] parts = - line.split(":"); - if (parts.length >= 2) { - try { - int count = Integer.parseInt( - parts[0].trim()); - double rate = Double - .parseDouble( - parts[1] - .trim()) / 100.0; - rules.add(new double[]{ - count, rate}); - } catch (Exception ignored) { - } - } - } - // 按人数排序(从大到小) - rules.sort((a, b) -> - Double.compare(b[0], a[0])); - logger.info( - "[Commission] 加载 " - + rules.size() + " 条提成规则"); - } catch (Exception e) { - logger.warning( - "[Commission] 加载失败: " - + e.getMessage()); - } - } - - private void writeDefault(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# 邀请提成规则"); - pw.println("# 格式: 邀请人数:提成百分比"); - pw.println("# 邀请10人提成20%"); - pw.println("10:20"); - pw.println("# 邀请5人提成15%"); - pw.println("5:15"); - pw.println("# 邀请1人提成10%"); - pw.println("1:10"); - } catch (IOException ignored) { - } - } - - /** - * 获取邀请人的提成比例 - * 根据已邀请人数匹配最高规则 - */ - public double getCommissionRate( - String inviter) { - int inviteCount = getInviteCount( - inviter); - for (double[] rule : rules) { - if (inviteCount >= (int) rule[0]) { - return rule[1]; - } - } - return 0; - } - - /** - * 获取邀请人数 - */ - public int getInviteCount( - String inviter) { - try { - Object val = - main.getDb().getField( - inviter, - "monthly_invite_count"); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - Object total = - main.getDb().getField( - inviter, - "total_invite_count"); - if (total instanceof Number) { - return ((Number) total).intValue(); - } - } catch (Exception ignored) { - } - return 0; - } - - /** - * 发放提成 - * 被邀请人获得奖励时,邀请人获得提成 - */ - public void payCommission( - String invitee, - double originalAmount) { - // 查找邀请人 - String inviter = getInviter(invitee); - if (inviter == null - || inviter.isEmpty()) return; - - double rate = getCommissionRate( - inviter); - if (rate <= 0) return; - - double commission = - originalAmount * rate; - if (commission <= 0) return; - - // 通过 Vault 发放 - var rsp = main.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault - .economy.Economy.class); - if (rsp == null) return; - net.milkbowl.vault.economy.Economy econ = - rsp.getProvider(); - if (econ == null) return; - - econ.depositPlayer(inviter, commission); - - // 通知邀请人 - Player inviterP = - main.getServer() - .getPlayer(inviter); - if (inviterP != null - && inviterP.isOnline()) { - inviterP.sendMessage( - "§a§l[邀请提成] §f" - + invitee + " 完成任务," - + "你获得 §e" - + String.format("%.2f", - commission) - + " §f游戏币提成 (" - + (int)(rate * 100) + "%)"); - } - - logger.info( - "[Commission] 提成: " - + inviter + " ← " - + invitee + " | " - + String.format("%.2f", - originalAmount) - + " × " - + (int)(rate * 100) + "% = " - + String.format("%.2f", - commission)); - } - - /** - * 获取邀请人 - */ - private String getInviter(String player) { - try { - Object val = - main.getDb().getField( - player, - "invited_by"); - if (val != null - && !val.toString() - .isEmpty()) { - return val.toString(); - } - } catch (Exception ignored) { - } - return null; - } - - /** - * 判断金额是否包含经济内容 - */ - public boolean hasEconomyContent( - String text) { - String t = text.toLowerCase(); - return t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额"); - } -} diff --git a/backup/src/main/java/Sdf1_login/ConfigManager.java b/backup/src/main/java/Sdf1_login/ConfigManager.java deleted file mode 100644 index a15ada0..0000000 --- a/backup/src/main/java/Sdf1_login/ConfigManager.java +++ /dev/null @@ -1,372 +0,0 @@ -package Sdf1_login; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; - -public class ConfigManager { - - private final File dataFolder; - private final Map messages - = new LinkedHashMap<>(); - private final Map smtpSettings - = new LinkedHashMap<>(); - - // ===== 公共字段 ===== - public String adminTag = "admin"; - public String adminPassword = "qweasd"; - public int maxAccountsPerIP = 3; - public String approvalMode = "manual"; - public int autoApproveDelayMinutes = 30; - public int loginTimeout = 60; - public boolean afkEnabled = false; - public int afkTimeout = 300; - public boolean smtpSsl = true; - public String smtpVerifyCode = ""; - public long smtpVerifyExpire = 0; - public long smtpLastSendTime = 0; - public String smtpConfiguringPlayer = ""; - public String broadcastServerUrl = ""; - public int httpPort = 8080; - public String garbageTag = "admin"; - - public double checkinRewardFixed = 0; - public double checkinRewardMin = 0; - public double checkinRewardMax = 0; - public boolean checkinGivePoints = true; - public int checkinPoints = 10; - public String checkinRewardType = "none"; - public int backCheckPointMultiplier = 5; - public int checkinBondMin = 1; - public int checkinBondMax = 3; - - public double providerPointsPerScore = 2.0; - public int baseEconomyReward = 100; - public double providerEconomyPerScore = 50.0; - public int requesterPointsReward = 10; - - public boolean garbageEnabled = true; - public int garbageInterval = 300; - public int garbageMaxRounds = 1; - - // ★ 构造函数接收 File,和 Main.java 调用一致 ★ - public ConfigManager(File dataFolder) { - this.dataFolder = dataFolder; - } - public String rewardChannel = "债券"; // bonds = 债券,economy = Vault经济 - - - // ==================== 消息 ==================== - - public void loadMessages() { - messages.clear(); - File file = new File(dataFolder, "消息.txt"); - if (!file.exists()) createDefaultMessages(file); - loadMap(file, messages); - } - - public String msg(String key) { - String val = messages.getOrDefault(key, key); - val = val.replace("", "&l"); - val = val.replace("", "&r"); - val = val.replace("
", "\n"); - val = val.replace("\\n", "\n"); - val = val.replaceAll( - "&([0-9a-fk-orA-FK-OR])", "§$1"); - return val; - } - - private void createDefaultMessages(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 消息配置 ====="); - L.add("login_timeout=§c登录超时"); - L.add("not_registered=§e您尚未注册"); - L.add("not_logged_in=§e您尚未登录"); - L.add("already_registered=§c您已注册过"); - L.add("already_logged_in=§c您已登录"); - L.add("reg_success=§a注册成功!"); - L.add("login_success=§a登录成功!"); - L.add("login_failed=§c密码错误"); - L.add("password_wrong=§c密码错误"); - L.add("password_changed=§a密码修改成功!"); - L.add("password_format_error=§c密码需6位以上含大小写和数字"); - L.add("password_same=§c新密码不能与旧密码相同"); - L.add("checkin_already=§c您今日已签到"); - L.add("checkin_success=§a签到成功!获得 {points} 积分"); - L.add("chat_muted=§c你已被禁言"); - L.add("未知参数=§c未知参数或权限不足"); - writeLines(f, L); - } - - // ==================== SMTP ==================== - - public void loadSmtp() { - smtpSettings.clear(); - File file = new File(dataFolder, "SMTP设置.txt"); - if (!file.exists()) createDefaultSmtp(file); - loadMap(file, smtpSettings); - } - - public String getSmtp(String key) { - return smtpSettings.getOrDefault(key, ""); - } - - public void setSmtp(String key, String value) { - smtpSettings.put(key, value); - } - - public void saveSmtp() { - File file = new File(dataFolder, "SMTP设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== SMTP 配置 ====="); - for (Map.Entry e - : smtpSettings.entrySet()) { - L.add(e.getKey() + "=" + e.getValue()); - } - writeLines(file, L); - } - - private void createDefaultSmtp(File f) { - List L = new ArrayList<>(); - L.add("# ===== SMTP 配置 ====="); - L.add("smtp地址=smtp.example.com"); - L.add("smtp端口=465"); - L.add("smtp账号="); - L.add("smtp密码="); - L.add("发件人名称=Sdf1_login"); - L.add("smtp加密=true"); - writeLines(f, L); - } - - // ==================== 插件设置 ==================== - - public void loadSettings() { - File file = new File(dataFolder, "插件设置.txt"); - if (!file.exists()) createDefaultSettings(file); - Map m = new LinkedHashMap<>(); - loadMap(file, m); - - adminTag = m.getOrDefault("管理标签", adminTag); - adminPassword = m.getOrDefault("管理密码", adminPassword); - maxAccountsPerIP = parseInt(m.getOrDefault("每IP最大账号数", "3"), 3); - approvalMode = m.getOrDefault("审批模式", approvalMode); - autoApproveDelayMinutes = parseInt(m.getOrDefault("自动审批延迟分钟", "30"), 30); - loginTimeout = parseInt(m.getOrDefault("登录超时秒数", "60"), 60); - afkEnabled = Boolean.parseBoolean(m.getOrDefault("挂机踢出", "false")); - afkTimeout = parseInt(m.getOrDefault("挂机超时秒数", "300"), 300); - smtpSsl = Boolean.parseBoolean(m.getOrDefault("smtp加密", "true")); - httpPort = parseInt(m.getOrDefault("http-port", "8080"), 8080); - broadcastServerUrl = m.getOrDefault("resource-pack-url", ""); - - providerPointsPerScore = parseDouble(m.getOrDefault("服务商积分倍率", "2.0"), 2.0); - baseEconomyReward = parseInt(m.getOrDefault("基础经济奖励", "100"), 100); - providerEconomyPerScore = parseDouble(m.getOrDefault("经济奖励每评分", "50.0"), 50.0); - requesterPointsReward = parseInt(m.getOrDefault("报单人积分奖励", "10"), 10); - - garbageEnabled = Boolean.parseBoolean(m.getOrDefault("垃圾站_启用", "true")); - garbageInterval = parseInt(m.getOrDefault("垃圾站_清理间隔秒", "300"), 300); - garbageMaxRounds = parseInt(m.getOrDefault("垃圾站_保留轮数", "1"), 1); - - checkinRewardFixed = parseDouble(m.getOrDefault("签到固定金额", "0"), 0); - checkinRewardMin = parseDouble(m.getOrDefault("签到最小金额", "0"), 0); - checkinRewardMax = parseDouble(m.getOrDefault("签到最大金额", "0"), 0); - checkinGivePoints = Boolean.parseBoolean(m.getOrDefault("签到给积分", "true")); - checkinPoints = parseInt(m.getOrDefault("签到积分数量", "10"), 10); - backCheckPointMultiplier = parseInt(m.getOrDefault("补签积分倍率", "5"), 5); - checkinBondMin = parseInt(m.getOrDefault("签到债券最小", "1"), 1); - checkinBondMax = parseInt(m.getOrDefault("签到债券最大", "3"), 3); - if (checkinBondMax < checkinBondMin) checkinBondMax = checkinBondMin; - - if (checkinRewardFixed > 0) checkinRewardType = "fixed"; - else if (checkinRewardMin > 0 && checkinRewardMax > checkinRewardMin) checkinRewardType = "range"; - else checkinRewardType = "none"; - rewardChannel = m.getOrDefault("奖励发放方式", "债券"); - rewardChannel = normalizeRewardChannel(rewardChannel); - -// 验证合法值 - if (!"economy".equalsIgnoreCase(rewardChannel) - && !"bonds".equalsIgnoreCase(rewardChannel)) { - rewardChannel = "bonds"; - } - - - - // ★ 自动补充新配置项 ★ - boolean needAppend = false; - List newKeys = new ArrayList<>(); - if (!m.containsKey("签到债券最小")) { needAppend = true; newKeys.add("签到债券最小=" + checkinBondMin); } - if (!m.containsKey("签到债券最大")) { needAppend = true; newKeys.add("签到债券最大=" + checkinBondMax); } - if (!m.containsKey("奖励发放方式")) { - needAppend = true; - newKeys.add("奖励发放方式=债券"); - } - if (needAppend) { - try { - FileWriter fw = new FileWriter(file, true); - BufferedWriter bw = new BufferedWriter(fw); - bw.newLine(); - for (String key : newKeys) { bw.write(key); bw.newLine(); } - bw.flush(); bw.close(); - System.out.println("[Config] 已补充签到债券配置"); - - } catch (IOException ignored) {} - if (!m.containsKey("奖励发放方式")) { - try { - FileWriter fw = new FileWriter(file, true); - BufferedWriter bw = new BufferedWriter(fw); - bw.newLine(); - bw.write("奖励发放方式=bonds"); - bw.newLine(); - bw.flush(); bw.close(); - } catch (IOException ignored) {} - } - - } - } - /** - * 奖励方式标准化:中英文全兼容 - * "债券" "bonds" "Bond" "BONDS" → "bonds" - * "经济" "economy" "Economy" "ECONOMY" → "economy" - * 其他任何值 → "bonds"(默认债券) - */ - private String normalizeRewardChannel(String raw) { - if (raw == null) return "bonds"; - String s = raw.trim().toLowerCase(); - // 中文 - if (s.contains("债券")) return "bonds"; - if (s.contains("经济")) return "economy"; - // 英文 - if (s.contains("bond")) return "bonds"; - if (s.contains("econ")) return "economy"; - if (s.contains("Vault")) return "economy"; - // 兜底 - return "bonds"; - } - - public void saveSettings() { - File file = new File(dataFolder, "插件设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=" + adminTag); - L.add("管理密码=" + adminPassword); - L.add("每IP最大账号数=" + maxAccountsPerIP); - L.add("审批模式=" + approvalMode); - L.add("自动审批延迟分钟=" + autoApproveDelayMinutes); - L.add("登录超时秒数=" + loginTimeout); - L.add("挂机踢出=" + afkEnabled); - L.add("挂机超时秒数=" + afkTimeout); - L.add("resource-pack-url=" + broadcastServerUrl); - L.add("http-port=" + httpPort); - L.add("垃圾站_启用=" + garbageEnabled); - L.add("垃圾站_清理间隔秒=" + garbageInterval); - L.add("垃圾站_保留轮数=" + garbageMaxRounds); - L.add("签到固定金额=" + checkinRewardFixed); - L.add("签到最小金额=" + checkinRewardMin); - L.add("签到最大金额=" + checkinRewardMax); - L.add("签到给积分=" + checkinGivePoints); - L.add("签到积分数量=" + checkinPoints); - L.add("签到债券最小=" + checkinBondMin); - L.add("签到债券最大=" + checkinBondMax); - L.add("补签积分倍率=" + backCheckPointMultiplier); - L.add("奖励发放方式=债券"); - writeLines(file, L); - } - - private void createDefaultSettings(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=admin"); - L.add("管理密码=qweasd"); - L.add("每IP最大账号数=3"); - L.add("审批模式=manual"); - L.add("自动审批延迟分钟=30"); - L.add("登录超时秒数=60"); - L.add("挂机踢出=false"); - L.add("挂机超时秒数=300"); - L.add("resource-pack-url="); - L.add("http-port=8080"); - L.add("垃圾站_启用=true"); - L.add("垃圾站_清理间隔秒=300"); - L.add("垃圾站_保留轮数=1"); - L.add("签到固定金额=0"); - L.add("签到最小金额=50"); - L.add("签到最大金额=500"); - L.add("签到给积分=true"); - L.add("签到积分数量=10"); - L.add("签到债券最小=1"); - L.add("签到债券最大=3"); - L.add("补签积分倍率=5"); - L.add("奖励发放方式=债券"); - writeLines(f, L); - } - public List getAfkWhitelist() { - List list = new ArrayList<>(); - File file = new File(dataFolder, "挂机白名单.txt"); - if (!file.exists()) { - try { - file.createNewFile(); - FileWriter fw = new FileWriter(file); - fw.write("# 每行一个玩家名\n"); - fw.flush(); - fw.close(); - } catch (IOException ignored) {} - return list; - } - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(file), - StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (!line.isEmpty() && !line.startsWith("#")) - list.add(line); - } - } catch (IOException ignored) {} - return list; - } - - // ==================== 其他 ==================== - - public String generateSmtpVerifyCode() { - String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(); - java.util.Random r = new java.util.Random(); - for (int i = 0; i < 6; i++) sb.append(chars.charAt(r.nextInt(chars.length()))); - smtpVerifyCode = sb.toString(); - smtpVerifyExpire = System.currentTimeMillis() + 300000L; - return smtpVerifyCode; - } - - public boolean checkSmtpVerifyCode(String code) { - if (System.currentTimeMillis() > smtpVerifyExpire) { smtpVerifyCode = ""; return false; } - return smtpVerifyCode.equalsIgnoreCase(code); - } - - public void clearSmtpVerifyCode() { smtpVerifyCode = ""; smtpVerifyExpire = 0; } - public boolean canSendSmtpVerify() { return System.currentTimeMillis() - smtpLastSendTime >= 60000L; } - public void recordSmtpSendTime() { smtpLastSendTime = System.currentTimeMillis(); } - - // ==================== 文件工具 ==================== - - private void loadMap(File file, Map map) { - try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); - } - } catch (IOException ignored) {} - } - - private void writeLines(File file, List lines) { - try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { - for (String l : lines) { w.write(l); w.newLine(); } - } catch (IOException ignored) {} - } - - private double parseDouble(String s, double def) { try { return Double.parseDouble(s.trim()); } catch (Exception e) { return def; } } - private int parseInt(String s, int def) { try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } } -} diff --git a/backup/src/main/java/Sdf1_login/CypayCommand.java b/backup/src/main/java/Sdf1_login/CypayCommand.java deleted file mode 100644 index 7fd9067..0000000 --- a/backup/src/main/java/Sdf1_login/CypayCommand.java +++ /dev/null @@ -1,577 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.stream.Collectors; - -public class CypayCommand - implements CommandExecutor, TabCompleter { - - private final Main plugin; - private static final SimpleDateFormat SDF = - new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - public CypayCommand(Main plugin) { - this.plugin = plugin; - } - - private boolean hasAdminPerm(CommandSender s) { - if (s.isOp()) return true; - if (!(s instanceof Player)) return true; - Player p = (Player) s; - String tag = - plugin.getConfig2().adminTag; - return !tag.isEmpty() - && p.getScoreboardTags().contains(tag); - } - - private int parseAmount(String s) { - return Integer.parseInt( - s.trim().replaceAll("[+]", "")); - } - - private List filter( - List list, String prefix) { - String low = prefix.toLowerCase(); - return list.stream() - .filter(x -> x.toLowerCase() - .startsWith(low)) - .collect(Collectors.toList()); - } - - @Override - public boolean onCommand(CommandSender sender, - Command cmd, - String label, - String[] args) { - - // ===== /cypay — 查自己 ===== - if (args.length == 0) { - String name = sender instanceof Player - ? sender.getName() : "Console"; - int bonds = - plugin.getBonds().getBonds(name); - String status = plugin.getBonds() - .getAccountStatus(name); - sender.sendMessage( - "§e§l[债券] §f" + name - + " 余额: §a" + bonds); - sender.sendMessage( - "§e§l[债券] §f状态: " - + ("frozen".equals(status) - ? "§c冻结中" - : "§a正常")); - return true; - } - - // ===== /cypay import ===== - /* if (args.length == 2 - && "import".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String file = args[1]; - if (!file.endsWith(".txt")) - file += ".txt"; - int result = plugin.getCDK() - .importFromFile(file); - if (result == -1) - sender.sendMessage("§c文件不存在: " - + file); - else - sender.sendMessage("§a导入完成: " - + result + " 条"); - return true; - }*/ - - // ===== /cypay freeze ===== - if (args.length == 2 - && "freeze".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - if (plugin.getBonds().isFrozen(target)) { - sender.sendMessage("§c" + target - + " 已处于冻结状态"); - return true; - } - plugin.getBonds().freezeAccount(target); - sender.sendMessage("§a已冻结 " + target - + " 的债券账户"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§c§l[系统] 你的债券账户" - + "已被管理员冻结"); - return true; - } - - // ===== /cypay unfreeze ===== - if (args.length == 2 - && "unfreeze".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - if (!plugin.getBonds() - .isFrozen(target)) { - sender.sendMessage("§c" + target - + " 未被冻结"); - return true; - } - plugin.getBonds().unfreezeAccount(target); - sender.sendMessage("§a已解冻 " + target - + " 的债券账户"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§a§l[系统] 你的债券账户" - + "已被解冻"); - return true; - } - - // ===== /cypay water [days] ===== - if (args.length >= 2 - && "water".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - int days = 7; - if (args.length >= 3) { - try { - days = Integer.parseInt(args[2]); - } catch (NumberFormatException ignored) { - } - } - days = Math.min(days, - BondManager.ADMIN_MAX_WATER_DAYS); - - if (days <= 0) days = 1; - - List> txs = - plugin.getBonds() - .getTransactions( - target, days); - - sender.sendMessage( - "§e§l===== " + target - + " 流水记录 (近" - + days + "天) ====="); - sender.sendMessage("§f当前余额: §a" - + plugin.getBonds() - .getBonds(target) - + " §f状态: " - + (plugin.getBonds() - .isFrozen(target) - ? "§c冻结" : "§a正常")); - - if (txs.isEmpty()) { - sender.sendMessage("§7 (暂无记录)"); - } else { - for (Map tx : txs) { - sender.sendMessage( - formatTxLine(tx)); - } - } - sender.sendMessage( - "§e§l==========================" - + "=========="); - return true; - } - // ===== /cypay printer [player] ===== - if ("printer".equalsIgnoreCase(args[0])) { - if (!hasAdminPerm(sender) - && !(sender instanceof Player) - == false) { - // 控制台也可以用 - if (!(sender instanceof Player)) - ; // 放行 - else if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - } - if (args.length == 1) { - // 打印全服 - sender.sendMessage( - "§e正在导出全服流水..."); - plugin.getBonds(); // 确保实例就绪 - if (plugin.getBondPrinter() != null) { - plugin.getBondPrinter() - .printAll(sender); - } else { - sender.sendMessage("§cPrinter未初始化"); - } - return true; - } - if (args.length == 2) { - // 打印指定玩家 - String target = args[1]; - sender.sendMessage("§e正在导出 " - + target + " 的流水..."); - if (plugin.getBondPrinter() != null) { - plugin.getBondPrinter() - .printPlayer(target, sender); - } else { - sender.sendMessage("§cPrinter未初始化"); - } - return true; - } - } - - // ===== /cypay give/remove ===== - if (args.length == 3) { - String action = args[0].toLowerCase(); - if ("give".equals(action) - || "remove".equals(action)) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - try { - int amount = - parseAmount(args[2]); - if (amount <= 0) { - sender.sendMessage( - "§c金额必须大于0"); - return true; - } - String txType = - "give".equals(action) - ? BondManager - .TX_ADMIN_GIVE - : BondManager - .TX_ADMIN_DEDUCT; - String opName = - sender instanceof Player - ? sender.getName() - : "Console"; - - if ("give".equals(action)) { - plugin.getBonds().addBonds( - target, amount, - txType, "", - opName, "管理员发放"); - sender.sendMessage( - "§a已给 " + target - + " " + amount - + " 债券"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§a管理员给了你 " - + amount - + " 债券"); - } else { - if (plugin.getBonds() - .deductBonds(target, - amount, - txType, "", - opName, - "管理员扣除")) { - sender.sendMessage( - "§a已扣除 " + target - + " " + amount - + " 债券"); - Player tp = plugin - .getServer() - .getPlayerExact( - target); - if (tp != null) - tp.sendMessage( - "§c管理员扣除了" - + "你的 " - + amount - + " 债券"); - } else { - sender.sendMessage( - "§c" + target - + " 债券不足" - + "或账户冻结"); - } - } - } catch (NumberFormatException e) { - sender.sendMessage( - "§c金额格式错误"); - } - return true; - } - } - - // ===== /cypay — 仅玩家转账 ===== - if (args.length == 2) { - try { - int amount = parseAmount(args[1]); - if (amount <= 0) { - sender.sendMessage( - "§c金额必须大于0"); - return true; - } - // ★ 控制台禁止转账 - if (!(sender instanceof Player)) { - sender.sendMessage( - "§c控制台不能转账,请使用 " - + "/cypay give <玩家> <金额>"); - return true; - } - Player p = (Player) sender; - String target = args[0]; - BondManager.TransferResult r = - plugin.getBonds().transfer( - p.getName(), - target, amount); - if (r.success) { - p.sendMessage("§e§l[债券转账] §f向 §a" - + target + " §f转出 §c" - + amount + " §f枚债券"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage("§e§l[债券转账] §f收到 §a" - + p.getName() - + " §f转入 §a" - + amount + " §f枚债券"); - } else { - p.sendMessage("§c" + r.message - + ",当前: " - + plugin.getBonds() - .getBonds(p.getName())); - } - return true; - } catch (NumberFormatException ignored) { - } - } - - // ===== /cypay — 查指定玩家 ===== - if (args.length == 1) { - String target = args[0]; - // ★ 验证玩家在 login.db 中存在 ★ - if (!plugin.getDb().userExists(target)) { - sender.sendMessage("§c玩家 " + target - + " 不存在于服务器"); - return true; - } - - // String target = args[0]; - if (hasAdminPerm(sender)) { - int bonds = plugin.getBonds() - .getBonds(target); - String status = plugin.getBonds() - .getAccountStatus(target); - sender.sendMessage( - "§e§l[债券] §f" + target - + " 余额: §a" - + bonds); - sender.sendMessage( - "§e§l[债券] §f状态: " - + ("frozen".equals( - status) - ? "§c冻结中" - : "§a正常")); - } else { - int bonds = plugin.getBonds() - .getBonds( - sender.getName()); - sender.sendMessage( - "§e§l[债券] §f你的余额: §a" - + bonds); - } - return true; - } - - showHelp(sender); - return true; - } - - // ===== 流水行格式化 ===== - - private String formatTxLine( - Map tx) { - 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 pos = amount > 0; - String sign = pos ? "+" : ""; - String color = pos ? "§a" : "§c"; - - StringBuilder sb = new StringBuilder(); - sb.append("§7[") - .append(SDF.format(new Date(time))) - .append("] ") - .append(formatTxType(type)) - .append(" ") - .append(color).append(sign) - .append(amount).append("§7"); - if (tgt != null && !tgt.isEmpty()) - sb.append(" §e↔ ").append(tgt); - if (reason != null && !reason.isEmpty()) - sb.append(" §7(").append(reason) - .append(")"); - sb.append(" §7余额:§f").append(bal); - return sb.toString(); - } - - private String formatTxType(String type) { - if (type == null) return "§7未知"; - switch (type) { - case BondManager.TX_ADMIN_GIVE: - return "§a管理员发放"; - case BondManager.TX_ADMIN_DEDUCT: - return "§c管理员扣除"; - case BondManager.TX_TRANSFER_OUT: - return "§c转出"; - case BondManager.TX_TRANSFER_IN: - return "§a转入"; - case BondManager.TX_REDEEM: - return "§aCDK兑换"; - case BondManager.TX_SHOP_BUY: - return "§e商城购买"; - case BondManager.TX_DAILY_SIGN: - return "§a签到奖励"; - case BondManager.TX_FREEZE: - return "§c冻结"; - case BondManager.TX_UNFREEZE: - return "§a解冻"; - default: - return "§7" + type; - } - } - - private void showHelp(CommandSender sender) { - sender.sendMessage("§e§l债券命令帮助:"); - sender.sendMessage( - "§e/cypay §7- 查询自己余额"); - sender.sendMessage( - "§e/cypay <玩家> §7- 查余额(管理员)"); - sender.sendMessage( - "§e/cypay <玩家> <金额> §7- 转账"); - if (hasAdminPerm(sender)) { - sender.sendMessage( - "§e/cypay give <玩家> <金额> §7- 给债券"); - sender.sendMessage( - "§e/cypay remove <玩家> <金额> §7- 扣债券"); - sender.sendMessage( - "§e/cypay import <文件> §7- 导入CDK"); - sender.sendMessage( - "§e/cypay freeze <玩家> §7- 冻结"); - sender.sendMessage( - "§e/cypay unfreeze <玩家> §7- 解冻"); - sender.sendMessage( - "§e/cypay water <玩家> [天数] §7- 流水"); - sender.sendMessage( - "§e/cypay printer §7- 导出全服流水xlsx"); - sender.sendMessage( - "§e/cypay printer <玩家> §7- 导出指定玩家xlsx"); - - } - } - - @Override - public List onTabComplete( - CommandSender sender, Command command, - String label, String[] args) { - List list = new ArrayList<>(); - - if (args.length == 1) { - String last = args[0].toLowerCase(); - if (hasAdminPerm(sender)) { - list.add("give"); - list.add("remove"); - list.add("import"); - list.add("freeze"); - list.add("unfreeze"); - list.add("water"); - list.add("printer"); - - File cdkDir = new File( - plugin.getDataFolder(), - "cdk"); - if (cdkDir.exists()) { - File[] files = cdkDir.listFiles( - (d, n) -> n - .endsWith(".txt")); - if (files != null) - for (File f : files) - list.add(f.getName()); - } - } - for (Player o : - Bukkit.getOnlinePlayers()) - list.add(o.getName()); - return filter(list, last); - } - - if (args.length == 2) { - String first = args[0].toLowerCase(); - if ("give".equals(first) - || "remove".equals(first) - || "freeze".equals(first) - || "unfreeze".equals(first) - || "water".equals(first)) { - for (Player o : - Bukkit.getOnlinePlayers()) - list.add(o.getName()); - return filter(list, - args[1].toLowerCase()); - } - list.add("10"); - list.add("50"); - list.add("100"); - return filter(list, - args[1].toLowerCase()); - } - - if (args.length == 3) { - String first = args[0].toLowerCase(); - if ("give".equals(first) - || "remove".equals(first)) { - list.add("10"); - list.add("50"); - list.add("100"); - list.add("999"); - return filter(list, - args[2].toLowerCase()); - } - if ("water".equals(first)) { - list.add("1"); - list.add("3"); - list.add("7"); - list.add("14"); - return filter(list, - args[2].toLowerCase()); - } - } - - return list; - } -} diff --git a/backup/src/main/java/Sdf1_login/DatabaseManager.java b/backup/src/main/java/Sdf1_login/DatabaseManager.java deleted file mode 100644 index cb2b8f7..0000000 --- a/backup/src/main/java/Sdf1_login/DatabaseManager.java +++ /dev/null @@ -1,1758 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.logging.Logger; - -public class DatabaseManager { - - private Connection db; - private final File dataFolder; - private final Logger logger; - - public DatabaseManager(File dataFolder) { - this.dataFolder = dataFolder; - this.logger = Logger.getLogger("Sdf1_login"); - } - - // ==================== 初始化 ==================== - - public void init() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(dataFolder, - "login.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 用户表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "users (" - + "player_name TEXT PRIMARY KEY," - + "password_hash TEXT NOT NULL," - + "password_salt TEXT NOT NULL," - + "email TEXT DEFAULT ''," - + "register_time INTEGER DEFAULT 0," - + "last_login_time INTEGER DEFAULT 0," - + "total_online_time INTEGER DEFAULT 0," - + "last_online_check INTEGER DEFAULT 0," - + "is_logged_in INTEGER DEFAULT 0," - + "blocks_broken INTEGER DEFAULT 0," - + "blocks_placed INTEGER DEFAULT 0," - + "gift_stage INTEGER DEFAULT 0," - + "gift_claimed TEXT DEFAULT ''," - + "invite_code TEXT DEFAULT ''," - + "invited_by TEXT DEFAULT ''," - + "monthly_invite_count INTEGER DEFAULT 0," - + "invite_month TEXT DEFAULT ''," - + "points INTEGER DEFAULT 0," - + "checkin_streak INTEGER DEFAULT 0," - + "last_checkin_date TEXT DEFAULT ''," - + "total_checkin_days INTEGER DEFAULT 0," - + "temp_password TEXT DEFAULT ''," - + "temp_pw_expire INTEGER DEFAULT 0," - + "temp_pw_used INTEGER DEFAULT 0," - + "ip_address TEXT DEFAULT ''," - + "tasks_completed TEXT DEFAULT ''," - + "premium_uuid TEXT DEFAULT ''," - + "register_ip TEXT DEFAULT ''," - + "register_type TEXT DEFAULT 'manual'," - + "backcheck_dates TEXT DEFAULT ''" - + ")"); - - - // 安全添加 users 缺失列 - safeAdd(st, "tasks_completed", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_password", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_pw_expire", - "INTEGER DEFAULT 0"); - safeAdd(st, "temp_pw_used", - "INTEGER DEFAULT 0"); - safeAdd(st, "ip_address", - "TEXT DEFAULT ''"); - safeAdd(st, "premium_uuid", - "TEXT DEFAULT ''"); - safeAdd(st, "register_ip", - "TEXT DEFAULT ''"); - safeAdd(st, "register_type", - "TEXT DEFAULT 'manual'"); - - // ===== 背包备份表:安全迁移 ===== - // 检查旧表是否存在(用 inventory_data 列判断) - boolean hasOldTable = false; - try { - ResultSet rs = st.executeQuery( - "SELECT inventory_data " - + "FROM inventory_backups " - + "LIMIT 1"); - hasOldTable = true; - rs.close(); - } catch (SQLException e) { - hasOldTable = false; - } - - if (hasOldTable) { - // 旧表存在,迁移数据到新表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups_new (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - // 迁移旧数据 - try { - st.execute("INSERT INTO " - + "inventory_backups_new " - + "(player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience) " - + "SELECT player_name, " - + "inventory_data, " - + "armor_data, " - + "extra_data, " - + "player_level, " - + "player_exp " - + "FROM inventory_backups"); - } catch (SQLException ignored) { - } - st.execute("DROP TABLE " - + "inventory_backups"); - st.execute("ALTER TABLE " - + "inventory_backups_new " - + "RENAME TO " - + "inventory_backups"); - } else { - // 无旧表或已迁移,直接创建 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - } - - // 安全报警表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "security_alerts (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "alert_ip TEXT NOT NULL," - + "alert_time INTEGER NOT NULL," - + "alert_date TEXT NOT NULL," - + "notified INTEGER DEFAULT 0," - + "notified_time INTEGER DEFAULT 0)"); - // PVP区域表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "name TEXT NOT NULL UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER DEFAULT 0" - + ")"); - -// PVP击杀总榜表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT NOT NULL," - + "region_name TEXT NOT NULL," - + "kills INTEGER DEFAULT 0," - + "deaths INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, region_name)" - + ")"); - - -// 菜单图标表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "menu_icons (" - + "player_name TEXT PRIMARY KEY," - + "icon_b64 TEXT DEFAULT ''," - + "item_name TEXT DEFAULT ''," - + "save_time INTEGER DEFAULT 0" - + ")"); - - // 工单表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "tickets (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "type TEXT NOT NULL," - + "status TEXT DEFAULT 'pending'," - + "priority TEXT DEFAULT 'normal'," - + "requester TEXT NOT NULL," - + "assigned_to TEXT DEFAULT ''," - + "title TEXT DEFAULT ''," - + "description TEXT DEFAULT ''," - + "reward_amount INTEGER DEFAULT 0," - + "score INTEGER DEFAULT 0," - + "target_player TEXT DEFAULT ''," - + "create_time INTEGER NOT NULL," - + "update_time INTEGER DEFAULT 0," - + "complete_time INTEGER DEFAULT 0," - + "admin_confirmed INTEGER DEFAULT 0," - + "reject_reason TEXT DEFAULT ''" - + ")"); - - // 工单回复表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "ticket_replies (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "ticket_id INTEGER NOT NULL," - + "sender TEXT NOT NULL," - + "message TEXT NOT NULL," - + "create_time INTEGER NOT NULL" - + ")"); - - // 服务商表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "service_providers (" - + "player_name TEXT PRIMARY KEY," - + "role TEXT DEFAULT 'waiter'," - + "active INTEGER DEFAULT 1," - + "join_time INTEGER DEFAULT 0" - + ")"); - // 垃圾回收站物品表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "items (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "item_data TEXT DEFAULT ''," - + "amount INTEGER DEFAULT 1," - + "create_time INTEGER DEFAULT 0" - + ")"); - safeAdd(st, "menu_snowball", - "INTEGER DEFAULT 1"); - st.close(); - logger.info("[Sdf1_login] 数据库初始化完成"); - } catch (Exception e) { - throw new RuntimeException( - "[Sdf1_login] DB初始化失败: " - + e.getMessage(), e); - } - } - - - private void safeAdd(Statement st, - String col, String def) { - try { - st.executeUpdate("ALTER TABLE users " - + "ADD COLUMN " + col + " " + def); - } catch (SQLException ignored) { - } - } - - public void close() { - try { - if (db != null && !db.isClosed()) - db.close(); - } catch (Exception ignored) { - } - } -// ==================== 菜单图标 ==================== - - public void saveMenuIcon(String name, - String iconB64, String itemName) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO menu_icons " - + "(player_name, icon_b64, " - + "item_name, save_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, iconB64); - ps.setString(3, itemName); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT icon_b64 FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - String b64 = null; - if (rs.next()) { - b64 = rs.getString("icon_b64"); - } - rs.close(); - ps.close(); - return b64; - } catch (SQLException e) { - return null; - } - } - - public void deleteMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM menu_icons " - + "WHERE player_name=? " - + "AND icon_b64 IS NOT NULL " - + "AND icon_b64 != '' " - + "LIMIT 1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - // ==================== 用户操作 ==================== - - public boolean userExists(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public void createUser(String name, String hash, - String salt) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO users " - + "(player_name, " - + "password_hash, " - + "password_salt, " - + "register_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, hash); - ps.setString(3, salt); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ========== 临时密码管理 ========== - - public void clearTempPassword(String name) { - setField(name, "temp_password", null); - setField(name, "temp_pw_expire", 0); - setField(name, "temp_pw_used", 1); - } - - public void clearAllTempPasswords() { - try { - Statement st = db.createStatement(); - st.executeUpdate( - "UPDATE users SET " - + "temp_password=NULL," - + "temp_pw_expire=0," - + "temp_pw_used=1 " - + "WHERE temp_password " - + "IS NOT NULL"); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getPlayerIPByName( - String name) { - Object ip = getField(name, "ip"); - return ip != null - ? String.valueOf(ip) : null; - } - - public boolean checkPasswordOrTemp( - String name, String hash) { - // 先检查主密码 - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String mainHash = - rs.getString("password_hash"); - rs.close(); - ps.close(); - if (hash.equals(mainHash)) - return true; - } else { - rs.close(); - ps.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - // 再检查临时密码 - try { - PreparedStatement ps2 = db.prepareStatement( - "SELECT temp_password, " - + "temp_pw_expire, " - + "temp_pw_used " - + "FROM users " - + "WHERE player_name=?"); - ps2.setString(1, name); - ResultSet rs2 = ps2.executeQuery(); - if (rs2.next()) { - String tempHash = - rs2.getString( - "temp_password"); - long expire = - rs2.getLong( - "temp_pw_expire"); - int used = - rs2.getInt( - "temp_pw_used"); - rs2.close(); - ps2.close(); - if (tempHash != null - && !tempHash.isEmpty() - && hash.equals(tempHash) - && System.currentTimeMillis() - < expire - && used != 1) { - return true; - } - } else { - rs2.close(); - ps2.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - public Map getUser(String name) { - Map r = - new LinkedHashMap<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - r.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return r; - } - - public void setField(String name, String field, - Object val) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE users SET " + field - + "=? " - + "WHERE player_name=?"); - if (val instanceof Integer) - ps.setInt(1, (Integer) val); - else if (val instanceof Long) - ps.setLong(1, (Long) val); - else - ps.setString(1, - String.valueOf(val)); - ps.setString(2, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Object getField(String name, - String field) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT " + field - + " FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - Object val = rs.next() - ? rs.getObject(1) : null; - rs.close(); - ps.close(); - return val; - } catch (SQLException e) { - return null; - } - } - - public boolean checkPassword(String name, - String hash) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String stored = - rs.getString("password_hash"); - rs.close(); - ps.close(); - return hash.equals(stored); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - - public boolean isUsingTempPassword(String name) { - String h = (String) getField(name, - "temp_password"); - return h != null && !h.isEmpty(); - } - - public void setLoggedIn(String name, - boolean val) { - setField(name, "is_logged_in", - val ? 1 : 0); - } - - public void addPoints(String name, int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - setField(name, "points", curVal + amount); - } - - public boolean deductPoints(String name, - int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - if (curVal < amount) return false; - setField(name, "points", - curVal - amount); - return true; - } - public List> - getAllUsers() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM users"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - public List> - getTicketsByProviderAll( - String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deleteUser(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void recordIP(String name, String ip) { - if (ip != null && !ip.isEmpty()) { - setField(name, "ip_address", ip); - } - } - - // ==================== IP查询 ==================== - - /** - * 统计指定IP注册的账号数量 - */ - public int countIPAccounts(String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address=?"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count; - } catch (SQLException e) { - return 0; - } - } - - /** - * 获取指定IP下所有账号详情 - */ - public List> - getAccountDetailsByIP(String ip) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, " - + "register_time " - + "FROM users " - + "WHERE ip_address=? " - + "ORDER BY register_time"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("register_time", - rs.getLong("register_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 背包备份 ==================== - - public void saveInventoryBackup(String name, - String contents, String armor, - String extra, int level, - double experience) { - try { - PreparedStatement del = - db.prepareStatement( - "DELETE FROM " - + "inventory_backups " - + "WHERE player_name=?"); - del.setString(1, name); - del.executeUpdate(); - del.close(); - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO " - + "inventory_backups" - + " (player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience, " - + "save_time) " - + "VALUES " - + "(?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, contents); - ps.setString(3, armor); - ps.setString(4, extra); - ps.setInt(5, level); - ps.setDouble(6, experience); - ps.setLong(7, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public String[] loadInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time " - + "DESC LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void deleteInventoryBackup( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM inventory_backups " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - boolean has = rs.next(); - rs.close(); - ps.close(); - return has; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } -// ==================== PVP区域 ==================== - - public void savePVPRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions " - + "(name, world, " - + "x1, y1, z1, " - + "x2, y2, z2, " - + "create_time) " - + "VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map - getPVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getAllPVPRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - db.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deletePVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - -// ==================== PVP击杀 ==================== - - public int getPVPKills( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int kills = 0; - if (rs.next()) - kills = rs.getInt("kills"); - rs.close(); - ps.close(); - return kills; - } catch (SQLException e) { - return 0; - } - } - - public int getPVPDeaths( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int deaths = 0; - if (rs.next()) - deaths = rs.getInt("deaths"); - rs.close(); - ps.close(); - return deaths; - } catch (SQLException e) { - return 0; - } - } - - public void addPVPKill( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,1,0) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "kills=kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void addPVPDeath( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,0,1) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "deaths=deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getPVPKillTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT player_name, " - + "kills, deaths " - + "FROM pvp_kills " - + "WHERE " - + "region_name=? " - + "AND kills>0 " - + "ORDER BY kills " - + "DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - // ==================== 安全报警 ==================== - - public void recordSecurityAlert( - String playerName, String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO security_alerts " - + "(player_name, alert_ip, " - + "alert_time, alert_date) " - + "VALUES (?,?,?,?)"); - ps.setString(1, playerName); - ps.setString(2, ip); - long now = System.currentTimeMillis(); - ps.setLong(3, now); - ps.setString(4, - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date( - now))); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - - } - - public List> - getUnnotifiedAlerts( - String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM security_alerts " - + "WHERE player_name=? " - + "AND notified=0 " - + "ORDER BY alert_time ASC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void markAlertNotified(int alertId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE security_alerts " - + "SET notified=1, " - + "notified_time=? " - + "WHERE id=?"); - ps.setLong(1, - System.currentTimeMillis()); - ps.setInt(2, alertId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - /** - * 获取玩家最近N条背包备份 - */ - public List> - getInventoryBackups(String name, - int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id, save_time " - + "FROM inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time DESC " - + "LIMIT ?"); - ps.setString(1, name); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", - rs.getInt("id")); - row.put("save_time", - rs.getLong("save_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - /** - * 获取指定ID的备份数据 - */ - public String[] getInventoryBackupById( - int backupId) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM inventory_backups " - + "WHERE id=?"); - ps.setInt(1, backupId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - - // ==================== 工单操作 ==================== - - public int createTicket(String type, - String status, String priority, - String requester, String title, - String description, - int rewardAmount, - String targetPlayer) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO tickets " - + "(type, status, priority, " - + "requester, title, " - + "description, " - + "reward_amount, " - + "target_player, " - + "create_time) " - + "VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, type); - ps.setString(2, status); - ps.setString(3, priority); - ps.setString(4, requester); - ps.setString(5, title); - ps.setString(6, description); - ps.setInt(7, rewardAmount); - ps.setString(8, targetPlayer); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ResultSet rs = - ps.getGeneratedKeys(); - int id = -1; - if (rs.next()) id = rs.getInt(1); - rs.close(); - ps.close(); - return id; - } catch (SQLException e) { - e.printStackTrace(); - return -1; - } - } - - public void updateTicketStatus(int ticketId, - String status) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET status=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, status); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void assignTicket(int ticketId, - String provider) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "assigned_to=?, " - + "status='processing', " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, provider); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void completeTicket(int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='completed', " - + "complete_time=?, " - + "update_time=? " - + "WHERE id=?"); - long now = System.currentTimeMillis(); - ps.setLong(1, now); - ps.setLong(2, now); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void confirmTicket(int ticketId, - int score) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "admin_confirmed=1, " - + "score=?, " - + "status='resolved', " - + "update_time=? " - + "WHERE id=?"); - ps.setInt(1, score); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public void rejectTicketComplete(int ticketId, - String reason) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='rejected', " - + "reject_reason=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, reason); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 更新工单任意字段 - */ - public void updateTicketField(int ticketId, - String field, Object value) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " + field - + "=? WHERE id=?"); - if (value instanceof Integer) - ps.setInt(1, (Integer) value); - else if (value instanceof Long) - ps.setLong(1, (Long) value); - else - ps.setString(1, - String.valueOf(value)); - ps.setInt(2, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map getTicket( - int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE id=?"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getTicketsByStatus(String status) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status=? " - + "ORDER BY create_time DESC"); - ps.setString(1, status); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void addTicketReply(int ticketId, - String sender, String message) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO ticket_replies " - + "(ticket_id, sender, " - + "message, create_time) " - + "VALUES (?,?,?,?)"); - ps.setInt(1, ticketId); - ps.setString(2, sender); - ps.setString(3, message); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - // 更新工单状态为已回复 - updateTicketStatus(ticketId, - "replied"); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getTicketReplies(int ticketId) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM ticket_replies " - + "WHERE ticket_id=? " - + "ORDER BY create_time ASC"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - /** - * 获取已回复但超时未互动的工单 - */ - public List> - getRepliedStaleTickets( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='replied' " - + "AND update_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByPlayer(String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE requester=? " - + "ORDER BY create_time DESC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByProvider(String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "AND (status='processing' " - + "OR status='replied') " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getUnconfirmedCompleted( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='completed' " - + "AND admin_confirmed=0 " - + "AND complete_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 服务商操作 ==================== - - public void addServiceProvider(String name, - String role) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO " - + "service_providers " - + "(player_name, role, " - + "active, join_time) " - + "VALUES (?,?,1,?)"); - ps.setString(1, name); - ps.setString(2, role); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void removeServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM service_providers " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean isServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM service_providers " - + "WHERE player_name=? " - + "AND active=1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public List> - getAllServiceProviders() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM service_providers " - + "WHERE active=1"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Connection getDb() { - return db; - } -} diff --git a/backup/src/main/java/Sdf1_login/EmailManager.java b/backup/src/main/java/Sdf1_login/EmailManager.java deleted file mode 100644 index b6906e5..0000000 --- a/backup/src/main/java/Sdf1_login/EmailManager.java +++ /dev/null @@ -1,284 +0,0 @@ -package Sdf1_login; - -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.Socket; -import java.util.Base64; -import java.util.logging.Logger; - -public class EmailManager { - - private final ConfigManager config; - private final Logger logger; - - public EmailManager(ConfigManager config) { - this.config = config; - this.logger = Logger.getLogger("Sdf1_login"); - } - - public boolean isConfigured() { - String host = config.getSmtp("smtp地址"); - String user = config.getSmtp("smtp账号"); - String pass = config.getSmtp("smtp密码"); - return host != null && !host.isEmpty() - && user != null && !user.isEmpty() - && pass != null && !pass.isEmpty(); - } - - public boolean sendVerifyCode(String to, - String code) { - if (!isConfigured()) { - logger.warning( - "[Sdf1_login] SMTP未配置," - + "无法发送验证码"); - return false; - } - logger.info("[Sdf1_login] 发送验证码到 " + to); - return sendEmailSync(to, - "[Sdf1_login] 验证码", - "您的验证码: " + code - + "\n5分钟内有效。\n" - + "如非本人操作请忽略。"); - } - - public boolean sendTempPassword(String to, - String player, String tempPwd) { - if (!isConfigured()) { - logger.warning( - "[Sdf1_login] SMTP未配置"); - return false; - } - return sendEmailSync(to, - "[Sdf1_login] 临时密码", - "玩家 " + player - + " 的临时密码: " + tempPwd - + "\n请尽快登录并修改密码!"); - } - - public boolean sendBody(String to, String subject, String body) { - if (!isConfigured()) { - logger.warning("[Sdf1_login] SMTP未配置"); - return false; - } - return sendEmailSync(to, subject, body); - } - - - private boolean sendEmailSync(String to, - String subject, String body) { - final boolean[] result = {false}; - Thread t = new Thread(() -> { - try { - result[0] = sendEmail( - to, subject, body); - } catch (Exception e) { - logger.severe( - "[Sdf1_login] 邮件线程异常: " - + e.getMessage()); - } - }, "Sdf1_login-SMTP"); - t.setDaemon(true); - t.start(); - try { - t.join(15000); - } catch (InterruptedException ignored) { - } - if (t.isAlive()) { - t.interrupt(); - logger.severe( - "[Sdf1_login] SMTP超时(15秒)"); - return false; - } - return result[0]; - } - - private boolean sendEmail(String to, - String subject, String body) { - String host = config.getSmtp("smtp地址"); - String portStr = config.getSmtp("smtp端口"); - String user = config.getSmtp("smtp账号"); - String pass = config.getSmtp("smtp密码"); - String fromName = config.getSmtp( - "发件人名称"); - - int port; - try { - port = Integer.parseInt(portStr); - } catch (Exception e) { - port = 465; - } - - try { - String encSubject = "=?UTF-8?B?" - + Base64.getEncoder() - .encodeToString( - subject.getBytes("UTF-8")) - + "?="; - String encBody = Base64.getEncoder() - .encodeToString( - body.getBytes("UTF-8")); - - if (port == 465) { - SSLSocketFactory factory = - (SSLSocketFactory) - SSLSocketFactory - .getDefault(); - SSLSocket socket = (SSLSocket) - factory.createSocket( - host, port); - socket.setSoTimeout(10000); - doSmtp(socket, host, user, pass, - to, encSubject, encBody, - fromName); - socket.close(); - } else { - Socket plainSocket = - new Socket(host, port); - plainSocket.setSoTimeout(10000); - BufferedReader preReader = - new BufferedReader( - new InputStreamReader( - plainSocket - .getInputStream(), - "UTF-8")); - PrintWriter preWriter = - new PrintWriter( - plainSocket - .getOutputStream(), - true); - checkResponse(preReader, "220"); - sendLine(preWriter, - "EHLO " + host); - checkResponse(preReader, "250"); - sendLine(preWriter, "STARTTLS"); - checkResponse(preReader, "220"); - SSLSocketFactory factory = - (SSLSocketFactory) - SSLSocketFactory - .getDefault(); - SSLSocket sslSocket = (SSLSocket) - factory.createSocket( - plainSocket, host, - port, true); - sslSocket.startHandshake(); - doSmtp(sslSocket, host, user, pass, - to, encSubject, encBody, - fromName); - sslSocket.close(); - } - logger.info( - "[Sdf1_login] 邮件发送成功 -> " - + to); - return true; - } catch (Exception e) { - logger.severe( - "[Sdf1_login] 邮件异常: " - + e.getClass() - .getSimpleName() - + ": " + e.getMessage()); - if (e.getCause() != null) { - logger.severe( - "[Sdf1_login] 根因: " - + e.getCause() - .getMessage()); - } - return false; - } - } - - private void doSmtp(SSLSocket socket, - String host, String user, String pass, - String to, String subject, String body, - String fromName) throws Exception { - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - socket.getInputStream(), - "UTF-8")); - OutputStream rawOut = - socket.getOutputStream(); - PrintWriter writer = - new PrintWriter( - new java.io.BufferedOutputStream( - rawOut), true); - - checkResponse(reader, "220"); - sendLine(writer, "EHLO " + host); - checkResponse(reader, "250"); - sendLine(writer, "AUTH LOGIN"); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder() - .encodeToString( - user.getBytes("UTF-8"))); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder() - .encodeToString( - pass.getBytes("UTF-8"))); - checkResponse(reader, "235"); - sendLine(writer, - "MAIL FROM:<" + user + ">"); - checkResponse(reader, "250"); - sendLine(writer, - "RCPT TO:<" + to + ">"); - checkResponse(reader, "250"); - sendLine(writer, "DATA"); - checkResponse(reader, "354"); - sendLine(writer, - "From: " + fromName - + " <" + user + ">"); - sendLine(writer, - "To: <" + to + ">"); - sendLine(writer, - "Reply-To: <" + user + ">"); - sendLine(writer, - "Subject: " + subject); - sendLine(writer, "MIME-Version: 1.0"); - sendLine(writer, - "Content-Type: text/plain; " - + "charset=UTF-8"); - sendLine(writer, - "Content-Transfer-Encoding: " - + "base64"); - sendLine(writer, ""); - sendLine(writer, body); - sendLine(writer, "."); - checkResponse(reader, "250"); - sendLine(writer, "QUIT"); - } - - private void sendLine(PrintWriter writer, - String line) { - writer.print(line + "\r\n"); - writer.flush(); - } - - private void checkResponse( - BufferedReader reader, - String expected) throws Exception { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - if (line.length() >= 4 - && Character.isDigit( - line.charAt(0)) - && line.charAt(3) == ' ') - break; - } - String resp = sb.toString().trim(); - if (!resp.startsWith(expected)) { - if (resp.startsWith("535")) - logger.severe( - "[Sdf1_login] SMTP 535 " - + "认证失败"); - throw new Exception( - "SMTP响应异常: 期望 " - + expected - + ",实际: " + resp); - } - } -} diff --git a/backup/src/main/java/Sdf1_login/GUIManager.java b/backup/src/main/java/Sdf1_login/GUIManager.java deleted file mode 100644 index cbc7102..0000000 --- a/backup/src/main/java/Sdf1_login/GUIManager.java +++ /dev/null @@ -1,2681 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.*; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - - - - -import java.util.*; - -public class GUIManager implements Listener { - - private final Main plugin; - - public static final String T_MAIN = - "§6§l服务器菜单"; - public static final String T_MORE = - "§6§l更多功能"; - public static final String T_EDITOR = - "§6§l编辑菜单项"; - public static final String T_BIOME = - "§b§l目标群系传送"; - public static final String T_MY_INFO = - "§6§l我的信息"; - public static final String T_GIFT_STAGES = - "§6§l礼包阶段"; - public static final String T_ADMIN = - "§6§l管理员面板"; - public static final String T_INVITE = - "§6§l邀请数据"; - public static final String T_USER_MGMT = - "§6§l用户管理"; - public static final String T_TASK_CENTER = - "§6§l任务中心"; - private final Map userMgmtPages = new HashMap<>(); - private static final int UMGMT_PAGE_SIZE = 45; - - - - public GUIManager(Main plugin) { - this.plugin = plugin; - } - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_MAIN); -// ===== 自定义菜单项 ===== - if (plugin.getMenu() != null) { - List customs = - plugin.getMenu().getItems(); - - // 已占用的槽位 - Set occupied = new HashSet<>(); - occupied.add(10); - occupied.add(12); - occupied.add(14); - occupied.add(16); - occupied.add(19); - occupied.add(21); - occupied.add(23); - occupied.add(25); - occupied.add(29); - occupied.add(31); - occupied.add(32); - occupied.add(34); - - // 自定义区域:第1排(0-8) + 第5排(36-44) + 第6排(45-53) - int[] customArea = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, - 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 52, 53 - }; - - int autoIdx = 0; - for (int ci = 0; - ci < customs.size() && ci < 27; ci++) { - MenuManager.MenuItem mi = - customs.get(ci); - - // 权限过滤 - if ("OP".equals(mi.permType) - && !p.isOp() - && !isAdmin(p)) continue; - - int targetSlot = -1; - - // 玩家指定了格子? - if (mi.slot != null) { - targetSlot = findBestSlot( - mi.slot - 1, occupied); - } - - // 未指定或冲突 → 自动排队 - if (targetSlot < 0) { - while (autoIdx < customArea.length) { - int s = customArea[autoIdx++]; - if (!occupied.contains(s)) { - targetSlot = s; - break; - } - } - } - - if (targetSlot < 0 - || targetSlot >= 54) continue; - occupied.add(targetSlot); - - // 放置物品 - ItemStack icon = - mi.icon.clone(); - ItemMeta im = - icon.getItemMeta(); - im.setDisplayName( - "§e§l" + mi.title); - im.setLore(java.util.Arrays.asList( - "§7指令: §f" - + mi.command, - "§7权限: §f" - + mi.permType, - "", - "§a左键执行")); - icon.setItemMeta(im); - g.setItem(targetSlot, icon); - } - } - -// 背景填充 - fillBg(g); - p.openInventory(g); - - // ===== Row1(9-17): 核心功能 ===== - g.setItem(10, mkItem(Material.PLAYER_HEAD, - "§a§l我的信息")); - g.setItem(12, mkItem(Material.PAPER, - "§b§l邀请数据")); - g.setItem(14, mkItem(Material.EMERALD, - "§d§l积分商城")); - g.setItem(16, mkItem(Material.COMPASS, - "§b§l群系传送", - "§7传送到指定群系")); - - // ===== Row2(18-26): 日常功能 ===== - g.setItem(19, mkItem(Material.BOOK, - "§6工单系统")); - g.setItem(21, mkItem(Material.EMERALD_BLOCK, - "§a§l每日签到", - "§7签到获取积分和惊喜奖励")); - g.setItem(23, mkItem(Material.ENDER_CHEST, - "§6§l任务中心")); - if (p.isOp() || isAdmin(p)) { - g.setItem(25, mkItem(Material.HOPPER, - "§6§l垃圾回收站", - "§7查看和取回清理的物品")); - - - } - // ===== Row3(27-35): 管理功能 ===== - // ★ 商店 - 所有人都能看 ★ - g.setItem(27, mkItem(Material.CHEST, - "§6§l商店", - "§7浏览、购买、出售物品", - "§7货币: 债券")); - - if (isAdmin(p)) { - g.setItem(29, mkItem( - Material.REDSTONE_BLOCK, - "§c§l管理员面板")); - } - if (p.isOp()) { - g.setItem(31, mkItem(Material.ECHO_SHARD, - "§c§l管理菜单", - "§7添加、编辑、删除菜单项")); - } - // 在 openMain 方法中,Row3 区域加: - - // ★ 余额查询 - 所有人都能看 ★ - double myBal = 0; - try { - var reg = plugin.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault.economy - .Economy.class); - if (reg != null) - myBal = reg.getProvider() - .getBalance(p); - } catch (Exception ignored) {} - int myBonds = plugin.getBonds().getBonds(p.getName()); - g.setItem(32, mkItem(Material.EMERALD, - "§b§l我的钱包", - "§7查看债券余额与流水记录")); - -// ★ 余额操作 - 仅OP和tag玩家 ★ - if (isAdmin(p)) { - g.setItem(34, mkItem(Material.GOLD_INGOT, - "§6§l余额操作", - "§7给钱、扣钱")); - } - - - - // ===== Row0(0-8): 自定义菜单项 ===== - if (plugin.getMenu() != null) { - List customs = - plugin.getMenu().getItems(); - for (int i = 0; - i < customs.size() && i < 9; - i++) { - MenuManager.MenuItem mi = - customs.get(i); - if ("OP".equals(mi.permType) - && !p.isOp() - && !isAdmin(p)) continue; - ItemStack icon = - mi.icon.clone(); - ItemMeta im = - icon.getItemMeta(); - im.setDisplayName( - "§e§l" + mi.title); - im.setLore(Arrays.asList( - "§7指令: §f" - + mi.command, - "§7权限: §f" - + mi.permType, - "", - "§a左键执行")); - icon.setItemMeta(im); - g.setItem(i, icon); - } - } - - // 背景最后填充(不覆盖物品) - fillBg(g); - p.openInventory(g); - } - private int findBestSlot( - int requested, - Set occupied) { - // 目标位空闲直接用 - if (requested >= 0 && requested < 54 - && !occupied.contains(requested)) { - return requested; - } - // BFS上下左右找最近空位 - int row = requested / 9; - int col = requested % 9; - boolean[][] visited = - new boolean[6][9]; - java.util.Queue queue = - new java.util.LinkedList<>(); - queue.add(new int[]{row, col}); - visited[row][col] = true; - int[][] dirs = { - {-1, 0}, {1, 0}, - {0, -1}, {0, 1}}; - while (!queue.isEmpty()) { - int[] cur = queue.poll(); - for (int[] d : dirs) { - int nr = cur[0] + d[0]; - int nc = cur[1] + d[1]; - if (nr >= 0 && nr < 6 - && nc >= 0 && nc < 9 - && !visited[nr][nc]) { - visited[nr][nc] = true; - int s = nr * 9 + nc; - if (!occupied.contains(s)) { - return s; - } - queue.add(new int[]{nr, nc}); - } - } - } - return -1; - } - - public void openMenuManager(Player p) { - List list = - plugin.getMenu().getItems(); - Inventory g = Bukkit.createInventory( - null, 27, - "§c§l菜单管理"); - fillBg(g); - - for (int i = 0; - i < list.size() && i < 9; i++) { - MenuManager.MenuItem mi = list.get(i); - g.setItem(i, mkItem( - mi.icon.getType(), - "§e" + mi.title, - "§7指令: " + mi.command, - "§7权限: " + mi.permType, - "", - "§a左键编辑", - "§7右键切换权限")); - } - - g.setItem(22, mkItem(Material.LIME_WOOL, - "§a§l添加菜单项")); - - g.setItem(26, mkItem(Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - - public void openBalanceOps(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§d§l余额操作"); - fillBg(g); - - net.milkbowl.vault.economy.Economy eco = null; - try { - var reg = plugin.getServer().getServicesManager() - .getRegistration(net.milkbowl.vault.economy.Economy.class); - if (reg != null) eco = reg.getProvider(); - } catch (Exception ignored) {} - - int slot = 0; - for (Player op : Bukkit.getOnlinePlayers()) { - if (slot >= 45) break; - double bal = 0; - if (eco != null) bal = eco.getBalance(op); - boolean isSelf = op.getName().equals(p.getName()); - int bondBal = plugin.getBonds().getBonds(op.getName()); - g.setItem(slot, mkItem( - isSelf ? Material.EMERALD_BLOCK : Material.PLAYER_HEAD, - "§e" + op.getName(), - "§7金币: §a$" + String.format("%.2f", bal), - "§7债券: §e" + bondBal + " §6枚", - "", - "§a左键经济(±) §6右键债券(±)")); - slot++; - } - - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - public void openShop(Player p) { - p.closeInventory(); - plugin.getShopManager().openShopMain(p); - } - - public void openTicket(Player p) { - p.closeInventory(); - p.performCommand("sdf1_login ticket"); - } - @EventHandler - public void onInvClose( - - InventoryCloseEvent e) { - if (!(e.getPlayer() instanceof Player)) - return; - if (!(e.getPlayer() instanceof Player)) return; - Player p = (Player) e.getPlayer(); - Player p3 = (Player) e.getPlayer(); - String t = e.getView().getTitle(); - if (!(e.getPlayer() instanceof Player)) return; - Player p2 = (Player) e.getPlayer(); - - if (!t.equals(T_EDITOR)) return; - - // ★ 编辑器关闭时清理空项 ★ - List list = - plugin.getMenu().getItems(); - if (!list.isEmpty()) { - MenuManager.MenuItem last = - list.get(list.size() - 1); - if ((last.command == null - || last.command.trim().isEmpty() - || last.command.trim() - .equals("/")) - && "新菜单项" - .equals(last.title)) { - list.remove(list.size() - 1); - } - } - } - - public void openSubMenu( - Player p, String fileName) { - String menuTitle = - "§6§l" + fileName - .replace(".txt", ""); - Inventory g = Bukkit.createInventory( - null, 54, menuTitle); - fillBg(g); - - java.io.File f = new java.io.File( - plugin.getDataFolder(), fileName); - if (!f.exists()) { - p.sendMessage("§c文件不存在: " - + fileName); - return; - } - try { - List lines = - java.nio.file.Files - .readAllLines( - f.toPath(), - java.nio.charset - .StandardCharsets - .UTF_8); - List items = - new ArrayList<>(); - StringBuilder buf = - new StringBuilder(); - boolean inItem = false; - - for (String raw : lines) { - String l = raw.trim(); - if (l.startsWith("#") - || l.startsWith("//")) - continue; - if (l.contains("")) { - inHtml = false; - l = l.substring( - l.indexOf("-->") + 3) - .trim(); - } else { - plugin.getLogger().info( - "[Menu] L" + lineNum - + ": [HTML注释中]"); - continue; - } - } - - // ===== 第3优先:整行注释(最高裁决) ===== - // 以 # 或 // 开头的行,直接跳过 - // 不管后面包含什么 /* "); - pw.println(); - pw.println("{"); - pw.println(" 标题:主城传送"); - pw.println(" 指令:/spawn"); - pw.println(" 权限类型:玩家"); - pw.println(" 图标:COMPASS"); - pw.println("}"); - pw.println(); - pw.println("{"); - pw.println(" 标题:随机传送"); - pw.println(" 指令:/rtp"); - pw.println(" 权限类型:玩家"); - pw.println(" 图标:ENDER_PEARL"); - pw.println("}"); - } catch (Exception ignored) { - } - } - - // ===== 保存 ===== - public void saveMenu() { - File f = new File( - plugin.getDataFolder(), - "菜单.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# 服务器菜单"); - pw.println(); - for (MenuItem mi : items) { - pw.println("{"); - pw.println(" 标题:" - + mi.title); - pw.println(" 指令:" - + mi.command); - pw.println(" 权限类型:" - + mi.permType); - // ★ 保存实际材质名 ★ - pw.println(" 图标:" - + mi.icon.getType().name()); - pw.println("}"); - pw.println(); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Menu] 保存失败: " - + e.getMessage()); - } - } - - // ===== 聊天编辑 ===== - public boolean isEditing(String name) { - return chatWait.contains(name); - } - - public void editTitle( - Player p, int idx) { - chatIdx.put(p.getName(), idx); - chatWait.add(p.getName()); - chatField.put(p.getName(), "title"); - p.closeInventory(); - p.sendMessage("§e请输入菜单标题:"); - } - - public void editCommand( - Player p, int idx) { - chatIdx.put(p.getName(), idx); - chatWait.add(p.getName()); - chatField.put(p.getName(), "command"); - p.closeInventory(); - p.sendMessage("§e请输入指令" - + "(自动补全/):"); - } - - public void onChat(Player p, String input) { - String name = p.getName(); - if (!chatWait.remove(name)) return; - - String field = chatField.remove(name); - Integer idx = chatIdx.get(name); - if (idx == null || idx >= items.size()) return; - - MenuItem mi = items.get(idx); - - if ("title".equals(field)) { - mi.title = input; - p.sendMessage("§a标题: " + input); - } else if ("command".equals(field)) { - if (!input.startsWith("/")) - input = "/" + input; - mi.command = input; - p.sendMessage("§a指令: " + input); - } - - Bukkit.getScheduler().runTask(plugin, () -> - plugin.gui.openEditor(p, idx)); - } -} diff --git a/backup/src/main/java/Sdf1_login/OrderManager.java b/backup/src/main/java/Sdf1_login/OrderManager.java deleted file mode 100644 index 9dcb625..0000000 --- a/backup/src/main/java/Sdf1_login/OrderManager.java +++ /dev/null @@ -1,1345 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.logging.Level; - -public class OrderManager implements Listener { - private final Main plugin; - private final List orders = - Collections.synchronizedList(new ArrayList<>()); - private final Map userViewPage = new HashMap<>(); - private final Map adminViewPage = new HashMap<>(); - private final Map adminViewTarget = new HashMap<>(); - private final Set partialRefundListening = new HashSet<>(); - private final Map partialRefundTarget = new HashMap<>(); - private final Map receiptBuilderSection = new HashMap<>(); - private String tplHeader = ""; - private String tplItemLine = "{品名} x{数量} {原价}→{执行价} 小计{小计}枚"; - private String tplDiscountLine = "优惠: -{优惠额}枚 ({优惠类型})"; - private String tplPackLine = "打包: +{打包费}枚 ({打包颜色})"; - private String tplSummary = "原价{原价合计} → 实付{实付}"; - private String tplFooter = "感谢惠顾,欢迎再来!"; - private static final long RETENTION_30D = 30L * 24 * 60 * 60 * 1000; - private static final long SELF_REFUND_WINDOW = 5 * 60 * 1000; - private static final int PAGE_SIZE = 21; - private static final int[] SLOTS = { - 10,11,12,13,14,15,16,19,20,21,22,23,24,25,28,29,30,31,32,33,34}; - // ===== 字段 ===== - private final Set shulkerUsedOrders = - Collections.synchronizedSet(new HashSet<>()); - // ===== 待审批的退货退款 ===== - private final Map pendingReturns = - new HashMap<>(); - // ===== 退货申请确认 ===== - private final Map returnConfirmWaiting = - new HashMap<>(); // uuid -> orderId - private static final int RETURN_CONFIRM_TIMEOUT = 600; // 30秒 - - - private static class PendingReturn { - long orderId; - UUID playerUUID; - String playerName; - int totalPaid; - long createTime; - String packColor; - String packType; - List items; - - PendingReturn(OrderRecord r) { - this.orderId = r.orderId; - this.playerUUID = r.uuid; - this.playerName = r.player; - this.totalPaid = r.totalPaid; - this.createTime = System.currentTimeMillis(); - this.packColor = r.packColor; - this.packType = r.packType; - this.items = new ArrayList<>(r.items); - } - } - - - public static class OrderItem { - public String name; public String mat; - public int originalPrice; public int finalPrice; public int qty; - public OrderItem(String n,String m,int op,int fp,int q){ - name=n;mat=m;originalPrice=op;finalPrice=fp;qty=q;} - public int subtotal(){return finalPrice*qty;} - public int originalSubtotal(){return originalPrice*qty;} - } - public static class OrderRecord { - public long orderId; public UUID uuid; public String player; - public List items; public int totalOriginal; public int totalPaid; - public int discount; public String discountType; public String couponCode; - public int packFee; public String packType; public String packColor; - public long timestamp; public long printTime; public int status; - public int refundedAmount; public String refundType; - } - - public OrderManager(Main plugin) { - this.plugin = plugin; - loadReceiptConfig(); - loadOrders(); - startCleanupTask(); - Bukkit.getPluginManager().registerEvents(this, plugin); - } - public List getOrders() { return orders; } - - // ===== 小票模板 ===== - private void loadReceiptConfig() { - File f = new File(plugin.getDataFolder(), "小票设置.txt"); - if (!f.exists()) createDefaultReceiptConfig(f); - Map m = loadMap(f); - tplHeader = m.getOrDefault("头部", tplHeader); - tplItemLine = m.getOrDefault("商品行", tplItemLine); - tplDiscountLine = m.getOrDefault("优惠行", tplDiscountLine); - tplPackLine = m.getOrDefault("打包行", tplPackLine); - tplSummary = m.getOrDefault("汇总", tplSummary); - tplFooter = m.getOrDefault("页脚", tplFooter); - } - private void saveReceiptConfig() { - File f = new File(plugin.getDataFolder(), "小票设置.txt"); - List L = new ArrayList<>(); - L.add("# 小票模板配置"); - L.add("头部=" + tplHeader); - L.add("商品行=" + tplItemLine); - L.add("优惠行=" + tplDiscountLine); - L.add("打包行=" + tplPackLine); - L.add("汇总=" + tplSummary); - L.add("页脚=" + tplFooter); - writeLines(f, L); - } - private void createDefaultReceiptConfig(File f) { - List L = new ArrayList<>(); - L.add("# 小票模板配置"); - L.add("头部=§6§l{商店名}§r\n购物小票\n订单#{订单号} {订单时间}\n玩家: {玩家}"); - L.add("商品行=§e{品名}§r x{数量} {原价}→{执行价} 小计{小计}枚"); - L.add("优惠行=优惠: §a-{优惠额}枚§r ({优惠类型}{优惠券码})"); - L.add("打包行=打包: §c+{打包费}枚§r ({打包颜色})"); - L.add("汇总=原价{原价合计}枚 → 实付§6{实付}枚§r 余额{余额}枚"); - L.add("页脚=感谢惠顾,欢迎再来!"); - writeLines(f, L); - } - - // ===== 订单记录 ===== - public void recordOrder(Player p, List items, - int totalOriginal, int totalPaid, int discount, - String discountType, String couponCode, int packFee, - String packType, String packColor) { - OrderRecord r = new OrderRecord(); - r.orderId = System.currentTimeMillis(); - r.uuid = p.getUniqueId(); r.player = p.getName(); - r.items = items; r.totalOriginal = totalOriginal; - r.totalPaid = totalPaid; r.discount = discount; - r.discountType = discountType; r.couponCode = couponCode; - r.packFee = packFee; r.packType = packType; - r.packColor = packColor; r.timestamp = r.orderId; - r.printTime = 0; r.status = 0; r.refundedAmount = 0; - r.refundType = null; - orders.add(r); saveOrders(); - } - @EventHandler - public void onBlockPlace(org.bukkit.event.block.BlockPlaceEvent e) { - ItemStack item = e.getItemInHand(); - if (item == null) return; - if (!item.getType().name().contains("SHULKER")) return; - - ItemMeta meta = item.getItemMeta(); - if (meta == null || meta.getLore() == null) return; - - for (String line : meta.getLore()) { - if (line.startsWith("§8#")) { - try { - long orderId = Long.parseLong( - line.replace("§8#", "").trim()); - shulkerUsedOrders.add(orderId); - } catch (NumberFormatException ignored) {} - } - } - } - - // ===== 小票聊天栏 ===== - /** - * 聊天栏打印小票(给定参数 Player + OrderRecord) - * 不要改动方法签名 - */ - public void sendReceiptChat(Player p, OrderRecord r) { - if (p == null || r == null) return; - - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - p.sendMessage("§7§m "); - p.sendMessage("§6§l======== 商店购物小票 ========"); - - // 退款标记 - String statusTag = getStatusTag(r); - if (!statusTag.isEmpty()) { - p.sendMessage(statusTag); - } - - p.sendMessage("§7订单号: §f#" + r.orderId); - p.sendMessage("§7下单时间: §f" - + df.format(new Date(r.timestamp))); - p.sendMessage("§7玩家: §f" + r.player); - p.sendMessage("§7------------------------------"); - - for (OrderItem it : r.items) { - String price; - if (it.originalPrice != it.finalPrice) { - price = "§f" + it.originalPrice - + "§7→§6" + it.finalPrice; - } else { - price = "§6" + it.finalPrice; - } - p.sendMessage("§7 §e" + it.name - + " §7x" + it.qty - + " " + price + "枚" - + " §6小计" + it.subtotal() + "枚"); - } - - p.sendMessage("§7------------------------------"); - p.sendMessage("§7原价: §f" + r.totalOriginal + "枚"); - - if (r.discount > 0) { - int pct = (r.totalOriginal > 0) - ? (r.discount * 100 / r.totalOriginal) - : 0; - String dt = "coupon".equals(r.discountType) - ? "§7(优惠券 -" + pct + "%)" - : "eco".equals(r.discountType) - ? "§7(环保 -" + pct + "%)" - : "§7(-" + pct + "%)"; - p.sendMessage("§7优惠: §a-" + r.discount - + "枚 " + dt); - } - - if (r.packFee > 0) { - String cn = r.packColor != null - ? r.packColor - .replace("_SHULKER_BOX", "") - .replace("SHULKER_BOX", "原色") - : "默认"; - p.sendMessage("§7打包: §c+" + r.packFee - + "枚 §7(" + cn + "潜影盒)"); - } else { - p.sendMessage("§7包装: 直接放入背包"); - } - - p.sendMessage("§7------------------------------"); - p.sendMessage("§7实付: §6" + r.totalPaid + "枚"); - - int bal = 0; - try { - bal = plugin.getBonds().getBonds(r.player); - } catch (Exception ignored) {} - p.sendMessage("§7余额: §a" + bal + "枚"); - - // 退款标记 - if (!statusTag.isEmpty()) { - p.sendMessage(statusTag); - } - - p.sendMessage("§6§l=============================="); - r.printTime = System.currentTimeMillis(); - } - - /** - * 生成书本小票内容 - */ - private String buildReceiptText(OrderRecord r) { - String statusTag = getStatusTag(r); - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - StringBuilder sb = new StringBuilder(); - - sb.append("§6§l===== 商店小票 =====\n"); - - // 退款标记 - String tag = getStatusTag(r); - if (!tag.isEmpty()) sb.append(tag).append("\n"); - - sb.append("订单号: #").append(r.orderId).append("\n"); - sb.append("时间: ").append( - df.format(new Date(r.timestamp))).append("\n"); - sb.append("玩家: ").append(r.player).append("\n"); - sb.append("------------------------------\n"); - - for (OrderItem it : r.items) { - String pr = (it.originalPrice != it.finalPrice) - ? it.originalPrice + "->" + it.finalPrice - : String.valueOf(it.finalPrice); - sb.append(it.name).append(" x").append(it.qty) - .append(" ").append(pr).append("枚") - .append(" 小计").append(it.subtotal()) - .append("枚\n"); - } - - sb.append("------------------------------\n"); - sb.append("原价: ").append(r.totalOriginal) - .append("枚\n"); - - if (r.discount > 0) { - int pct = (r.totalOriginal > 0) - ? (r.discount * 100 / r.totalOriginal) - : 0; - String dt = "coupon".equals(r.discountType) - ? "优惠券(-" + pct + "%)" - : "环保(-" + pct + "%)"; - sb.append("优惠: -").append(r.discount) - .append("枚 ").append(dt).append("\n"); - } - - if (r.packFee > 0) { - sb.append("打包: +").append(r.packFee) - .append("枚\n"); - } - - sb.append("------------------------------\n"); - sb.append("实付: ").append(r.totalPaid).append("枚\n"); - - int bal = 0; - try { - bal = plugin.getBonds().getBonds(r.player); - } catch (Exception ignored) {} - sb.append("余额: ").append(bal).append("枚\n"); - - // 退款标记 - if (!statusTag.isEmpty()) sb.append("\n").append(tag).append("\n"); - - return sb.toString(); - } - - /** - * 退款状态标记文本 - */ - private String getStatusTag(OrderRecord r) { - switch (r.status) { - case 2: - return "§c§l<<<<<<< 已全额退款 >>>>>>>" - + "\n§c退款金额: " - + r.refundedAmount + "枚"; - case 3: - return "§c§l<<<<<<< 已部分退款 >>>>>>>" - + "\n§c退款金额: " - + r.refundedAmount + "枚"; - case 4: - return "§c§l<<<<<<< 退款申请已拒绝 >>>>>>>"; - default: - return ""; - } - } - - /** - * 创建小票书 - */ - public ItemStack createReceiptBook(OrderRecord r) { - String text = buildReceiptText(r); - String[] lines = text.split("\n"); - - ItemStack book = new ItemStack(Material.WRITTEN_BOOK); - BookMeta bm = (BookMeta) book.getItemMeta(); - bm.setTitle("购物小票 #" + r.orderId); - bm.setAuthor("商店"); - - StringBuilder page = new StringBuilder(); - int lineCount = 0; - for (String line : lines) { - // ★ 保留颜色代码 - page.append(line).append("\n"); - lineCount++; - if (lineCount >= 13) { - bm.addPage(page.toString().trim()); - page = new StringBuilder(); - lineCount = 0; - } - } - if (page.length() > 0) { - bm.addPage(page.toString().trim()); - } - - book.setItemMeta(bm); - return book; - } - - // ===== 退款工具 ===== - private boolean withinSelfRefundWindow(OrderRecord r) { return System.currentTimeMillis() - r.timestamp < SELF_REFUND_WINDOW; } - private int countDirectItems(Player p, String matName) { - Material m = Material.matchMaterial(matName); if (m == null) return 0; - int c = 0; for (ItemStack is : p.getInventory().getContents()) if (is != null && is.getType() == m) c += is.getAmount(); - return c; - } - private int countShulkers(Player p, String matName) { - Material m = Material.matchMaterial(matName); if (m == null) return 0; - int c = 0; for (ItemStack is : p.getInventory().getContents()) if (is != null && is.getType() == m) c += is.getAmount(); - return c; - } - private void removeItems(Player p, String matName, int amt) { - Material m = Material.matchMaterial(matName); if (m != null) p.getInventory().removeItem(new ItemStack(m, amt)); - } - private boolean removeOneShulker(Player p, - String materialName) { - // 先尝试精确匹配 - Material mat = Material.matchMaterial(materialName); - if (mat != null) { - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - p.getInventory().clear(i); - return true; - } - } - } - // 匹配不到,移除任意一个潜影盒 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType().name() - .endsWith("_SHULKER_BOX")) { - p.getInventory().clear(i); - return true; - } - } - return false; - } - public String validateRefund(OrderRecord r, Player p) { - if (r.status != 0) - return "订单状态: " + getStatusName(r.status); - - String pt = r.packType != null ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // ★ 检查:潜影盒是否被放置过 - if (shulkerUsedOrders.contains(r.orderId)) { - return "§c潜影盒已被使用过,无法自助退款"; - } - - // ★ 检查:背包里是否有同一订单的潜影盒 - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is == null) continue; - if (!is.getType().name().contains("SHULKER")) - continue; - ItemMeta im = is.getItemMeta(); - if (im == null || im.getLore() == null) - continue; - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - return null; // 找到了,可退 - } - } - } - return "§c对应的潜影盒已不在背包中"; - } else { - // ★ 散装:检测物品数量 - for (OrderItem it : r.items) { - int have = countDirectItems(p, it.mat); - if (have < it.qty) - return "§c" + it.name + "不足(有" - + have + "/" + it.qty + ")"; - } - } - return null; - } - - - - private void notifyPlayer(UUID uuid, String msg) { Player p = Bukkit.getPlayer(uuid); if (p != null && p.isOnline()) p.sendMessage(msg); } - private void notifyAdmins(Player p, OrderRecord r) { - String m = "§e[订单] " + p.getName() + " 申请退款 #" + r.orderId + " §6" + r.totalPaid + "枚"; - for (Player a : Bukkit.getOnlinePlayers()) if (isAdmin(a) && !a.equals(p)) a.sendMessage(m); - } - private String getStatusName(int s) { switch(s){case 0:return "正常";case 1:return "申请中";case 2:return "已退";case 3:return "部分退";case 4:return "已拒绝";default:return "未知";} } - private boolean isAdmin(Player p) { - if (p.isOp()) return true; - try { ConfigManager cm = plugin.getConfigMgr(); if (cm == null) return false; return p.hasPermission("sdf1.admin") || p.hasPermission("sdf1." + cm.adminTag); } catch (Exception e) { return p.hasPermission("sdf1.admin"); } - } - - // ===== 订单中心 ===== - public void openOrderCenter(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l订单中心"); - g.setItem(13, mkItem(Material.BOOK, "§e§l购买记录", "§7查看历史订单", "", "§a点击进入")); - if (isAdmin(p)) { g.setItem(31, mkItem(Material.NETHER_STAR, "§c§l管理员面板", "§7在线玩家购物记录", "§7支持退款操作", "", "§a点击进入")); - g.setItem(33, mkItem(Material.CRAFTING_TABLE, "§e§l小票配置", "§7配置小票模板样式", "", "§a点击进入")); } - g.setItem(49, mkItem(Material.ARROW, "§7返回商店")); - p.openInventory(g); - } - - // ===== 购买记录 ===== - public void openMyOrders(Player p, int page) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l购买记录"); - List list = getPlayerAllOrders(p.getUniqueId()); - int total = Math.max(1, (int) Math.ceil((double) list.size() / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); - userViewPage.put(p.getUniqueId(), page); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, list.size()); - for (int i = start; i < end; i++) { - int si = i - start; if (si >= SLOTS.length) break; - OrderRecord r = list.get(i); - Material icon; String st; String act; - if (r.status != 0) { icon = r.status == 1 ? Material.PAPER : Material.BARRIER; st = "§7状态: " + getStatusName(r.status); act = "§b左键: 补打小票"; } - else if (withinSelfRefundWindow(r)) { icon = Material.EMERALD; st = "§7状态: §a可退款(5分钟内)"; act = "§b左键: 补打小票\n§a右键: 退款退货\n§eShift+右键: 仅退款"; } - else { icon = Material.BOOK; st = "§7状态: §7已过自助退款期"; act = "§b左键: 补打小票\n§e右键: 申请退款退货\n§6Shift+右键: 申请仅退款"; } - ItemStack is = new ItemStack(icon); ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e订单 #" + r.orderId); - List lore = new ArrayList<>(); lore.add("§7" + fmtDate(r.timestamp)); - for (OrderItem it : r.items) lore.add("§7 §e" + it.name + " §7x" + it.qty + " §6" + it.subtotal() + "枚"); - lore.add("§7─────────"); lore.add("§7原价: §f" + r.totalOriginal + "枚"); - if (r.discount > 0) lore.add("§7优惠: §a-" + r.discount + "枚"); - lore.add("§7实付: §6" + r.totalPaid + "枚"); lore.add(st); lore.add(""); - for (String line : act.split("\n")) lore.add(line); - im.setLore(lore); is.setItemMeta(im); - } - g.setItem(SLOTS[si], is); - } - if (list.isEmpty()) g.setItem(31, mkItem(Material.BARRIER, "§7暂无订单记录")); - addPageButtons(g, page, total); - p.openInventory(g); - } - - // ===== 管理员面板 ===== - public void openAdminPanel(Player p, int page) { - if (!isAdmin(p)) { p.sendMessage("§c权限不足"); return; } - Inventory g = Bukkit.createInventory(null, 54, "§c§l管理员订单面板"); - Player[] online = Bukkit.getOnlinePlayers().toArray(new Player[0]); - int total = Math.max(1, (int) Math.ceil((double) online.length / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); adminViewPage.put(p.getUniqueId(), page); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, online.length); - // ★ 替换 openAdminPanel 中的物品创建部分 - for (int i = start; i < end; i++) { - int si = i - start; - if (si >= SLOTS.length) break; - Player t = online[i]; - List tl = getPlayerOrdersWithin( - t.getUniqueId(), RETENTION_30D); - int ts = 0; - for (OrderRecord r : tl) ts += r.totalPaid; - - ItemStack is = new ItemStack(Material.PLAYER_HEAD); - org.bukkit.inventory.meta.SkullMeta sm = - (org.bukkit.inventory.meta.SkullMeta) - is.getItemMeta(); - if (sm != null) { - sm.setOwner(t.getName()); // ★ 设置头颅皮肤 - sm.setDisplayName("§e" + t.getName()); - sm.setLore(Arrays.asList( - "§7订单: §f" + tl.size() + "笔", - "§7消费: §6" + ts + "枚", - "", "§a点击进入")); - is.setItemMeta(sm); // ★ 这行之前缺失了 - } - g.setItem(SLOTS[si], is); - } - - if (online.length == 0) g.setItem(31, mkItem(Material.BARRIER, "§7无在线玩家")); - addPageButtons(g, page, total); p.openInventory(g); - } - - // ===== 管理员订单 ===== - public void openAdminOrders(Player p, UUID targetUUID, int page) { - Inventory g = Bukkit.createInventory(null, 54, "§c§l订单管理"); - String name = Bukkit.getOfflinePlayer(targetUUID).getName(); - List list = getPlayerOrdersWithin(targetUUID, RETENTION_30D); - int total = Math.max(1, (int) Math.ceil((double) list.size() / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); - adminViewPage.put(p.getUniqueId(), page); adminViewTarget.put(p.getUniqueId(), targetUUID); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, list.size()); - for (int i = start; i < end; i++) { - int si = i - start; if (si >= SLOTS.length) break; - OrderRecord r = list.get(i); - Material icon = r.status == 2 || r.status == 4 ? Material.BARRIER : r.status == 1 ? Material.PAPER : Material.BOOK; - ItemStack is = new ItemStack(icon); ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e订单 #" + r.orderId); - List lore = new ArrayList<>(); lore.add("§7" + fmtDate(r.timestamp)); - for (OrderItem it : r.items) lore.add("§7 §e" + it.name + " §7x" + it.qty + " §6" + it.subtotal() + "枚"); - lore.add("§7─────────"); lore.add("§7实付: §6" + r.totalPaid + "枚"); - if (r.status == 1) { lore.add("§7状态: §e申请中(" + nn(r.refundType) + ")"); lore.add(""); lore.add("§a右键: 批准"); lore.add("§c左键: 拒绝"); } - else if (r.status == 2) lore.add("§7已退: §a" + r.refundedAmount + "枚"); - else if (r.status == 3) lore.add("§7部分退: §a" + r.refundedAmount + "枚"); - else if (r.status == 4) lore.add("§7已拒绝"); - else { lore.add(""); lore.add("§a右键: 整单退款"); lore.add("§e左键: 部分退款(输入%)"); } - im.setLore(lore); is.setItemMeta(im); - } - g.setItem(SLOTS[si], is); - } - if (list.isEmpty()) g.setItem(31, mkItem(Material.BARRIER, "§7该玩家暂无订单")); - addPageButtons(g, page, total); - g.setItem(49, mkItem(org.bukkit.Material.PAPER, name + " §e" + (page + 1) + "/" + total)); - p.openInventory(g); - } - private void addPageButtons(Inventory g, int page, int total) { - g.setItem(48, page > 0 ? mkItem(Material.ARROW, "§7上一页") : mkItem(Material.BLACK_STAINED_GLASS_PANE, " ")); - if (g.getItem(49) == null) g.setItem(49, mkItem(Material.PAPER, "§e" + (page + 1) + "/" + total)); - g.setItem(50, page < total - 1 ? mkItem(Material.ARROW, "§7下一页") : mkItem(Material.BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(53, mkItem(Material.ARROW, "§7返回")); - } - - // ===== 小票配置 ===== - public void openReceiptConfig(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l小票配置"); - g.setItem(10, mkItem(Material.PAPER, "§e§l头部", "§7" + brief(tplHeader), "", "§a点击编辑")); - g.setItem(11, mkItem(Material.PAPER, "§e§l商品行", "§7" + brief(tplItemLine), "", "§a点击编辑")); - g.setItem(12, mkItem(Material.PAPER, "§e§l优惠行", "§7" + brief(tplDiscountLine), "", "§a点击编辑")); - g.setItem(14, mkItem(Material.PAPER, "§e§l打包行", "§7" + brief(tplPackLine), "", "§a点击编辑")); - g.setItem(15, mkItem(Material.PAPER, "§e§l汇总", "§7" + brief(tplSummary), "", "§a点击编辑")); - g.setItem(16, mkItem(Material.PAPER, "§e§l页脚", "§7" + brief(tplFooter), "", "§a点击编辑")); - g.setItem(22, mkItem(Material.BOOK, "§6§l预览(随机价格)", "§7预览小票效果")); - g.setItem(48, mkItem(Material.LIME_WOOL, "§a§l保存")); g.setItem(49, mkItem(Material.ARROW, "§7返回")); - g.setItem(50, mkItem(Material.RED_WOOL, "§c§l重置")); - p.openInventory(g); - } - public void openReceiptBuilder(Player p, String section) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l编辑: " + sectionName(section)); - g.setItem(10, mkItem(Material.EMERALD, "§a{品名}")); g.setItem(11, mkItem(Material.EMERALD, "§a{原价}")); - g.setItem(12, mkItem(Material.EMERALD, "§a{执行价}")); g.setItem(13, mkItem(Material.EMERALD, "§a{数量}")); - g.setItem(14, mkItem(Material.EMERALD, "§a{小计}")); g.setItem(15, mkItem(Material.EMERALD, "§a{商店名}")); - g.setItem(16, mkItem(Material.EMERALD, "§a{玩家}")); g.setItem(19, mkItem(Material.DIAMOND, "§b{订单号}")); - g.setItem(20, mkItem(Material.DIAMOND, "§b{订单时间}")); g.setItem(21, mkItem(Material.DIAMOND, "§b{打印时间}")); - g.setItem(22, mkItem(Material.DIAMOND, "§b{日期}")); g.setItem(23, mkItem(Material.DIAMOND, "§b{时间}")); - g.setItem(24, mkItem(Material.DIAMOND, "§b{原价合计}")); g.setItem(25, mkItem(Material.DIAMOND, "§b{实付}")); - g.setItem(28, mkItem(Material.GOLD_INGOT, "§6{优惠额}")); g.setItem(29, mkItem(Material.GOLD_INGOT, "§6{优惠类型}")); - g.setItem(30, mkItem(Material.GOLD_INGOT, "§6{优惠券码}")); g.setItem(31, mkItem(Material.GOLD_INGOT, "§6{打包费}")); - g.setItem(32, mkItem(Material.GOLD_INGOT, "§6{打包颜色}")); g.setItem(33, mkItem(Material.GOLD_INGOT, "§6{余额}")); - g.setItem(37, mkItem(Material.STONE, "§f═══")); g.setItem(38, mkItem(Material.STONE, "§f───")); - g.setItem(39, mkItem(Material.STONE, "§f═════════════")); g.setItem(40, mkItem(Material.PAPER, "§e换行(\\n)")); - g.setItem(47, mkItem(Material.RED_STAINED_GLASS_PANE, "§c删末尾")); g.setItem(48, mkItem(Material.RED_WOOL, "§c清空")); - g.setItem(49, mkItem(Material.ARROW, "§7返回")); g.setItem(50, mkItem(Material.LIME_WOOL, "§a保存")); - g.setItem(51, mkItem(Material.BLUE_WOOL, "§9重置")); - p.openInventory(g); - } - // ===== 点击处理 ===== - public boolean handleCenterClick(Player p, int raw) { - if (raw == 13) { openMyOrders(p, 0); return true; } - if (raw == 31 && isAdmin(p)) { openAdminPanel(p, 0); return true; } - if (raw == 33 && isAdmin(p)) { openReceiptConfig(p); return true; } - if (raw == 49) { plugin.getShopManager().openShopMain(p); return true; } - return true; - } - public boolean handleMyOrdersClick(Player p, int raw, boolean left, boolean shift) { - List list = getPlayerAllOrders(p.getUniqueId()); - int page = userViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { openMyOrders(p, page - 1); return true; } - if (raw == 50) { openMyOrders(p, page + 1); return true; } - if (raw == 53) { openOrderCenter(p); return true; } - int si = slotIndex(raw); if (si < 0) return true; - int actual = si + page * PAGE_SIZE; if (actual < 0 || actual >= list.size()) return true; - OrderRecord r = list.get(actual); - if (left) { - try { ItemStack book = createReceiptBook(r); - HashMap ov = p.getInventory().addItem(book); - for (ItemStack d : ov.values()) p.getWorld().dropItemNaturally(p.getLocation(), d); - r.printTime = System.currentTimeMillis(); p.sendMessage("§a已补打小票书: #" + r.orderId); - } catch (Exception e) { p.sendMessage("§c补打失败"); } - } else { - if (r.status != 0) { - p.sendMessage("§c状态: " + getStatusName(r.status)); - } else if (withinSelfRefundWindow(r)) { - if (shift) { - // ★ Shift+右键:仅退款申请(需管理员审批) - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请,等待管理员审批"); - notifyAdmins(p, r); - } else { - // ★ 普通右键:5分钟内直接退(自动通过) - String reason = validateRefund(r, p); - if (reason != null) { - p.sendMessage("§c" + reason - + ",Shift+右键可申请仅退款"); - } else { - // 扣货 - String pt = r.packType != null - ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - if (isShulker) { - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory() - .getItem(i); - if (is != null && is.getType() - .name().contains("SHULKER")) { - p.getInventory().clear(i); - break; - } - } - } else { - for (OrderItem it : r.items) - removeItems(p, it.mat, it.qty); - } - // 退钱 - plugin.getBonds().addBonds( - r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - r.refundType = "auto_return"; - saveOrders(); - p.sendMessage("§a退款退货成功: §6" - + r.totalPaid + "枚"); - } - } - - - } else { - if (r.status != 0) { - p.sendMessage("§c状态: " - + getStatusName(r.status)); - } else if (withinSelfRefundWindow(r)) { - if (shift) { - // 仅退款申请 - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请," - + "等待管理员审批"); - notifyAdmins(p, r); - } else { - // 5分钟内退货退款 - String reason = validateRefund(r, p); - if (reason != null) { - // ★ 不满足条件:询问是否申请 - p.sendMessage("§c" + reason); - askReturnConfirm(p, r); - } else { - // 满足条件:自动退 - doAutoReturn(p, r); - } - } - } else { - if (shift) { - // 申请仅退款 - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请"); - notifyAdmins(p, r); - } else { - // ★ 申请退货退款:先检查条件 - String reason = validateRefund(r, p); - if (reason != null) { - p.sendMessage("§c" + reason); - } - // ★ 无论是否满足条件都询问 - askReturnConfirm(p, r); - } - } - } - } - if (left) { - // 已退款订单仍然允许补打小票,但小票内会显示退款标记 - try { - ItemStack book = createReceiptBook(r); - HashMap overflow = - p.getInventory().addItem(book); - for (ItemStack drop : overflow.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - r.printTime = System.currentTimeMillis(); - if (r.status != 0) { - p.sendMessage("§e已补打小票(含退款标记): #" - + r.orderId); - } else { - p.sendMessage("§a已补打小票书: #" - + r.orderId); - } - } catch (Exception e) { - p.sendMessage("§c补打失败"); - } - } - - final int fp = page; Bukkit.getScheduler().runTask(plugin, () -> openMyOrders(p, fp)); - return true; - } - public boolean handleAdminClick(Player p, int raw) { - Player[] online = Bukkit.getOnlinePlayers().toArray(new Player[0]); - int page = adminViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { openAdminPanel(p, page - 1); return true; } - if (raw == 50) { openAdminPanel(p, page + 1); return true; } - if (raw == 53) { openOrderCenter(p); return true; } - int si = slotIndex(raw); if (si < 0) return true; - int actual = si + page * PAGE_SIZE; if (actual < 0 || actual >= online.length) return true; - openAdminOrders(p, online[actual].getUniqueId(), 0); return true; - } - public boolean handleAdminOrdersClick(Player p, int raw, boolean left, boolean shift) { - UUID tu = adminViewTarget.get(p.getUniqueId()); - if (tu == null) return false; - List list = getPlayerOrdersWithin(tu, RETENTION_30D); - int page = adminViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { - openAdminOrders(p, tu, page - 1); - return true; - } - if (raw == 50) { - openAdminOrders(p, tu, page + 1); - return true; - } - if (raw == 53) { - openAdminPanel(p, 0); - return true; - } - int si = slotIndex(raw); - if (si < 0) return true; - int actual = si + page * PAGE_SIZE; - if (actual < 0 || actual >= list.size()) return true; - OrderRecord r = list.get(actual); - if (r.status == 2 || r.status == 3 || r.status == 4) { - p.sendMessage("§c状态: " + getStatusName(r.status)); - return true; - } - if (left) { - if (r.status == 1) { - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝: #" + r.orderId); - notifyPlayer(r.uuid, "§c退款被拒绝"); - openAdminOrders(p, tu, page); - } else if (r.status == 0) { - partialRefundListening.add(p.getUniqueId()); - partialRefundTarget.put(p.getUniqueId(), r); - p.closeInventory(); - p.sendMessage("§e输入退款百分比(1-100): #" + r.orderId + " 实付" + r.totalPaid + "枚"); - } - } else { - if (r.status == 0 || r.status == 1) { - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a全额退款 §e" + r.player + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, "§a退款 §6" + r.totalPaid + "枚"); - openAdminOrders(p, tu, page); - } - } - // ★ 替换 handleAdminOrdersClick 中的审批逻辑 - if (left) { - // 左键 = 拒绝 - if (r.status == 1) { - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝退款: #" + r.orderId); - notifyPlayer(r.uuid, "§c退款申请已被拒绝"); - openAdminOrders(p, tu, page); - } - } else { - // 右键 = 批准 - if (r.status == 1) { - String rt = r.refundType != null - ? r.refundType : ""; - - // ★ 退款退货申请 → 检查潜影盒 - if (rt.equals("refund_return_apply")) { - String pt = r.packType != null - ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // 检查玩家背包里有没有带订单标签的盒子 - Player target = Bukkit.getPlayer(r.uuid); - if (target == null || !target.isOnline()) { - p.sendMessage("§c玩家不在线,无法执行退货退款"); - openAdminOrders(p, tu, page); - return true; - } - - boolean foundTagged = false; - boolean foundAny = false; - for (int i = 0; i < 36; i++) { - ItemStack is = target.getInventory() - .getItem(i); - if (is == null) continue; - if (!is.getType().name() - .contains("SHULKER")) continue; - foundAny = true; - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - foundTagged = true; - // 收走盒子 - target.getInventory().clear(i); - break; - } - } - } - if (foundTagged) break; - } - - if (foundTagged) { - // ★ 有带标记的盒子 → 收走 + 退钱 - plugin.getBonds().addBonds( - r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" - + r.player + " §6" - + r.totalPaid + "枚(盒子已收走)"); - notifyPlayer(r.uuid, - "§a[订单] 退货退款成功,§6" - + r.totalPaid + "枚已退还"); - openAdminOrders(p, tu, page); - return true; - } else if (foundAny) { - // ★ 有盒子但没标记 → 等管理员决定 - pendingReturns.put(r.orderId, - new PendingReturn(r)); - p.closeInventory(); - p.sendMessage("§e§l[退货审批] 订单 #" - + r.orderId); - p.sendMessage("§7玩家背包有潜影盒但" - + "无订单标记,可能已被替换"); - p.sendMessage("§a输入 §e1 §a= 收走盒子并退款"); - p.sendMessage("§e输入 §e2 §e= 仅退款(不收盒子)"); - p.sendMessage("§c输入 §c3 §c= 拒绝退款"); - p.sendMessage("§72分钟内未操作自动拒绝"); - // 启动2分钟超时 - startReturnTimeout(r.orderId); - return true; - } else { - // ★ 背包里没有任何盒子 - p.sendMessage("§c玩家背包里没有潜影盒"); - p.sendMessage("§7可拒绝并通知玩家" - + "申请仅退款或把货拿回来"); - openAdminOrders(p, tu, page); - return true; - } - } - // ★ 散装退货退款:扣货+退钱 - Player target = Bukkit.getPlayer(r.uuid); - if (target != null && target.isOnline()) { - for (OrderItem it : r.items) - removeItems(target, it.mat, it.qty); - } - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" + r.player - + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, - "§a[订单] 退货退款成功,§6" - + r.totalPaid + "枚"); - openAdminOrders(p, tu, page); - return true; - } - - // ★ 仅退款申请 / 5分钟内自动退款 - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已批准退款 §e" + r.player - + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, - "§a[订单] 退款已批准,§6" - + r.totalPaid + "枚已退还"); - openAdminOrders(p, tu, page); - } - } - return true; - } - - /** - * 询问用户是否提交退货退款申请 - */ - private void askReturnConfirm(Player p, OrderRecord r) { - returnConfirmWaiting.put(p.getUniqueId(), r.orderId); - p.sendMessage(""); - p.sendMessage("§e§l[退货退款申请]"); - p.sendMessage("§7订单 #" + r.orderId - + " 实付" + r.totalPaid + "枚"); - p.sendMessage("§a输入 §e1 §a= 提交退货退款申请"); - p.sendMessage("§c输入 §e2 §c= 取消"); - p.sendMessage("§730秒内未操作自动取消"); - p.sendMessage(""); - - // 30秒超时 - final UUID uuid = p.getUniqueId(); - final long oid = r.orderId; - new BukkitRunnable() { - @Override - public void run() { - Long waiting = returnConfirmWaiting.get(uuid); - if (waiting != null && waiting == oid) { - returnConfirmWaiting.remove(uuid); - Player pl = Bukkit.getPlayer(uuid); - if (pl != null && pl.isOnline()) { - pl.sendMessage("§7退货退款申请已超时取消"); - } - } - } - }.runTaskLater(plugin, RETURN_CONFIRM_TIMEOUT); - } - - /** - * 执行5分钟内自动退货退款 - */ - private void doAutoReturn(Player p, OrderRecord r) { - String pt = r.packType != null ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // 找并收走带标签的盒子 - boolean found = false; - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is == null) continue; - if (!is.getType().name().contains("SHULKER")) - continue; - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - p.getInventory().clear(i); - found = true; - break; - } - } - } - if (found) break; - } - // 没找到标签盒子,收任意潜影盒 - if (!found) { - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType().name() - .contains("SHULKER")) { - p.getInventory().clear(i); - break; - } - } - } - } else { - for (OrderItem it : r.items) - removeItems(p, it.mat, it.qty); - } - - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - r.refundType = "auto_return"; - saveOrders(); - p.sendMessage("§a退款退货成功: §6" - + r.totalPaid + "枚"); - } - - - // ===== 2分钟超时 ===== - private void startReturnTimeout(long orderId) { - new BukkitRunnable() { - @Override - public void run() { - PendingReturn pr = pendingReturns - .remove(orderId); - if (pr == null) return; // 已处理 - Player admin = Bukkit.getPlayer(pr.playerUUID); - if (admin != null && admin.isOnline()) { - admin.sendMessage("§c[退货审批] 订单 #" - + orderId + " 超时,自动拒绝"); - } - // 通知玩家 - notifyPlayer(pr.playerUUID, - "§c[订单] 退货退款申请已超时拒绝"); - // 恢复订单状态 - OrderRecord r = findOrder(orderId); - if (r != null) { - r.status = 0; - r.refundType = null; - saveOrders(); - } - } - }.runTaskLater(plugin, 2400L); // 2分钟 - } - - // ===== 查找订单 ===== - private OrderRecord findOrder(long orderId) { - synchronized (orders) { - for (OrderRecord r : orders) { - if (r.orderId == orderId) return r; - } - } - return null; - } - - - public boolean handleReceiptConfigClick(Player p, int raw) { - if (raw == 49) { openOrderCenter(p); return true; } - if (raw == 48) { saveReceiptConfig(); p.sendMessage("§a已保存"); openReceiptConfig(p); return true; } - if (raw == 50) { createDefaultReceiptConfig(new File(plugin.getDataFolder(), "小票设置.txt")); loadReceiptConfig(); p.sendMessage("§e已重置"); openReceiptConfig(p); return true; } - if (raw == 22) { sendRandomPreview(p); return true; } - String sec = null; - if (raw == 10) sec = "header"; else if (raw == 11) sec = "itemLine"; else if (raw == 12) sec = "discountLine"; - else if (raw == 14) sec = "packLine"; else if (raw == 15) sec = "summary"; else if (raw == 16) sec = "footer"; - if (sec != null) { receiptBuilderSection.put(p.getUniqueId(), sec); openReceiptBuilder(p, sec); } - return true; - } - public boolean handleReceiptBuilderClick(Player p, int raw) { - String sec = receiptBuilderSection.get(p.getUniqueId()); if (sec == null) return false; - String token = getTokenBySlot(raw); - if (token != null) { String cur = getSection(sec); setSection(sec, cur.isEmpty() ? token : cur + token); openReceiptBuilder(p, sec); return true; } - if (raw == 47) { String c = getSection(sec); int lb = c.lastIndexOf('}'); if (lb >= 0) { int ls = c.lastIndexOf('{', lb); if (ls >= 0) setSection(sec, c.substring(0, ls)); } else setSection(sec, c.substring(0, Math.max(0, c.length() - 8))); openReceiptBuilder(p, sec); return true; } - if (raw == 48) { setSection(sec, ""); openReceiptBuilder(p, sec); return true; } - if (raw == 49) { receiptBuilderSection.remove(p.getUniqueId()); openReceiptConfig(p); return true; } - if (raw == 50) { saveReceiptConfig(); receiptBuilderSection.remove(p.getUniqueId()); p.sendMessage("§a已保存"); openReceiptConfig(p); return true; } - if (raw == 51) { setSection(sec, getDefaultVal(sec)); openReceiptBuilder(p, sec); return true; } - return true; - } - - // ===== 聊天事件 ===== - @EventHandler - public void onAdminChat(AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); UUID uuid = p.getUniqueId(); - // ★ 在 onAdminChat 方法开头添加 -// ===== 退货审批输入 ===== - for (Map.Entry entry : - new HashMap<>(pendingReturns).entrySet()) { - Long pendingOrderId = entry.getKey(); - PendingReturn pr = entry.getValue(); - if (pr.playerUUID.equals(p.getUniqueId()) - || isAdmin(p)) { - try { - int choice = Integer.parseInt( - e.getMessage().trim()); - pendingReturns.remove(pendingOrderId); - OrderRecord r = findOrder(pendingOrderId); - if (r == null) { - p.sendMessage("§c订单不存在"); - return; - } - - if (choice == 1) { - // ★ 收走盒子并退款 - Player target = Bukkit.getPlayer( - pr.playerUUID); - if (target != null && target.isOnline()) { - for (int i = 0; i < 36; i++) { - ItemStack is = target.getInventory() - .getItem(i); - if (is != null && is.getType().name() - .contains("SHULKER")) { - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + pendingOrderId)) { - target.getInventory().clear(i); - break; - } - } - } - break; - } - } - } - plugin.getBonds().addBonds(pr.playerName, pr.totalPaid); - r.status = 2; - r.refundedAmount = pr.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" + pr.playerName - + " §6" + pr.totalPaid + "枚(盒子已收走)"); - notifyPlayer(pr.playerUUID, - "§a[订单] 退货退款成功,§6" - + pr.totalPaid + "枚已退还"); - - } else if (choice == 2) { - // ★ 仅退款(不收盒子) - plugin.getBonds().addBonds(pr.playerName, pr.totalPaid); - r.status = 2; - r.refundedAmount = pr.totalPaid; - saveOrders(); - p.sendMessage("§e已仅退款 §e" + pr.playerName - + " §6" + pr.totalPaid + "枚(未收盒子)"); - notifyPlayer(pr.playerUUID, - "§e[订单] 仅退款成功,§6" - + pr.totalPaid + "枚已退还" - + ",盒子无需退回"); - - } else if (choice == 3) { - // ★ 拒绝 - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝退款 #" + pendingOrderId); - notifyPlayer(pr.playerUUID, - "§c[订单] 退货退款已被拒绝" - + ",您可以申请仅退款" - + "或把货拿回来后重新申请"); - } else { - p.sendMessage("§c请输入 1、2 或 3"); - pendingReturns.put(pendingOrderId, pr); - } - return; - - } catch (NumberFormatException ignored) { - // 不是数字,跳过 - } - } - } -// ★ 在 onAdminChat 方法开头,处理用户退货确认 - if (returnConfirmWaiting.containsKey(uuid)) { - String msg = e.getMessage().trim(); - if (msg.equals("1")) { - // ★ 确认提交 - Long orderId = returnConfirmWaiting.remove(uuid); - if (orderId != null) { - OrderRecord r = findOrder(orderId); - if (r != null) { - r.status = 1; - r.refundType = "refund_return_apply"; - saveOrders(); - p.sendMessage("§e已提交退货退款申请," - + "等待管理员审批"); - notifyAdmins(p, r); - } - } - e.setCancelled(true); - return; - } else if (msg.equals("2")) { - // ★ 取消 - returnConfirmWaiting.remove(uuid); - p.sendMessage("§7已取消退货退款申请"); - e.setCancelled(true); - return; - } else { - // 输入其他内容:取消等待 - returnConfirmWaiting.remove(uuid); - // 不 return,让消息正常发送 - } - } - - if (partialRefundListening.contains(uuid)) { - e.setCancelled(true); partialRefundListening.remove(uuid); - OrderRecord t = partialRefundTarget.remove(uuid); if (t == null) return; - try { int pct = Integer.parseInt(e.getMessage().trim()); if (pct < 1 || pct > 100) { p.sendMessage("§c1-100"); return; } - int ref = t.totalPaid * pct / 100; if (ref <= 0) ref = 1; - plugin.getBonds().addBonds(t.player, ref); t.status = 3; t.refundedAmount = ref; saveOrders(); - p.sendMessage("§a部分退款 §e" + t.player + " §6" + ref + "枚(" + pct + "%)"); - notifyPlayer(t.uuid, "§a退款 §6" + ref + "枚"); - final UUID tu = adminViewTarget.get(uuid); final int pg = adminViewPage.getOrDefault(uuid, 0); - Bukkit.getScheduler().runTask(plugin, () -> openAdminOrders(p, tu, pg)); - } catch (NumberFormatException ex) { p.sendMessage("§c请输入数字"); } catch (Exception ex) { plugin.getLogger().log(Level.WARNING, "[Order]退款异常", ex); } - return; - } - if (receiptBuilderSection.containsKey(uuid)) { - e.setCancelled(true); String sec = receiptBuilderSection.remove(uuid); if (sec == null) return; - String msg = e.getMessage().trim(); - if ("cancel".equalsIgnoreCase(msg)) { Bukkit.getScheduler().runTask(plugin, () -> openReceiptConfig(p)); return; } - setSection(sec, msg.replace("\\n", "\n")); p.sendMessage("§a已更新 §e" + sec); - Bukkit.getScheduler().runTask(plugin, () -> openReceiptConfig(p)); - } - } - - // ===== 数据查询 ===== - private List getPlayerAllOrders(UUID uuid) { - List r = new ArrayList<>(); - synchronized (orders) { for (OrderRecord o : orders) if (o.uuid.equals(uuid) && System.currentTimeMillis() - o.timestamp < RETENTION_30D) r.add(o); } - Collections.reverse(r); return r; - } - private List getPlayerOrdersWithin(UUID uuid, long ms) { - List r = new ArrayList<>(); long cut = System.currentTimeMillis() - ms; - synchronized (orders) { for (OrderRecord o : orders) if (o.uuid.equals(uuid) && o.timestamp >= cut) r.add(o); } - Collections.reverse(r); return r; - } - - // ===== 持久化 ===== - private void loadOrders() { - File f = new File(plugin.getDataFolder(), "orders.dat"); if (!f.exists()) return; - try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); - OrderRecord cur = null; String line; - while ((line = br.readLine()) != null) { line = line.trim(); - if (line.equals("==")) { cur = new OrderRecord(); cur.items = new ArrayList<>(); } - else if (cur != null && line.contains("=")) { int eq = line.indexOf('='); String k = line.substring(0, eq); String v = line.substring(eq + 1); - switch (k) { case "id": cur.orderId = Long.parseLong(v); cur.timestamp = cur.orderId; break; case "uuid": cur.uuid = UUID.fromString(v); break; - case "player": cur.player = v; break; case "totalOriginal": cur.totalOriginal = Integer.parseInt(v); break; case "totalPaid": cur.totalPaid = Integer.parseInt(v); break; - case "discount": cur.discount = Integer.parseInt(v); break; case "discountType": cur.discountType = v; break; case "couponCode": cur.couponCode = v; break; - case "packFee": cur.packFee = Integer.parseInt(v); break; case "packType": cur.packType = v; break; case "packColor": cur.packColor = v; break; - case "status": cur.status = Integer.parseInt(v); break; case "refundedAmount": cur.refundedAmount = Integer.parseInt(v); break; - case "printTime": cur.printTime = Long.parseLong(v); break; case "refundType": cur.refundType = v; break; - case "item": String[] pp = v.split("\\|"); if (pp.length >= 5) cur.items.add(new OrderItem(pp[0], pp[1], Integer.parseInt(pp[2]), Integer.parseInt(pp[3]), Integer.parseInt(pp[4]))); break; } - } else if (line.isEmpty() && cur != null && cur.uuid != null) { orders.add(cur); cur = null; } - } if (cur != null && cur.uuid != null) orders.add(cur); br.close(); - } catch (Exception e) { plugin.getLogger().warning("[Order]加载失败: " + e.getMessage()); } - } - private void saveOrders() { - File f = new File(plugin.getDataFolder(), "orders.dat"); - try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); - synchronized (orders) { for (OrderRecord r : orders) { pw.println("=="); - pw.println("id=" + r.orderId); pw.println("uuid=" + r.uuid); pw.println("player=" + r.player); - pw.println("totalOriginal=" + r.totalOriginal); pw.println("totalPaid=" + r.totalPaid); - pw.println("discount=" + r.discount); pw.println("discountType=" + nn(r.discountType)); - pw.println("couponCode=" + nn(r.couponCode)); pw.println("packFee=" + r.packFee); - pw.println("packType=" + nn(r.packType)); pw.println("packColor=" + nn(r.packColor)); - pw.println("status=" + r.status); pw.println("refundedAmount=" + r.refundedAmount); - pw.println("printTime=" + r.printTime); pw.println("refundType=" + nn(r.refundType)); - for (OrderItem it : r.items) pw.println("item=" + it.name + "|" + it.mat + "|" + it.originalPrice + "|" + it.finalPrice + "|" + it.qty); - } } pw.flush(); pw.close(); - } catch (Exception e) { plugin.getLogger().warning("[Order]保存失败: " + e.getMessage()); } - } - private void startCleanupTask() { new BukkitRunnable() { public void run() { synchronized (orders) { if (orders.removeIf(OrderManager.this::isExpired)) saveOrders(); } } }.runTaskTimer(plugin, 36000L, 36000L); } - /** - * 将小票书塞入潜影盒 - */ - public void addBookToShulker(ItemStack shulker, ItemStack book) { - try { - org.bukkit.inventory.meta.BlockStateMeta bm = - (org.bukkit.inventory.meta.BlockStateMeta) - shulker.getItemMeta(); - if (bm == null) return; - org.bukkit.block.BlockState st = bm.getBlockState(); - if (st instanceof org.bukkit.block.Container) { - ((org.bukkit.block.Container) st) - .getInventory().addItem(book); - bm.setBlockState(st); - shulker.setItemMeta(bm); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Order] 塞书失败: " + e.getMessage()); - } - } - - // ===== 工具 ===== - private String nn(String s) { return s != null ? s : ""; } - private boolean isExpired(OrderRecord r) { return System.currentTimeMillis() - r.timestamp > RETENTION_30D; } - private String fmtDate(long ts) { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ts)); } - private int slotIndex(int raw) { for (int i = 0; i < SLOTS.length; i++) if (SLOTS[i] == raw) return i; return -1; } - private String brief(String s) { if (s == null || s.isEmpty()) return "(空)"; String c = sc(s); return c.length() > 30 ? c.substring(0, 30) + "..." : c; } - private String sectionName(String s) { switch(s){case "header":return "头部";case "itemLine":return "商品行";case "discountLine":return "优惠行";case "packLine":return "打包行";case "summary":return "汇总";case "footer":return "页脚";default:return s;} } - private String sc(String s) { - if (s == null) return ""; - return s.replaceAll("§[0-9a-fk-orA-FK-OR]", ""); - } - private String getSection(String s) { switch(s){case "header":return tplHeader;case "itemLine":return tplItemLine;case "discountLine":return tplDiscountLine;case "packLine":return tplPackLine;case "summary":return tplSummary;case "footer":return tplFooter;default:return "";} } - private void setSection(String s, String v) { switch(s){case "header":tplHeader=v;break;case "itemLine":tplItemLine=v;break;case "discountLine":tplDiscountLine=v;break;case "packLine":tplPackLine=v;break;case "summary":tplSummary=v;break;case "footer":tplFooter=v;break;} } - private String getDefaultVal(String s) { switch(s){case "header":return "§6§l{商店名}§r\n购物小票\n订单#{订单号} {订单时间}\n玩家: {玩家}";case "itemLine":return "§e{品名}§r x{数量} {原价}→{执行价} 小计{小计}枚";case "discountLine":return "优惠: §a-{优惠额}枚§r ({优惠类型}{优惠券码})";case "packLine":return "打包: §c+{打包费}枚§r ({打包颜色})";case "summary":return "原价{原价合计}枚 → 实付§6{实付}枚§r 余额{余额}枚";case "footer":return "感谢惠顾,欢迎再来!";default:return "";} } - private String getTokenBySlot(int raw) { switch(raw){case 10:return "{品名}";case 11:return "{原价}";case 12:return "{执行价}";case 13:return "{数量}";case 14:return "{小计}";case 15:return "{商店名}";case 16:return "{玩家}";case 19:return "{订单号}";case 20:return "{订单时间}";case 21:return "{打印时间}";case 22:return "{日期}";case 23:return "{时间}";case 24:return "{原价合计}";case 25:return "{实付}";case 28:return "{优惠额}";case 29:return "{优惠类型}";case 30:return "{优惠券码}";case 31:return "{打包费}";case 32:return "{打包颜色}";case 33:return "{余额}";case 37:return "═══";case 38:return "───";case 39:return "═════════════";case 40:return "\\n";default:return null;} } - private ItemStack mkItem(Material mat, String name, String... lore) { ItemStack it = new ItemStack(mat); ItemMeta im = it.getItemMeta(); if (im != null) { im.setDisplayName(name); if (lore.length > 0) im.setLore(Arrays.asList(lore)); it.setItemMeta(im); } return it; } - private Map loadMap(File f) { Map m = new LinkedHashMap<>(); try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) continue; int eq = line.indexOf('='); if (eq > 0) m.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); } br.close(); } catch (Exception ignored) {} return m; } - private void writeLines(File f, List lines) { try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); for (String l : lines) pw.println(l); pw.flush(); pw.close(); } catch (Exception ignored) {} } - private void sendRandomPreview(Player p) { String[] n = {"钻石剑","钻石镐","铁剑","金苹果"}; String[] m = {"DIAMOND_SWORD","DIAMOND_PICKAXE","IRON_SWORD","GOLDEN_APPLE"}; Random r = new Random(); List items = new ArrayList<>(); for (int i = 0; i < 2 + r.nextInt(3); i++) { int idx = r.nextInt(n.length); items.add(new OrderItem(n[idx], m[idx], 100 + r.nextInt(5000), 100 + r.nextInt(5000), 1 + r.nextInt(10))); } OrderRecord o = new OrderRecord(); o.orderId = System.currentTimeMillis(); o.uuid = p.getUniqueId(); o.player = p.getName(); o.items = items; o.totalOriginal = 0; for (OrderItem it : items) o.totalOriginal += it.originalSubtotal(); o.totalPaid = o.totalOriginal; o.timestamp = o.orderId; o.status = 0; p.sendMessage("§6§l===== 小票预览(随机) ====="); sendReceiptChat(p, o); p.sendMessage("§6§l===== 预览结束 ====="); } -} diff --git a/backup/src/main/java/Sdf1_login/PVPManager.java b/backup/src/main/java/Sdf1_login/PVPManager.java deleted file mode 100644 index 326c0fb..0000000 --- a/backup/src/main/java/Sdf1_login/PVPManager.java +++ /dev/null @@ -1,1380 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.NamespacedKey; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.scoreboard.*; - - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class PVPManager implements Listener { - - private final Main plugin; - private Connection pvpDb; - - private final Map - regions = new ConcurrentHashMap<>(); - private final Map - inRegion = new ConcurrentHashMap<>(); - private final Map - sessionKills = - new ConcurrentHashMap<>(); - private final Map - lastKillTime = - new ConcurrentHashMap<>(); - private final Map - selectState = - new ConcurrentHashMap<>(); - private final Map - killSessions = - new ConcurrentHashMap<>(); - private final Set tripleAnnounced = - ConcurrentHashMap.newKeySet(); - private final Map lastScoreboardRefresh = new ConcurrentHashMap<>(); - - - // 边框显示 - private final Set - permanentBorders = - ConcurrentHashMap.newKeySet(); - private final Set - tempBorders = - ConcurrentHashMap.newKeySet(); - - - public static class KillSession { - public int count = 0; - public long firstKill = 0; - public long lastKill = 0; - public boolean tripleAnnounced = - false; - public String region = ""; - } - - public static class PVPRegion { - public String name; - public String world; - public int x1, y1, z1; - public int x2, y2, z2; - public int minX, minY, minZ; - public int maxX, maxY, maxZ; - - public void calcBounds() { - minX = Math.min(x1, x2); - minY = Math.min(y1, y2); - minZ = Math.min(z1, z2); - maxX = Math.max(x1, x2); - maxY = Math.max(y1, y2); - maxZ = Math.max(z1, z2); - } - - public boolean contains( - Location loc) { - if (loc == null) return false; - if (!loc.getWorld().getName() - .equals(world)) - return false; - int x = loc.getBlockX(); - int y = loc.getBlockY(); - int z = loc.getBlockZ(); - return x >= minX && x <= maxX - && y >= minY && y <= maxY - && z >= minZ && z <= maxZ; - } - } - - public PVPManager(Main plugin) { - this.plugin = plugin; - initDB(); - loadRegions(); - } - - // ===== 独立数据库 ===== - - private void initDB() { - try { - Class.forName( - "org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), - "pvp.db"); - pvpDb = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = - pvpDb.createStatement(); - st.execute( - "PRAGMA journal_mode=WAL"); - st.execute( - "CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "name TEXT NOT NULL " - + "UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER " - + "DEFAULT 0)"); - st.execute( - "CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT " - + "NOT NULL," - + "region_name TEXT " - + "NOT NULL," - + "kills INTEGER " - + "DEFAULT 0," - + "deaths INTEGER " - + "DEFAULT 0," - + "PRIMARY KEY" - + "(player_name, " - + "region_name))"); - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[PVP] DB init failed: " - + e.getMessage(), e); - } - } - - // ===== DB 操作 ===== - - private void saveRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions" - + " (name, world," - + " x1,y1,z1," - + " x2,y2,z2," - + " create_time)" - + " VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private Map - getRegion(String name) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - private List> - getAllRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - pvpDb.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - private void deleteRegion(String name) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private int getKills(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=?" - + " AND " - + "region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = - ps.executeQuery(); - int v = 0; - if (rs.next()) - v = rs.getInt("kills"); - rs.close(); - ps.close(); - return v; - } catch (SQLException e) { - return 0; - } - } - - private int getDeaths(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=?" - + " AND " - + "region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = - ps.executeQuery(); - int v = 0; - if (rs.next()) - v = rs.getInt("deaths"); - rs.close(); - ps.close(); - return v; - } catch (SQLException e) { - return 0; - } - } - - private void addKill(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT INTO pvp_kills" - + " (player_name," - + " region_name," - + " kills,deaths)" - + " VALUES " - + "(?,?,1,0) " - + "ON CONFLICT" - + "(player_name," - + " region_name)" - + " DO UPDATE " - + "SET kills=" - + "kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private void addDeath(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT INTO pvp_kills" - + " (player_name," - + " region_name," - + " kills,deaths)" - + " VALUES " - + "(?,?,0,1) " - + "ON CONFLICT" - + "(player_name," - + " region_name)" - + " DO UPDATE " - + "SET deaths=" - + "deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private List> - getTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT player_name," - + " kills,deaths" - + " FROM " - + "pvp_kills " - + "WHERE " - + "region_name=?" - + " AND kills>0" - + " ORDER BY kills" - + " DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = - ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString( - "player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Set getRegionNames() { - return regions.keySet(); - } - - // ===== 加载 ===== - - private void loadRegions() { - regions.clear(); - List> list = - getAllRegions(); - for (Map row : list) { - PVPRegion r = new PVPRegion(); - r.name = (String) row - .get("name"); - r.world = (String) row - .get("world"); - r.x1 = ((Number) row - .get("x1")).intValue(); - r.y1 = ((Number) row - .get("y1")).intValue(); - r.z1 = ((Number) row - .get("z1")).intValue(); - r.x2 = ((Number) row - .get("x2")).intValue(); - r.y2 = ((Number) row - .get("y2")).intValue(); - r.z2 = ((Number) row - .get("z2")).intValue(); - r.calcBounds(); - regions.put(r.name, r); - } - } - - // ===== 工具 ===== - - public ItemStack createTool() { - ItemStack tool = new ItemStack( - Material.BLAZE_ROD); - ItemMeta m = tool.getItemMeta(); - m.setDisplayName( - "§6§l[PVP] 区域选择工具"); - m.setLore(Arrays.asList( - "§7右键:设置第一个角", - "§7左键:设置第二个角")); - m.getPersistentDataContainer() - .set(new NamespacedKey( - plugin, "pvp_tool"), - PersistentDataType.STRING, - "tool"); - tool.setItemMeta(m); - return tool; - } - - private boolean isPVPTool( - ItemStack item) { - if (item == null) return false; - ItemMeta m = item.getItemMeta(); - if (m == null) return false; - return m.getPersistentDataContainer() - .has(new NamespacedKey( - plugin, "pvp_tool"), - PersistentDataType.STRING); - } - - public void giveTool(Player p) { - boolean has = false; - for (ItemStack it : p.getInventory() - .getContents()) { - if (isPVPTool(it)) { - has = true; - break; - } - } - if (!has) { - p.getInventory() - .addItem(createTool()); - } - } - - // ===== 边框粒子 ===== - - private void drawBorder(Player target, - PVPRegion r) { - if (r == null) return; - World w = Bukkit.getWorld(r.world); - if (w == null) return; - int step = 2; - drawLine(target, w, - r.minX, r.minY, r.minZ, - r.maxX, r.minY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.minZ, - r.maxX, r.minY, r.maxZ, step); - drawLine(target, w, - r.maxX, r.minY, r.maxZ, - r.minX, r.minY, r.maxZ, step); - drawLine(target, w, - r.minX, r.minY, r.maxZ, - r.minX, r.minY, r.minZ, step); - drawLine(target, w, - r.minX, r.maxY, r.minZ, - r.maxX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.maxY, r.minZ, - r.maxX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.maxX, r.maxY, r.maxZ, - r.minX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.minX, r.maxY, r.maxZ, - r.minX, r.maxY, r.minZ, step); - drawLine(target, w, - r.minX, r.minY, r.minZ, - r.minX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.minZ, - r.maxX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.maxZ, - r.maxX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.minX, r.minY, r.maxZ, - r.minX, r.maxY, r.maxZ, step); - } - - private void drawLine(Player target, - World w, - int x1, int y1, int z1, - int x2, int y2, int z2, - int step) { - double dx = x2 - x1; - double dy = y2 - y1; - double dz = z2 - z1; - double dist = Math.sqrt( - dx * dx + dy * dy + dz * dz); - int steps = (int) (dist / step); - if (steps < 1) steps = 1; - for (int i = 0; i <= steps; i++) { - double t = (double) i / steps; - Location loc = new Location(w, - x1 + dx * t, - y1 + dy * t + 0.1, - z1 + dz * t); - w.spawnParticle( - Particle.FLAME, loc, 1, - 0.0, 0.0, 0.0, 0.0); - } - } - - // ===== 每秒 tick ===== - - public void tickRegions() { - long now = - System.currentTimeMillis(); - - // 玩家区域检测 - for (Player p : - Bukkit.getOnlinePlayers()) { - String pn = p.getName(); - Location loc = p.getLocation(); - String inside = null; - for (PVPRegion r : - regions.values()) { - if (r.contains(loc)) { - inside = r.name; - break; - } - } - String was = inRegion.get(pn); - - if (inside != null && !inside.equals(was)) { - inRegion.put(pn, inside); - if (was == null) { - String reg = inside; - final Player pp = p; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> - updateScoreboard( - pp, reg), - 1L); - } - - } else if (inside == null - && was != null) { - inRegion.remove(pn); - // 不删 sessionKills,等倒计时结束再清 - removeScoreboard(p); - final Player pp = p; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> plugin - .giveMenuSnowball(pp), - 5L); - } - - } - - // 5分钟超时重置连杀session - for (String pname : - new ArrayList<>( - killSessions - .keySet())) { - KillSession ks = - killSessions.get(pname); - if (ks == null) continue; - if (now - ks.lastKill - > 5 * 60 * 1000) { - killSessions.remove(pname); - } - } - - // 5分钟无击杀 → 计数榜切总榜 - // 5分钟无击杀 → 归档并清零 - for (String rName : - new ArrayList<>( - lastKillTime - .keySet())) { - long last = lastKillTime - .getOrDefault(rName, 0L); - if (now - last > 5 * 60 * 1000) { - // 清零该区域所有sessionKills - sessionKills.keySet() - .removeIf(k -> k - .startsWith( - rName + ":")); - // 切换到总榜 - refreshScoreboard(rName); - lastKillTime.remove(rName); - lastScoreboardRefresh.remove(rName); - - } - } - - - // 边框渲染 - for (Player p : - Bukkit.getOnlinePlayers()) { - String pr = - inRegion.get(p.getName()); - if (pr == null) continue; - PVPRegion r = regions.get(pr); - if (r == null) continue; - if (permanentBorders - .contains(pr)) { - drawBorder(p, r); - } - if (tempBorders - .contains(pr)) { - drawBorder(p, r); - } - // 每秒刷新活跃区域的计分板倒计时 - for (String rName : new ArrayList<>(lastKillTime.keySet())) { - long last = lastScoreboardRefresh.getOrDefault(rName, 0L); - if (now - last >= 1000) { - lastScoreboardRefresh.put(rName, now); - for (Player tp : Bukkit.getOnlinePlayers()) { - String inR = inRegion.get(tp.getName()); - if (rName.equals(inR)) { - showCounter(tp, rName); - } - } - } - } - - - - } - } - - private void broadcastWithSound(String msg, Sound sound) { - Bukkit.broadcastMessage(msg); - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), sound, 1.0f, 1.0f); - } - } - - - private void initSession(String player, - String region) { - killSessions.remove(player); - tripleAnnounced.remove( - region + ":" + player); - KillSession s = new KillSession(); - s.region = region; - s.firstKill = - System.currentTimeMillis(); - s.lastKill = - System.currentTimeMillis(); - killSessions.put(player, s); - } - - // ===== 击杀事件 ===== - - @EventHandler - public void onDeath( - EntityDeathEvent event) { - if (!(event.getEntity() - instanceof Player)) return; - Player victim = - (Player) event.getEntity(); - Player killer = - victim.getKiller(); - if (killer == null) return; - if (killer.equals(victim)) return; - - String region = - inRegion.get( - victim.getName()); - if (region == null) return; - - String vName = victim.getName(); - String kName = killer.getName(); - - // 总榜 - addKill(kName, region); - addDeath(vName, region); - - // 计数榜 - String kKey = - region + ":" + kName; - sessionKills.merge(kKey, 1, - Integer::sum); - lastKillTime.put(region, - System.currentTimeMillis()); - - // 终结检测 - // 终结检测 - KillSession victimSession = - killSessions.get(vName); - if (victimSession != null - && victimSession.count >= 3) { - String endMsg = - "§b§l" + kName - + " §e终结了 " - + vName + " §e的 " - + victimSession.count - + "连杀!§a恭喜终结者 " - + kName - + "§e!§7快来挑战!"; - killSessions.remove(vName); - broadcastWithSound(endMsg, Sound.ENTITY_ENDER_DRAGON_GROWL); - - killSessions.remove(vName); - } - - - // 击杀者连杀 - KillSession ks = - killSessions.get(kName); - if (ks == null - || !ks.region.equals(region) - || System.currentTimeMillis() - - ks.lastKill > 5 * 60 * 1000) { - tripleAnnounced.remove( - region + ":" + kName); - KillSession ns = - new KillSession(); - ns.region = region; - ns.count = 1; - ns.firstKill = - System.currentTimeMillis(); - ns.lastKill = - System.currentTimeMillis(); - killSessions.put(kName, ns); - } else { - ks.count++; - ks.lastKill = - System.currentTimeMillis(); - } - - // 多杀通报 - KillSession killerSession = - killSessions.get(kName); - if (killerSession != null) { - plugin.getLogger().info( - "[PVP] checkMultiKill: " - + kName + " count=" - + killerSession.count - + " region=" + region); - checkMultiKill(killer, - region, killerSession); - } - - // 刷新计分板 - for (Player p : - Bukkit.getOnlinePlayers()) { - if (region.equals( - inRegion.get( - p.getName()))) { - updateScoreboard(p, region); - } - } - - victim.sendMessage("§c你在「" - + region + "」被 " - + kName + " 击杀"); - killer.sendMessage("§a你在「" - + region + "」击杀了 " - + vName); - } - - private void checkMultiKill( - Player killer, String region, - KillSession session) { - int count = session.count; - String kName = killer.getName(); - String key = region + ":" + kName; - String msg = null; - - if (count >= 5) { - msg = "§c§l" + kName - + " §e在「" + region - + "」达成 " + count - + "连杀!§7§l快来挑战!"; - } else if (count == 3) { - if (tripleAnnounced.add(key)) { - msg = "§6§l三杀!" - + kName - + " 势不可挡!" - + "§7快来挑战!"; - } - } - - if (msg != null) { - if (count >= 5) { - broadcastWithSound(msg, Sound.ENTITY_ENDER_DRAGON_GROWL); - } else { - broadcastWithSound(msg, Sound.BLOCK_END_PORTAL_SPAWN); - } - } - - } - - - - // ===== 计分板(英文ID) ===== - - private void ensureScoreboard( - Player p, String region) { - // 活跃击杀:每秒强制刷新(更新倒计时) - if (lastKillTime.containsKey(region)) { - showCounter(p, region); - return; - } - // 无活跃击杀:只在没有scoreboard时创建总榜 - Scoreboard sb = p.getScoreboard(); - if (sb == null - || sb.getObjective( - "sdf1pvp") == null) { - showTotal(p, region); - } - } - - - private void updateScoreboard( - Player p, String region) { - if (lastKillTime - .containsKey(region)) { - showCounter(p, region); - } else { - showTotal(p, region); - } - } - - private void showTotal(Player p, - String region) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - Scoreboard sb = - mgr.getNewScoreboard(); - Objective obj = - sb.registerNewObjective( - "sdf1pvp", - Criteria.DUMMY, - "§6§l击杀总榜 [" - + region + "]"); - obj.setDisplaySlot( - DisplaySlot.SIDEBAR); - - List> top = - getTop(region, 10); - if (top.isEmpty()) { - obj.getScore("§7暂无记录") - .setScore(0); - } else { - int rank = 10; - for (Map row : - top) { - String name = (String) row - .get("player_name"); - int kills = ((Number) row - .get("kills")) - .intValue(); - int deaths = ((Number) row - .get("deaths")) - .intValue(); - String line = - "§e#" + (11 - rank) - + " §f" + name - + " §a" + kills - + "§7/§c" + deaths; - obj.getScore(line) - .setScore(rank); - rank--; - } - } - p.setScoreboard(sb); - } - private void showCounter(Player p, - String region) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - Scoreboard sb = mgr.getNewScoreboard(); - Objective obj = - sb.registerNewObjective( - "sdf1pvp", - Criteria.DUMMY, - "§c§l实时战况 [" - + region + "]"); - obj.setDisplaySlot( - DisplaySlot.SIDEBAR); - - List entries = - new ArrayList<>(); - for (Map.Entry e : - sessionKills.entrySet()) { - if (!e.getKey().startsWith( - region + ":")) - continue; - if (e.getValue() <= 0) continue; - String pName = e.getKey() - .substring( - region.length() + 1); - entries.add(new String[]{ - pName, - String.valueOf( - e.getValue())}); - } - - if (entries.isEmpty()) { - obj.getScore("§7等待击杀...") - .setScore(1); - } else { - entries.sort((a, b) -> - Integer.compare( - Integer.parseInt(b[1]), - Integer.parseInt(a[1]))); - int rank = 10; - for (String[] entry : entries) { - obj.getScore( - "§e" + entry[0] - + " §a" + entry[1]) - .setScore(rank); - rank--; - if (rank < 1) break; - } - } - - Long last = - lastKillTime.get(region); - if (last != null) { - long elapsed = - System.currentTimeMillis() - - last; - long remaining = - Math.max(0, - 5 * 60 * 1000 - elapsed); - long secs = remaining / 1000; - obj.getScore( - "§7冷却: " + secs + "s") - .setScore(-1); - } - - // 关键:强制赋值新scoreboard - p.setScoreboard(sb); - } - - private void removeScoreboard( - Player p) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - p.setScoreboard( - mgr.getNewScoreboard()); - } - - private void refreshScoreboard( - String regionName) { - for (Player p : - Bukkit.getOnlinePlayers()) { - String pr = - inRegion.get(p.getName()); - if (regionName.equals(pr)) { - showTotal(p, regionName); - } - } - } - - // ===== 工具选择事件 ===== - - @EventHandler - public void onInteract( - PlayerInteractEvent event) { - Player p = event.getPlayer(); - ItemStack hand = - p.getInventory() - .getItemInMainHand(); - if (!isPVPTool(hand)) return; - - if (!event.getAction() - .isRightClick() - && !event.getAction() - .isLeftClick()) return; - - Location loc = p.getLocation(); - String[] state = - selectState.get(p.getName()); - if (state == null) { - state = new String[]{ - loc.getWorld() - .getName(), - "", ""}; - selectState.put( - p.getName(), state); - } - - String coord = - loc.getBlockX() + "," - + loc.getBlockY() + "," - + loc.getBlockZ(); - - if (event.getAction() - .isRightClick()) { - state[0] = loc.getWorld() - .getName(); - state[1] = coord; - p.sendMessage( - "§a第一个角: " + coord); - } else { - state[2] = coord; - p.sendMessage( - "§a第二个角: " + coord); - } - event.setCancelled(true); - } - - // ===== 退出清理 ===== - - @EventHandler - public void onQuit( - PlayerQuitEvent event) { - String name = - event.getPlayer().getName(); - inRegion.remove(name); - selectState.remove(name); - } - - // ===== 指令 ===== - - public boolean onCommand(Player p, - String[] args) { - if (args.length == 0) { - return showStats(p, - p.getName()); - } - switch (args[0].toLowerCase()) { - case "create": - return handleCreate( - p, args); - case "stats": - if (args.length >= 2) - return showStats(p, - args[1]); - return showStats(p, - p.getName()); - case "list": - return handleList(p); - case "delete": - if (args.length >= 2) - return handleDelete(p, - args[1]); - p.sendMessage( - "§c用法: " - + "/pvp delete <名字>"); - return true; - case "tool": - giveTool(p); - p.sendMessage( - "§a已发放" - + "PVP区域选择工具"); - return true; - case "on": - return handleBorderOn( - p, args); - case "off": - return handleBorderOff( - p, args); - case "tempban": - return handleTempBan( - p, args); - default: - return showStats(p, - p.getName()); - } - } - - private boolean handleBorderOn( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp on <区域名>"); - return true; - } - if (!regions - .containsKey(name)) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - permanentBorders.add(name); - p.sendMessage("§a已开启「" - + name + "」永久边框显示"); - return true; - } - - private boolean handleBorderOff( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp off <区域名>"); - return true; - } - permanentBorders.remove(name); - tempBorders.remove(name); - p.sendMessage("§a已关闭「" - + name + "」边框显示"); - return true; - } - - private boolean handleTempBan( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp tempban <区域名>"); - return true; - } - if (!regions - .containsKey(name)) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - tempBorders.add(name); - p.sendMessage("§a「" + name - + "」临时边框显示15秒"); - final String rName = name; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> - tempBorders - .remove(rName), - 300L); - return true; - } - - private boolean handleCreate( - Player p, String[] args) { - if (args.length >= 8) { - try { - int x1 = Integer.parseInt( - args[1]); - int y1 = Integer.parseInt( - args[2]); - int z1 = Integer.parseInt( - args[3]); - int x2 = Integer.parseInt( - args[4]); - int y2 = Integer.parseInt( - args[5]); - int z2 = Integer.parseInt( - args[6]); - String rName = args[7]; - return createRegion(p, - rName, - p.getWorld() - .getName(), - x1, y1, z1, - x2, y2, z2); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c坐标格式错误"); - return true; - } - } - - if (args.length >= 2) { - String[] state = - selectState - .get(p.getName()); - if (state == null - || state[1].isEmpty() - || state[2].isEmpty()) { - p.sendMessage( - "§c请先用工具选择两个角"); - return true; - } - String[] p1 = - state[1].split(","); - String[] p2 = - state[2].split(","); - return createRegion(p, - args[1], state[0], - Integer.parseInt(p1[0]), - Integer.parseInt(p1[1]), - Integer.parseInt(p1[2]), - Integer.parseInt(p2[0]), - Integer.parseInt(p2[1]), - Integer.parseInt(p2[2])); - } - - p.sendMessage( - "§c用法: /pvp create " - + " " - + " <名字>"); - p.sendMessage( - "§c或: /pvp create " - + "<名字> (先用工具选择)"); - return true; - } - - private boolean createRegion( - Player p, String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - if (getRegion(name) != null) { - p.sendMessage( - "§c区域已存在: " + name); - return true; - } - PVPRegion r = new PVPRegion(); - r.name = name; - r.world = world; - r.x1 = x1; - r.y1 = y1; - r.z1 = z1; - r.x2 = x2; - r.y2 = y2; - r.z2 = z2; - r.calcBounds(); - saveRegion(name, world, - x1, y1, z1, - x2, y2, z2); - regions.put(name, r); - p.sendMessage( - "§a§lPVP区域创建成功!"); - p.sendMessage( - "§7名称: §f" + name); - p.sendMessage("§7范围: §f" - + r.minX + "," + r.minY - + "," + r.minZ + " → " - + r.maxX + "," + r.maxY - + "," + r.maxZ); - selectState.remove(p.getName()); - return true; - } - - private boolean handleList( - Player p) { - List> all = - getAllRegions(); - if (all.isEmpty()) { - p.sendMessage( - "§7暂无PVP区域"); - return true; - } - p.sendMessage( - "§6§lPVP区域列表:"); - for (Map row : - all) { - p.sendMessage("§e- §f" - + row.get("name") - + " §7(" + row.get("world") - + ")"); - } - return true; - } - - private boolean handleDelete( - Player p, String name) { - if (getRegion(name) == null) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - deleteRegion(name); - regions.remove(name); - permanentBorders.remove(name); - tempBorders.remove(name); - sessionKills.keySet() - .removeIf(k -> k - .startsWith( - name + ":")); - lastKillTime.remove(name); - lastScoreboardRefresh.remove(name); - p.sendMessage( - "§a已删除: " + name); - return true; - } - - private boolean showStats(Player p, - String target) { - String region = - inRegion.get(target); - if (region == null) { - p.sendMessage("§6§l" + target - + " 的PVP战绩:"); - List> all = - getAllRegions(); - boolean has = false; - for (Map row : - all) { - String rn = (String) row - .get("name"); - int kills = - getKills(target, rn); - int deaths = - getDeaths(target, rn); - if (kills > 0 - || deaths > 0) { - p.sendMessage( - "§e- §f" + rn - + " §a击杀:" + kills - + " §c死亡:" + deaths); - has = true; - } - } - if (!has) - p.sendMessage( - "§7暂无记录"); - } else { - int kills = - getKills(target, region); - int deaths = - getDeaths(target, region); - double kd = deaths > 0 - ? (double) kills / deaths - : kills; - p.sendMessage("§6§l" + target - + " @ " + region); - p.sendMessage( - "§a击杀: " + kills - + " §c死亡: " + deaths - + " §eKD: " - + String.format( - "%.2f", kd)); - } - return true; - } -} diff --git a/backup/src/main/java/Sdf1_login/PasswordUtils.java b/backup/src/main/java/Sdf1_login/PasswordUtils.java deleted file mode 100644 index 4029ed4..0000000 --- a/backup/src/main/java/Sdf1_login/PasswordUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -package Sdf1_login; - -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; - -public class PasswordUtils { - - private static final SecureRandom RANDOM = - new SecureRandom(); - - public static String generateSalt() { - byte[] salt = new byte[16]; - RANDOM.nextBytes(salt); - return Base64.getEncoder().encodeToString(salt); - } - - public static String hash(String password, String salt) { - try { - MessageDigest md = - MessageDigest.getInstance("SHA-256"); - md.update(Base64.getDecoder().decode(salt)); - byte[] hash = - md.digest(password.getBytes("UTF-8")); - return Base64.getEncoder().encodeToString(hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static boolean validate(String password) { - if (password == null) return false; - if (password.length() < 6 || password.length() > 20) - return false; - boolean hasUpper = false; - boolean hasLower = false; - boolean hasDigit = false; - for (char c : password.toCharArray()) { - if (Character.isUpperCase(c)) hasUpper = true; - if (Character.isLowerCase(c)) hasLower = true; - if (Character.isDigit(c)) hasDigit = true; - } - return hasUpper && hasLower && hasDigit; - } - - public static String generateTempPassword() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(8); - for (int i = 0; i < 8; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateInviteCode() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - StringBuilder sb = new StringBuilder(6); - for (int i = 0; i < 6; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateVerifyCode() { - int code = 100000 + RANDOM.nextInt(900000); - return String.valueOf(code); - } -} diff --git a/backup/src/main/java/Sdf1_login/PlayerStorage.java b/backup/src/main/java/Sdf1_login/PlayerStorage.java deleted file mode 100644 index f93caa3..0000000 --- a/backup/src/main/java/Sdf1_login/PlayerStorage.java +++ /dev/null @@ -1,130 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.logging.Logger; - -public class PlayerStorage { - private Connection conn; - private final Logger logger; - - public PlayerStorage(File dataFolder, - Logger logger) { - this.logger = logger; - File dbFile = new File( - dataFolder, - "quest_data.db"); - try { - Class.forName( - "org.sqlite.JDBC"); - conn = DriverManager - .getConnection( - "jdbc:sqlite:" - + dbFile - .getAbsolutePath()); - conn.setAutoCommit(true); - Statement s = - conn.createStatement(); - s.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_kv (" - + "player TEXT NOT NULL," - + "key TEXT NOT NULL," - + "value TEXT," - + "PRIMARY KEY" - + "(player, key))"); - s.close(); - logger.info( - "[QuestStorage] 初始化: " - + dbFile.getAbsolutePath()); - } catch (Exception e) { - logger.severe( - "[QuestStorage] 失败: " - + e.getMessage()); - } - } - - public Object getCondition( - String player, String key) { - if (conn == null) return null; - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value " - + "FROM quest_kv " - + "WHERE player=? " - + "AND key=?"); - ps.setString(1, - player.toLowerCase()); - ps.setString(2, key); - ResultSet rs = - ps.executeQuery(); - String val = rs.next() - ? rs.getString("value") - : null; - rs.close(); - ps.close(); - return parseValue(val); - } catch (SQLException e) { - return null; - } - } - - public void setCondition( - String player, String key) { - setConditionRaw( - player, key, "true"); - } - public void debugPlayer(String player) { - - } - public void setConditionRaw( - String player, String key, - String value) { - if (conn == null) return; - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO quest_kv" - + "(player,key,value)" - + " VALUES(?,?,?)"); - ps.setString(1, - player.toLowerCase()); - ps.setString(2, key); - ps.setString(3, value); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - logger.warning( - "[QuestStorage] 写入失败: " - + key + "=" + value - + " | " + e.getMessage()); - } - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) - conn.close(); - } catch (Exception e) {} - } - - private Object parseValue(String v) { - if (v == null) return null; - try { - return Long.parseLong(v); - } catch (Exception e1) { - try { - return Double.parseDouble(v); - } catch (Exception e2) { - if ("true".equalsIgnoreCase(v)) - return true; - if ("false".equalsIgnoreCase(v)) - return false; - return v; - } - } - } -} diff --git a/backup/src/main/java/Sdf1_login/PointsManager.java b/backup/src/main/java/Sdf1_login/PointsManager.java deleted file mode 100644 index 77fc195..0000000 --- a/backup/src/main/java/Sdf1_login/PointsManager.java +++ /dev/null @@ -1,374 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - - -import java.lang.reflect.Method; -import java.util.*; - -public class PointsManager { - - private final Main plugin; - private Object cyInstance = null; - private Method cyActivationMethod = null; - private Method cyGetShopItemsMethod = null; - - private final List> cyItems = - new ArrayList<>(); - - public PointsManager(Main plugin) { - this.plugin = plugin; - discoverCY(); - } - - private void discoverCY() { - try { - org.bukkit.plugin.Plugin p = Bukkit - .getPluginManager() - .getPlugin("CY_beibao"); - if (p != null && p.isEnabled()) { - cyInstance = p; - cyGetShopItemsMethod = p.getClass() - .getMethod("onSdf1GetShopItems"); - cyActivationMethod = p.getClass() - .getMethod("onSdf1Activation", - String.class, - int.class, int.class); - plugin.getLogger().info( - "[Sdf1_login] CY_beibao 联控已连接"); - } - } catch (Exception ignored) { - } - } - - private void refreshCYItems() { - cyItems.clear(); - if (cyInstance == null - || cyGetShopItemsMethod == null) return; - try { - Object result = - cyGetShopItemsMethod.invoke(cyInstance); - if (result == null) return; - String data = result.toString(); - if (data.isEmpty()) return; - String[] items = data.split(";"); - for (String itemStr : items) { - String[] p = itemStr.split("\\|"); - if (p.length < 8) continue; - Map item = - new LinkedHashMap<>(); - item.put("id", p[0]); - item.put("name", p[1]); - item.put("days", - Integer.parseInt(p[2])); - item.put("slots", - Integer.parseInt(p[3])); - item.put("price", - Integer.parseInt(p[4])); - item.put("stock", - Integer.parseInt(p[5])); - item.put("icon", p[6]); - item.put("lifetime", - "1".equals(p[7])); - cyItems.add(item); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 拉取CY商品失败: " - + e.getMessage()); - } - } - - private void activateCY(String name, - int slots, int days) { - try { - if (cyInstance == null - || cyActivationMethod == null) return; - cyActivationMethod.invoke( - cyInstance, name, slots, days); - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] CY激活失败: " - + e.getMessage()); - } - } - - public int getPoints(String name) { - return ((Number) plugin.getDb() - .getField(name, "points")).intValue(); - } - - public Inventory createShopGUI(Player p) { - int points = getPoints(p.getName()); - Inventory inv = Bukkit.createInventory( - null, 54, - "§d§l积分商城 | 当前积分: " + points); - fillBg(inv); - inv.setItem(10, mkItem(Material.EMERALD, - "§a游戏币充值", - "§7消耗积分兑换游戏币", - "§7100积分 = $1000", - "", - "§e点击兑换")); - inv.setItem(12, mkItem(Material.ENDER_CHEST, - "§b背包空间", - "§7消耗积分兑换背包格子", - "§750积分 = 9格永久空间", - "", - "§e点击兑换")); - inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE, - "§e经验瓶", - "§7消耗积分兑换经验", - "§720积分 = 10级经验", - "", - "§e点击兑换")); - if (cyInstance != null) { - inv.setItem(31, mkItem(Material.CHEST, - "§6背包商城", - "§7来自CY_beibao的商品", - "", - "§e点击查看")); - } - inv.setItem(49, mkItem(Material.ARROW, "§7返回")); - return inv; - } - - public Inventory createCYShopGUI(Player p) { - refreshCYItems(); - int points = getPoints(p.getName()); - int size = Math.max(54, - ((cyItems.size() / 9) + 1) * 9); - if (size > 54) size = 54; - Inventory inv = Bukkit.createInventory( - null, size, - "§d§lCY背包商城 | 当前积分: " + points); - fillBg(inv); - for (int i = 0; i < cyItems.size() - && i < 45; i++) { - Map item = cyItems.get(i); - String name = (String) item.get("name"); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - List lore = new ArrayList<>(); - lore.add("§7价格: §e" + price + " 积分"); - if (lifetime) { - lore.add("§7格子: §e终身 +" + slots); - } else { - lore.add("§7格子: §e+" + slots); - lore.add("§7时间: §e" + days + "天"); - } - if (stock == 0) { - lore.add("§7库存: §a无限"); - } else if (stock == -1) { - lore.add("§7库存: §c已下架"); - } else { - lore.add("§7库存: §e" + stock); - } - lore.add(""); - if (points >= price && stock != -1) { - lore.add("§e点击购买"); - } else if (stock == -1) { - lore.add("§c已下架"); - } else { - lore.add("§c积分不足"); - } - inv.setItem(i, mkItem(Material.CHEST, - "§a" + name, - lore.toArray(new String[0]))); - } - inv.setItem(size - 1, - mkItem(Material.ARROW, "§7返回")); - return inv; - } - - public void addItem(String name, - int price, String cmd2) { - // 追加到商品配置文件 - File file = new File( - plugin.getDataFolder(), - "ShopItems.txt"); - try { - java.io.FileWriter fw = - new java.io.FileWriter(file, true); - fw.write(name + ":" + price - + ":" + cmd2 + "\n"); - fw.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void removeItem(int index) { - File file = new File( - plugin.getDataFolder(), - "ShopItems.txt"); - try { - java.util.List lines = - new java.util.ArrayList<>(); - java.io.BufferedReader br = - new java.io.BufferedReader( - new java.io.FileReader( - file)); - String line; - int i = 0; - while ((line = br.readLine()) != null) { - if (line.trim().isEmpty() - || line.startsWith("#")) { - lines.add(line); - continue; - } - if (i != index) { - lines.add(line); - } - i++; - } - br.close(); - java.io.PrintWriter pw = - new java.io.PrintWriter(file); - for (String l : lines) pw.println(l); - pw.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - public void handleClick(Player p, int slot) { - int points = getPoints(p.getName()); - switch (slot) { - case 10: - if (points >= 100) { - plugin.getDb().deductPoints( - p.getName(), 100); - if (plugin.getEconomy() != null) { - plugin.getEconomy() - .depositPlayer(p, 1000); - } - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "100")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 12: - if (points >= 50) { - plugin.getDb().deductPoints( - p.getName(), 50); - activateCY(p.getName(), 9, 0); - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "50")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 14: - if (points >= 20) { - plugin.getDb().deductPoints( - p.getName(), 20); - p.setLevel(p.getLevel() + 10); - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "20")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 31: - if (cyInstance != null) { - p.openInventory( - createCYShopGUI(p)); - } - break; - } - } - - public void handleCYClick(Player p, int slot) { - if (slot < 0 || slot >= cyItems.size() - || slot >= 45) return; - Map item = cyItems.get(slot); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - if (stock == -1) { - p.sendMessage("§c该商品已下架"); - return; - } - int points = getPoints(p.getName()); - if (points < price) { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - return; - } - - plugin.getDb().deductPoints(p.getName(), price); - int actDays = lifetime ? 0 : days; - activateCY(p.getName(), slots, actDays); - - String tl = lifetime - ? "终身" : (days + "天"); - p.sendMessage("§a购买成功!获得 " - + tl + " +" + slots + "格 背包空间"); - } - - private void fillBg(Inventory inv) { - ItemStack gl = mkItem( - Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < inv.getSize(); i++) - inv.setItem(i, gl); - } - - private ItemStack mkItem(Material mat, String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/src/main/java/Sdf1_login/QuestStorage.java b/backup/src/main/java/Sdf1_login/QuestStorage.java deleted file mode 100644 index 45dbabf..0000000 --- a/backup/src/main/java/Sdf1_login/QuestStorage.java +++ /dev/null @@ -1,138 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -public class QuestStorage { - - private Connection conn; - - public QuestStorage(File dataFolder) { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - dataFolder, - "quest_data.db"); - conn = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getPath()); - initTable(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void initTable() - throws SQLException { - Statement stmt = - conn.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_conditions (" - + "player TEXT NOT NULL, " - + "condition_key TEXT NOT NULL, " - + "value TEXT DEFAULT '1', " - + "timestamp INTEGER, " - + "PRIMARY KEY " - + "(player, condition_key))"); - stmt.close(); - } - - /** - * 标记条件已完成(value = "1") - */ - public void setCondition( - String player, - String condition) { - setConditionRaw( - player, condition, "1"); - } - - /** - * 写入任意值(计数器等) - */ - public void setConditionRaw( - String player, - String condition, - String value) { - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO " - + "quest_conditions " - + "(player, " - + "condition_key, " - + "value, timestamp) " - + "VALUES " - + "(?, ?, ?, ?)"); - ps.setString(1, player); - ps.setString(2, condition); - ps.setString(3, value); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 查询条件值 - */ - public Object getCondition( - String player, - String condition) { - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value FROM " - + "quest_conditions " - + "WHERE player = ? " - + "AND " - + "condition_key = ?"); - ps.setString(1, player); - ps.setString(2, condition); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - String val = - rs.getString("value"); - rs.close(); - ps.close(); - // 尝试解析为数字 - try { - if (val.contains(".")) { - return Double.parseDouble( - val); - } - return Long.parseLong(val); - } catch (NumberFormatException e) { - return val; - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) { - conn.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } -} diff --git a/backup/src/main/java/Sdf1_login/QuestTracker.java b/backup/src/main/java/Sdf1_login/QuestTracker.java deleted file mode 100644 index 228c1ac..0000000 --- a/backup/src/main/java/Sdf1_login/QuestTracker.java +++ /dev/null @@ -1,3252 +0,0 @@ -package Sdf1_login; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -// 如果 Bonds 在 Sdf1_login 包下: -import Sdf1_login.BondManager; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - - -public class QuestTracker { - - private final Plugin plugin; - private final QuestStorage storage; - private final Map - playerBiome = new HashMap<>(); - private final Map> - questCache = new HashMap<>(); - private final List - checkInRecords = new ArrayList<>(); - private final Map - lastCheckInPos = new HashMap<>(); - - public void recordCheckIn( - String player, - String biomeKey, - String category, - String worldName, - double x, double y, double z) { - String record = player - + "|" + worldName - + "|" + biomeKey - + "|" + category - + "|" + String.format( - "%.1f", x) - + "|" + String.format( - "%.1f", y) - + "|" + String.format( - "%.1f", z) - + "|" + System.currentTimeMillis(); - checkInRecords.add(record); - plugin.getLogger().info( - "[QuestTracker] 打卡记录: " - + record); - } - - public List getCheckInRecords() { - return new ArrayList<>(checkInRecords); - } - - public List getPlayerRecords( - String player) { - List list = new ArrayList<>(); - for (String r : checkInRecords) { - if (r.startsWith( - player.toLowerCase() - + "|")) { - list.add(r); - } - } - return list; - } - - - public QuestTracker(Plugin plugin) { - this.plugin = plugin; - this.storage = new QuestStorage( - plugin.getDataFolder()); - - loadAllQuests(); - } - - public void debugPlayer(String player) { - plugin.getLogger().info( - "[QuestTracker] === " - + player + " 任务数据 ==="); - } - public void shutdown() { - if (storage != null) { - storage.close(); - } - } - - /** - * 玩家移动时记录当前群系 - */ - public void onPlayerMove( - String player, - String biomeName) { - playerBiome.put(player, biomeName); - } - /** - * 标记玩家已打卡该群系 - */ - // ========== 群系分类映射表 ========== - private static final Map - BIOME_CATEGORY = new HashMap<>(); - static { - // 海洋群系 - BIOME_CATEGORY.put("minecraft:ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋"); - // 平原群系 - BIOME_CATEGORY.put("minecraft:plains", "平原"); - BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原"); - BIOME_CATEGORY.put("minecraft:meadow", "平原"); - // 森林群系 - BIOME_CATEGORY.put("minecraft:forest", "森林"); - BIOME_CATEGORY.put("minecraft:flower_forest", "森林"); - BIOME_CATEGORY.put("minecraft:birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:dark_forest", "森林"); - BIOME_CATEGORY.put("minecraft:taiga", "森林"); - BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:cherry_grove", "森林"); - BIOME_CATEGORY.put("minecraft:jungle", "森林"); - BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林"); - BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林"); - // 沙漠群系 - BIOME_CATEGORY.put("minecraft:desert", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠"); - BIOME_CATEGORY.put("minecraft:beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠"); - // 恶地群系 - BIOME_CATEGORY.put("minecraft:badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地"); - // 雪原群系 - BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原"); - BIOME_CATEGORY.put("minecraft:grove", "雪原"); - BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原"); - BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_river", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原"); - // 沙砾群系 - BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾"); - // 沼泽群系 - BIOME_CATEGORY.put("minecraft:swamp", "沼泽"); - BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽"); - // 河流 - BIOME_CATEGORY.put("minecraft:river", "河流"); - // 蘑菇岛 - BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛"); - // 地下群系 - BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下"); - BIOME_CATEGORY.put("minecraft:lush_caves", "地下"); - BIOME_CATEGORY.put("minecraft:deep_dark", "地下"); - // 下界群系 - BIOME_CATEGORY.put("minecraft:nether_wastes", "下界"); - BIOME_CATEGORY.put("minecraft:crimson_forest", "下界"); - BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); - BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); - BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); - // 岩岸/沙滩 - BIOME_CATEGORY.put("minecraft:stony_shore", - "石岸"); -// 海岸 - BIOME_CATEGORY.put("minecraft:mushroom_fields", - "蘑菇岛"); -// 苍白之园 - BIOME_CATEGORY.put( - "minecraft:pale_garden", "森林"); -// 末地小型岛屿 - BIOME_CATEGORY.put( - "minecraft:small_end_islands", "末地"); -// 主世界结构归类 - BIOME_CATEGORY.put("minecraft:village", "平原"); - BIOME_CATEGORY.put( - "minecraft:desert_pyramid", "沙漠"); - BIOME_CATEGORY.put( - "minecraft:jungle_pyramid", "森林"); - BIOME_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - - // 末地群系 - BIOME_CATEGORY.put("minecraft:the_end", "末地"); - BIOME_CATEGORY.put("minecraft:the_void", "末地"); - BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); - - - } - - // 所有群系大类列表 - private static final String[] - ALL_CATEGORIES = { - "海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "石岸", "蘑菇岛", "地下", - "下界", "末地" - }; - - // ===== 结构映射 ===== - private static final Map - STRUCTURE_CATEGORY = - new HashMap<>(); - static { - // 主世界结构 - STRUCTURE_CATEGORY.put( - "minecraft:village", "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:desert_pyramid", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:jungle_pyramid", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - STRUCTURE_CATEGORY.put( - "minecraft:igloo", "雪原"); - STRUCTURE_CATEGORY.put( - "minecraft:mansion", "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:pillager_outpost", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:stronghold", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:ancient_city", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:trail_ruins", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:trial_chambers", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:buried_treasure", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:fossil", "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:ancient_city", - "地下"); - // 下界结构 - STRUCTURE_CATEGORY.put( - "minecraft:nether_fortress", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:bastion_remnant", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:nether_fossil", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:basalt_pillars", - "下界"); - } - - // 所有群系分类的条件关键词映射 - private static final Map - CATEGORY_KEYWORDS = new HashMap<>(); - static { - CATEGORY_KEYWORDS.put("海洋", "海洋"); - CATEGORY_KEYWORDS.put("平原", "平原"); - CATEGORY_KEYWORDS.put("森林", "森林"); - CATEGORY_KEYWORDS.put("沙漠", "沙漠"); - CATEGORY_KEYWORDS.put("恶地", "恶地"); - CATEGORY_KEYWORDS.put("雪原", "雪原"); - CATEGORY_KEYWORDS.put("沙砾", "沙砾"); - CATEGORY_KEYWORDS.put("沼泽", "沼泽"); - CATEGORY_KEYWORDS.put("河流", "河流"); - CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛"); - CATEGORY_KEYWORDS.put("地下", "地下"); - CATEGORY_KEYWORDS.put("下界", "下界"); - CATEGORY_KEYWORDS.put("末地", "末地"); - // 条件文本中的别名映射 - CATEGORY_KEYWORDS.put("地狱", "下界"); - CATEGORY_KEYWORDS.put("下届", "下界"); - CATEGORY_KEYWORDS.put("主岛", "末地"); - CATEGORY_KEYWORDS.put("石岸", "石岸"); - CATEGORY_KEYWORDS.put("沙滩", "石岸"); - CATEGORY_KEYWORDS.put("海岸", "石岸"); - - } - - /** - * 用世界维度名分类(最可靠) - * 世界名含 the_nether → 下界 - * 世界名含 the_end → 末地 - * 其余 → 主世界 - */ - public String mapBiomeCategory( - String rawKey, String worldName) { - // 优先用世界名判断维度 - if (worldName != null) { - String w = - worldName.toLowerCase(); - if (w.contains("nether")) - return "下界"; - if (w.contains("end")) - return "末地"; - } - - // 用群系映射表 - if (rawKey != null) { - String lower = - rawKey.toLowerCase(); - // 带 minecraft: 前缀的直接查 - String cat = - BIOME_CATEGORY.get(lower); - if (cat != null) - return cat; - - // 不带前缀的尝试补全 - String prefixed = - "minecraft:" + lower; - cat = BIOME_CATEGORY.get(prefixed); - if (cat != null) - return cat; - - // 模糊匹配群系名 - for (Map.Entry entry - : BIOME_CATEGORY.entrySet()) { - if (entry.getKey() - .contains(lower) - || lower.contains( - entry.getKey() - .replace( - "minecraft:", - ""))) { - return entry.getValue(); - } - } - } - - return "主世界"; - } - - - -// ========== 解析为区块 ========== - - private List
parseIntoSections( - List lines) { - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - return parseHashSections(lines); - } else if (hasDash) { - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - continue; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseHashSections(converted); - } - return new ArrayList<>(); - } - - private List
parseHashSections( - List lines) { - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add(new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } else if (t.isEmpty()) { - // skip - } else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty() - && curTitle != null) { - curLines.add(cond); - } - } - } - if (curTitle != null) { - sections.add(new Section( - curTitle, curLines)); - } - return sections; - } - - private List
buildFlatSections( - List lines, - String category) { - List conditions = - new ArrayList<>(); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - conditions.add(cond); - } - } - if (conditions.isEmpty()) - return new ArrayList<>(); - List
list = - new ArrayList<>(); - list.add(new Section( - "第一阶段", conditions)); - return list; - } - - // ========== 文件加载入口 ========== - - private void loadAllQuests() { - loadCategory("新人任务"); - loadCategory("主线任务"); - loadCategory("支线任务"); - } - - private void loadCategory(String name) { - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 扫描中..."); - File folder = new File( - plugin.getDataFolder(), - name); - String path = "plugins/Sdf1_login/" - + name + "/"; - - if (!folder.exists()) { - folder.mkdirs(); - plugin.getLogger().warning( - "[QuestTracker] 已创建目录: " - + path); - questCache.put(name, - new ArrayList<>()); - return; - } - - File[] mdFiles = folder.listFiles( - (d, n) -> n.endsWith(".md")); - - if (mdFiles == null - || mdFiles.length == 0) { - plugin.getLogger().warning( - "[QuestTracker] " + path - + " 下无 .md 文件"); - questCache.put(name, - new ArrayList<>()); - return; - } - Arrays.sort(mdFiles); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 发现 " + mdFiles.length - + " 个文件,开始扫描..."); - - // 合并所有文件的阶段 - TreeMap merged = - new TreeMap<>(); - int fileCount = 0; - - for (int fi = 0; - fi < mdFiles.length; fi++) { - File file = mdFiles[fi]; - List lines = - readFileLines(file); - - if (lines.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 空文件,跳过"); - continue; - } - - List
sections = - parseIntoSections(lines); - - if (sections.isEmpty()) { - sections = buildFlatSections( - lines, name); - } - - if (sections.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 无法识别,跳过"); - continue; - } - - // 每个文件独立处理阶段号 - int defaultNum = fi + 1; - buildSingleFileStages( - sections, name, - merged, defaultNum); - - fileCount++; - } - - if (merged.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " + name - + ": 无有效任务数据"); - questCache.put(name, - new ArrayList<>()); - return; - } - - // 日志 - for (Map.Entry en - : merged.entrySet()) { - int num = en.getKey() >= 1000 - ? en.getKey() - 999 - : en.getKey(); - plugin.getLogger().info( - "[QuestTracker] " + name - + " 阶段" + num - + " \"" - + en.getValue().displayName - + "\" → 条件" - + en.getValue() - .conditions.size() - + "项, 详情" - + en.getValue() - .details.size() - + "项, 奖励" - + en.getValue() - .rewards.size() - + "项"); - } - - List list = - new ArrayList<>( - merged.values()); - questCache.put(name, list); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 解析完成 → " - + list.size() + " 个阶段" - + " (来自 " + fileCount - + " 个文件)"); - } - - private Integer extractStageNumber( - String title) { - if (title == null) return null; - String t = title.toLowerCase().trim(); - - // 含阶段/stage/关/chapter 关键词 - boolean isStage = - t.contains("阶段") - || t.contains("stage") - || t.contains("关卡") - || t.contains("章节") - || t.contains("chapter"); - - if (isStage) { - String digits = title - .replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - try { - return Integer.parseInt(digits); - } catch (NumberFormatException e) { - // fall through - } - } - return parseChineseNumber(title); - } - - // 无关键词但以数字开头(如 "1. 挖掘") - String trimmed = t - .replaceAll("^[.、\\s]+", "") - .trim(); - if (trimmed.length() > 0 - && Character.isDigit( - trimmed.charAt(0))) { - String num = trimmed - .replaceAll("[^0-9]", ""); - if (!num.isEmpty()) { - try { - return Integer.parseInt(num); - } catch (NumberFormatException e) { - // fall through - } - } - } - - return null; - } -// ========== 领取记录 ========== - - /** - * 标记已领取某阶段奖励 - */ - public void markClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return; - // 用阶段编号代替显示名,保证稳定 - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - storage.setCondition(player, key); - } - - public boolean hasClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return false; - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - return toBool( - storage.getCondition( - player, key)); - } - - private int parseChineseNumber( - String title) { - String[] cn = {"一", "二", "三", - "四", "五", "六", "七", - "八", "九", "十"}; - if (title.contains("十")) { - for (int j = 0; j < 9; j++) { - if (title.contains(cn[j])) { - return 10 + j + 1; - } - } - return 10; - } - for (int i = 0; i < 9; i++) { - if (title.contains(cn[i])) { - return i + 1; - } - } - return 0; - } - - // ========== md 解析核心 ========== - - /** - * 读取文件所有行 - */ - private List readFileLines( - File file) { - List lines = - new ArrayList<>(); - try { - BufferedReader br = - new BufferedReader( - new FileReader(file)); - String line; - while ((line = br.readLine()) - != null) { - lines.add(line); - } - br.close(); - } catch (Exception e) { - plugin.getLogger().warning( - "[QuestTracker] 读取 " - + file.getName() - + " 失败: " - + e.getMessage()); - } - return lines; - } - - /** - * 自动检测格式并解析单个文件 - */ - private List parseMdFile( - File file, String category) { - List lines = - readFileLines(file); - if (lines.isEmpty()) - return new ArrayList<>(); - - // 检测分隔符 - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) - hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - // # 分隔格式 - return parseSections( - lines, category); - } else if (hasDash) { - // ---- 分隔格式:转换为 # 后复用 - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - title = "separator"; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseSections( - converted, category); - } else { - // 无分隔符:整文件一个阶段 - return parseFlat(lines, category); - } - } - - /** - * # 分隔格式 → 区块 → 阶段 - */ - private List parseSections( - List lines, - String category) { - // 拆分为区块 - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - - // # 开头 → 新区块 - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add( - new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } - // 空行/分隔线 → 跳过 - else if (t.isEmpty() - || t.startsWith("----") - || t.startsWith("====") - || t.startsWith("***")) { - // skip - } - // 普通行 → 区块内容 - else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - curLines.add(cond); - } - } - } - // 保存最后一个区块 - if (curTitle != null) { - sections.add( - new Section( - curTitle, curLines)); - } - - // 直接转为 QuestFile 列表 - List result = - new ArrayList<>(); - int num = 1; - for (Section sec : sections) { - QuestFile qf = new QuestFile(); - qf.displayName = - extractDisplayName( - sec.title); - qf.fileName = category; - qf.conditions = - new ArrayList<>(sec.lines); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - result.add(qf); - num++; - } - return result; - } - - /** - * 无分隔符:整文件 = 一个阶段 - */ - private List parseFlat( - List lines, - String category) { - List list = - new ArrayList<>(); - QuestFile qf = makeDefaultStage( - category); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - qf.conditions.add(cond); - } - } - if (!qf.conditions.isEmpty()) { - list.add(qf); - } - return list; - } -// ========== Vault 经济奖励发放 ========== - - /** - * 发放阶段奖励 - * 含经济内容的走 Vault API或债券 - */ - public void claimRewards( - Player player, - QuestFile qf) { - if (qf == null - || qf.rewards == null - || qf.rewards.isEmpty()) { - player.sendMessage("§c无可领取的奖励"); - return; - } - - Main main = (Main) plugin; - // ★ 读取配置:economy = Vault,bonds = 债券(默认) - boolean useEconomy = "economy".equalsIgnoreCase( - main.getConfigMgr().rewardChannel); - - // Vault 经济引用(仅 useEconomy 时使用) - net.milkbowl.vault.economy.Economy econ = null; - if (useEconomy) { - econ = main.getEconomy(); - } - - int sentCount = 0; - - for (String reward : qf.rewards) { - String rt = reward.trim(); - if (rt.isEmpty()) continue; - - // 检查可选奖励标记 - if (hasOptionalMarker(rt)) { - if (!areAllConditionsMet(player.getName(), qf)) { - player.sendMessage( - "§7[可选奖励] 需完成所有可选条件: " + rt); - continue; - } - } - - // 按 + 分割子项 - String[] parts = rt.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 尝试识别经济金额 - double amt = extractEconomyAmount(part); - if (amt > 0) { - // ★ 根据配置分发 - if (useEconomy && econ != null) { - // —— Vault 经济 —— - double before = - econ.getBalance(player); - econ.depositPlayer(player, amt); - double after = - econ.getBalance(player); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(余额: §e" - + String.format("%.2f", before) - + " §7→ §a" - + String.format("%.2f", after) - + "§7)"); - if (plugin instanceof Main) { - ((Main) plugin).getCommission() - .payCommission( - player.getName(), amt); - } - } else { - // —— 债券(默认) —— - int bondAmt = - (int) Math.round(amt); - BondManager bonds = main.getBonds(); - if (bonds != null) { - int before = bonds.getBonds( - player.getName()); - bonds.addBonds( - player.getName(), - bondAmt, - "quest_reward", - qf.displayName, - "任务系统", - "任务奖励:" + qf.displayName); - int after = bonds.getBonds( - player.getName()); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(债券: §e" - + before - + " §7→ §a" - + after + "§7)"); - } else { - player.sendMessage( - "§c债券系统不可用"); - } - } - sentCount++; - continue; - } - - // 尝试发物品 - java.util.List items = - parseRewardItems(part); - if (!items.isEmpty()) { - for (ItemStack is : items) { - player.getInventory().addItem(is); - } - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - continue; - } - - // 非经济非物品 → 仅消息 - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - } - } - - plugin.getLogger().info( - "[QuestTracker] " + player.getName() - + " 预取了 \"" + qf.displayName - + "\" 共 " + sentCount + "项奖励"); - } - - /** - * 检查奖励文本是否含可选标记 - */ - private boolean hasOptionalMarker( - String text) { - return text.contains("(可选)") - || text.contains("(进阶)") - || text.contains("【可选】") - || text.contains("【进阶】"); - } - - /** - * 检查阶段所有条件(含可选)是否全部满足 - * 用于决定可选奖励是否发放 - */ - private boolean areAllConditionsMet( - String player, QuestFile qf) { - if (qf.conditions == null - || qf.conditions.isEmpty()) - return true; - for (String cond : qf.conditions) { - if (!isConditionMet(player, cond)) - return false; - } - return true; - } - - /** - * 从奖励文本中提取经济金额 - * 支持: "500游戏币" "500" "金币500" - * "$500" "1000-5000"(随机) "1000~5000" - */ - private double extractEconomyAmount( - String text) { - String t = text.replaceAll(" ", ""); - - // 必须包含经济关键词才算经济奖励 - boolean hasEcon = - t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额") - || t.contains("发钱") - || t.contains("债券"); // ★ 新增 - - - if (!hasEcon) return 0; - - // 范围: "1000-5000" 或 "1000~5000" - Matcher rm = Pattern - .compile("(\\d+)[-~](\\d+)") - .matcher(t); - if (rm.find()) { - double min = Double.parseDouble( - rm.group(1)); - double max = Double.parseDouble( - rm.group(2)); - return min + Math.random() - * (max - min); - } - - // 纯数字(含 $ 前缀) - Matcher nm = Pattern - .compile("\\$?(\\d+(\\.\\d+)?)") - .matcher(t); - if (nm.find()) { - return Double.parseDouble( - nm.group(1)); - } - - return 0; - } - /** - * 解析奖励物品文本 - * "皮革套装*1" → 4件皮革装备 - * "木剑*1" → 1把木剑 - * "钻石*64" → 64颗钻石 - */ - private List parseRewardItems( - String text) { - List result = - new ArrayList<>(); - - // 去掉条件前缀 - // 如 "完成注册登录:皮革套装*1" - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - - // 按 + 分割多个物品 - String[] parts = - text.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 跳过经济文本 - if (extractEconomyAmount(part) > 0) - continue; - - int amount = 1; - String itemName = part; - - // 解析 *N 数量 - if (part.contains("*")) { - String[] sp = - part.split("\\*"); - itemName = sp[0].trim(); - try { - amount = Integer.parseInt( - sp[1].trim()); - } catch (Exception e) { - amount = 1; - } - } - - List mats = - matchMaterials(itemName); - for (Material m : mats) { - int give = m.name() - .contains("HELMET") - || m.name() - .contains("CHESTPLATE") - || m.name() - .contains("LEGGINGS") - || m.name() - .contains("BOOTS") - ? 1 : amount; - result.add( - new ItemStack(m, give)); - } - } - return result; - } - - /** - * 中文物品名 → Material 列表 - * 套装返回多个部件 - */ - private List matchMaterials( - String name) { - List list = - new ArrayList<>(); - String n = name.toLowerCase().trim(); - - // 套装 - if (n.contains("皮革套装") - || n.contains("皮套装")) { - list.add(Material.LEATHER_HELMET); - list.add(Material.LEATHER_CHESTPLATE); - list.add(Material.LEATHER_LEGGINGS); - list.add(Material.LEATHER_BOOTS); - return list; - } - if (n.contains("铁套装") - || n.contains("铁甲")) { - list.add(Material.IRON_HELMET); - list.add(Material.IRON_CHESTPLATE); - list.add(Material.IRON_LEGGINGS); - list.add(Material.IRON_BOOTS); - return list; - } - if (n.contains("钻石套装") - || n.contains("钻甲")) { - list.add(Material.DIAMOND_HELMET); - list.add(Material.DIAMOND_CHESTPLATE); - list.add(Material.DIAMOND_LEGGINGS); - list.add(Material.DIAMOND_BOOTS); - return list; - } - // 木制工具套装 - if (n.contains("木制工具") - || n.contains("木工具")) { - list.add(Material.WOODEN_SWORD); - list.add(Material.WOODEN_PICKAXE); - list.add(Material.WOODEN_AXE); - list.add(Material.WOODEN_SHOVEL); - return list; - } - if (n.contains("石制工具") - || n.contains("石工具")) { - list.add(Material.STONE_SWORD); - list.add(Material.STONE_PICKAXE); - list.add(Material.STONE_AXE); - list.add(Material.STONE_SHOVEL); - return list; - } - if (n.contains("铁工具")) { - list.add(Material.IRON_SWORD); - list.add(Material.IRON_PICKAXE); - list.add(Material.IRON_AXE); - list.add(Material.IRON_SHOVEL); - return list; - } - - // 单个物品 - if (n.contains("木剑")) - list.add(Material.WOODEN_SWORD); - else if (n.contains("木镐")) - list.add(Material.WOODEN_PICKAXE); - else if (n.contains("铁剑")) - list.add(Material.IRON_SWORD); - else if (n.contains("铁镐")) - list.add(Material.IRON_PICKAXE); - else if (n.contains("钻石剑")) - list.add(Material.DIAMOND_SWORD); - else if (n.contains("钻石镐")) - list.add(Material.DIAMOND_PICKAXE); - else if (n.contains("钻石")) - list.add(Material.DIAMOND); - else if (n.contains("铁锭")) - list.add(Material.IRON_INGOT); - else if (n.contains("金锭") - || n.contains("金块")) - list.add(Material.GOLD_INGOT); - else if (n.contains("煤炭")) - list.add(Material.COAL); - else if (n.contains("木棍")) - list.add(Material.STICK); - else if (n.contains("弓")) - list.add(Material.BOW); - else if (n.contains("箭")) - list.add(Material.ARROW); - else if (n.contains("床")) - list.add(Material.RED_BED); - else { - // 尝试直接匹配 Material 名 - try { - list.add(Material.valueOf( - n.toUpperCase() - .replaceAll("[^A-Z_]", - ""))); - } catch (Exception e) { - // 未知物品 - } - } - return list; - } - - private QuestFile getOrCreateStage( - TreeMap map, - int key, - String category) { - if (!map.containsKey(key)) { - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + key; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - } - return map.get(key); - } - - /** - * 单个文件的区块 → 阶段 - * defaultStageNum: 文件无阶段标题时的默认编号 - */ - private void buildSingleFileStages( - List
sections, - String category, - TreeMap stageMap, - int defaultStageNum) { - Integer curKey = null; - - for (Section sec : sections) { - Integer stageNum = - extractStageNumber(sec.title); - - if (stageNum != null) { - curKey = stageNum; - } else if (curKey == null) { - curKey = defaultStageNum; - } - - QuestFile qf = getOrCreateStage( - stageMap, curKey, category); - - String displayName = - extractDisplayName(sec.title); - - // 设置显示名 - if (!displayName.isEmpty() - && qf.displayName - .startsWith("阶段 ")) { - qf.displayName = displayName; - } - - // 路由 - if (stageNum != null - && sec.lines.isEmpty()) { - if (!displayName.isEmpty()) { - qf.displayName = displayName; - } - } - else if (isRewardTitle(sec.title)) { - // 奖励 → 只进 rewards - qf.rewards.addAll(sec.lines); - } - else if (isDetailsTitle(sec.title)) { - // 详情 → 进 details + conditions - qf.details.addAll(sec.lines); - for (String line : sec.lines) { - if (!qf.conditions - .contains(line)) { - qf.conditions.add(line); - } - } - } - else if (isConditionsTitle( - sec.title)) { - // 条件 → 只进 conditions - qf.conditions.addAll(sec.lines); - } - else { - // 其他 → 条件 - qf.conditions.addAll(sec.lines); - } - } - } - - /** - * "主线任务-斗志昂扬" → "斗志昂扬" - * "支线-钓鱼" → "钓鱼" - * "阶段 1" → "阶段 1" - * "第一阶段" → "第一阶段" - */ - private String extractDisplayName( - String title) { - if (title == null) return ""; - // 按 - 或 — 或 – 分割 - String[] parts = title.split( - "\\s*[-—–]\\s*", 2); - if (parts.length >= 2) { - String right = parts[1].trim(); - if (!right.isEmpty()) { - return right; - } - } - return title; - } - private DatabaseManager getDb() { - return ((Main) plugin).getDb(); - } - - private boolean isDetailsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("任务详情") - || t.contains("详情") - || t.contains("任务说明") - || t.contains("任务目标") - || t.contains("description") - || t.contains("任务要求"); - } - - private boolean isConditionsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("解锁条件") - || t.contains("条件") - || t.contains("前置条件") - || t.contains("前置要求") - || t.contains("解锁") - || t.contains("condition"); - } - - - private void ensureStage( - TreeMap map, - String category, - QuestFile current, - int counter) { - if (current != null) return; - // 用大数字确保排在有编号阶段后面 - int key = 1000 + counter; - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + (counter + 1); - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - // 更新调用方的引用(通过返回值) - // 这里用 map 操作,current 无法直接更新 - // 所以改用返回值方式 - } - /** - * 自动将条件中的奖励内容移到 rewards - */ - private void separateRewards(QuestFile qf) { - List remaining = - new ArrayList<>(); - for (String line : qf.conditions) { - if (isRewardContent(line)) { - if (!qf.rewards.contains(line)) { - qf.rewards.add(line); - } - } else { - remaining.add(line); - } - } - qf.conditions = remaining; - } - private boolean checkStageDependency( - String player, - String condition) { - Matcher m = Pattern.compile( - "第([一二三四五六七八九十\\d]+)" - + "阶段") - .matcher(condition); - - while (m.find()) { - String numStr = m.group(1); - int stageNum = - parseChineseNumber(numStr); - if (stageNum == 0) { - try { - stageNum = - Integer.parseInt(numStr); - } catch (Exception e) { - return false; - } - } - - boolean stageDone = false; - for (List quests - : questCache.values()) { - for (QuestFile qf : quests) { - Integer qfNum = - extractStageNumber( - qf.displayName); - if (qfNum != null - && qfNum == stageNum) { - if (isStageComplete(player, - qf.conditions)) { - stageDone = true; - } - break; - } - } - if (stageDone) break; - } - - if (!stageDone) return false; - } - return true; - } - - /** - * 判断一行内容是否为奖励 - */ - private boolean isRewardContent( - String line) { - String l = line.toLowerCase(); - // 含货币关键词 - if (l.contains("游戏币") - || l.contains("金币") - || l.contains("积分") - || l.contains("钻石") - || l.contains("发钱") - || l.contains("发放") - || l.contains("经验") - || l.contains("等级") - || l.contains("经济") - || l.contains("余额") - || l.contains("块钱")) - return true; - // 纯数字+单位(如 "500游戏币") - if (l.matches( - ".*\\d+\\s*(游戏币|金币|元|积分|" - + "钻石|块钱|经验|级|经济|余额).*")) - return true; - // $前缀(如 "$500") - if (l.matches(".*\\$\\d+.*")) - return true; - // 范围格式(如 "1000-5000" 或 "1000~5000") - if (l.matches(".*\\d+[-~]\\d+.*") - && !l.contains("阶段") - && !l.contains("stage")) - return true; - return false; - } - - // ========== 标题分类 ========== - - private boolean isStageTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("阶段") - || t.contains("stage") - || t.contains("第一") - || t.contains("第二") - || t.contains("第三") - || t.contains("第四") - || t.contains("第五"); - } - - private boolean isRewardTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("奖励") - || t.contains("奖品") - || t.contains("报酬") - || t.contains("reward") - || t.contains("奖赏") - || t.contains("礼包内容") - || t.contains("礼包") - || t.contains("物品奖励"); - } - - - private QuestFile makeDefaultStage( - String category) { - QuestFile qf = new QuestFile(); - qf.displayName = "第一阶段"; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - return qf; - } - - private static class Section { - final String title; - final List lines; - Section(String title, - List lines) { - this.title = title; - this.lines = lines; - } - } - - // ========== 公开查询 ========== - - public List getQuests( - String category) { - List list = - questCache.get(category); - return list != null ? list - : new ArrayList<>(); - } - - public int getCompletedStageCount( - String player, - List quests) { - int count = 0; - if (quests == null) return 0; - for (QuestFile qf : quests) { - if (isStageComplete(player, - qf.conditions)) { - count++; - } - } - return count; - } - - public QuestFile getCurrentStage( - String player, - List quests) { - if (quests == null) return null; - for (QuestFile qf : quests) { - if (!isStageComplete( - player, qf.conditions)) { - return qf; - } - } - return null; - } - - public int getStageNumber( - List quests, - QuestFile qf) { - if (quests == null || qf == null) - return 0; - return quests.indexOf(qf) + 1; - } - - public int getStageNumber( - QuestFile qf) { - if (qf == null) return 0; - List quests = - getQuests(qf.fileName); - return getStageNumber(quests, qf); - } - - public boolean isStageCompleted( - String player, - QuestFile qf) { - if (qf == null) return false; - return isStageComplete( - player, qf.conditions); - } - - public List getUnmetConditions( - String player, - List conditions) { - List unmet = - new ArrayList<>(); - if (conditions == null) return unmet; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) { - unmet.add( - getProgress( - player, cond)); - } - } - return unmet; - } - public String getProgress( - String player, String rawCond) { - if (player == null || rawCond == null) - return "§7未知"; - String key = stripOptional(rawCond); - - if (key.contains("打卡") - && key.contains("群系")) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return "§7打卡 " + biome - + "群系: §e" - + catCount + "/1"; - } - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - if (key.contains("完成") - && key.contains("阶段")) { - boolean done = checkStageDependency( - player, key); - return done - ? "§a✓ " + key - : "§c✗ " + key; - } - - if ("完成注册登录".equals(key)) { - boolean done = getDb() != null - && getDb().userExists( - player); - return done - ? "§a✓ 完成注册登录" - : "§c✗ 完成注册登录"; - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - boolean done = val != null - && !val.toString().isEmpty(); - return done - ? "§a✓ 绑定Email" - : "§c✗ 绑定Email"; - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + needed + "小时"; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - int cur = getCounter( - player, "CHAT"); - return "§7聊天: §e" - + cur + "/1"; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return "§7方块操作: §e" - + total + "/" + needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return "§7连续登录: §e" - + streak + "/" + needed + "天"; - } - - if (key.startsWith("MINE:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "MINE:" + parts[1]); - return "§7挖 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "VILLAGER_TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - - } - - // ===== 击杀泛解析(支持中文列表) ===== -// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等 - if (key.contains("击杀") && key.contains("只")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - - String mobPart = key; - java.util.regex.Matcher killM = - java.util.regex.Pattern - .compile("击杀\\d+只(.+)") - .matcher(key); - if (killM.find()) { - mobPart = killM.group(1).trim(); - } - - String[] mobNames = mobPart - .split("[、,,,]+"); - int totalKills = 0; - StringBuilder names = new StringBuilder(); - for (String name : mobNames) { - String trimmed = name.trim(); - if (trimmed.isEmpty()) continue; - String entityType = - chineseMobToEntity(trimmed); - if (!entityType.isEmpty()) { - totalKills += getCounter( - player, - "KILL:" + entityType); - if (names.length() > 0) - names.append("/"); - names.append(trimmed); - } - } - return "§7击杀" + names + ": §e" - + totalKills + "/" + needed; - } - -// 单一KILL:xxx:N 格式(原有逻辑保留) - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - int needed = parseInt(p[2]); - int cur = getCounter(player, - "KILL:" + p[1]); - return "§7击杀" + p[1] + ": §e" - + cur + "/" + needed; - } - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "KILL_PLAYER"); - return "§7击杀玩家: §e" - + cur + "/" + needed; - } - - if (key.startsWith("CRAFT")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "CRAFT"); - return "§7合成: §e" - + cur + "/" + needed; - } - - if (key.startsWith("FISH")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "FISH"); - return "§7钓鱼: §e" - + cur + "/" + needed; - } - - if (key.startsWith("PLAYTIME")) { - String[] parts = key.split(":"); - long needed = parts.length >= 2 - ? Long.parseLong(parts[1]) - : 0; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + formatMs(needed); - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7上报村庄: §e" - + cur + "/" + needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7探索村庄: §e" - + cur + "/1"; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return "§7近7天登录: §e" - + streak + "/" - + needed + "天"; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7新手任务: §e" - + done + "/" - + quests.size(); - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7主线任务: §e" - + done + "/" - + quests.size(); - } - // ===== 泛解析:击杀类进度 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < mobTypes.size(); i++) { - total += getCounter(player, - "KILL:" + mobTypes.get(i)); - if (i > 0) names.append("/"); - names.append(reverseMobName( - mobTypes.get(i))); - } - return "§7击杀" + names + ": §e" - + total + "/" + needed; - } - } - - // ===== 泛解析:村民交易进度 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - // ===== 泛解析:采集/挖矿进度 ===== - if (key.contains("采集") - || key.contains("挖")) { - int needed = - parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < matTypes.size(); i++) { - total += getCounter( - player, - "MINE:" + matTypes - .get(i)); - if (i > 0) - names.append("/"); - names.append( - translateMaterialName( - matTypes - .get(i))); - } - return "§7采集" + names - + ": §e" + total - + "/" + needed; - } - } - - // ===== 泛解析:合成进度 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "CRAFT"); - return "§7合成: §e" + cur + "/" + needed; - } - - // ===== 泛解析:钓鱼进度 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "FISH"); - return "§7钓鱼: §e" + cur + "/" + needed; - } - - return "§7" + key; - } - private String translateMaterialName( - String name) { - switch (name) { - case "COAL_ORE": - return "煤矿石"; - case "DEEPSLATE_COAL_ORE": - return "深层煤矿石"; - case "IRON_ORE": - return "铁矿石"; - case "DEEPSLATE_IRON_ORE": - return "深层铁矿石"; - case "COPPER_ORE": - return "铜矿石"; - case "DEEPSLATE_COPPER_ORE": - return "深层铜矿石"; - case "GOLD_ORE": - return "金矿石"; - case "DEEPSLATE_GOLD_ORE": - return "深层金矿石"; - case "DIAMOND_ORE": - return "钻石矿石"; - case "DEEPSLATE_DIAMOND_ORE": - return "深层钻石矿石"; - case "EMERALD_ORE": - return "绿宝石矿石"; - case "DEEPSLATE_EMERALD_ORE": - return "深层绿宝石矿石"; - case "LAPIS_ORE": - return "青金石矿石"; - case "DEEPSLATE_LAPIS_ORE": - return "深层青金石矿石"; - case "REDSTONE_ORE": - return "红石矿石"; - case "DEEPSLATE_REDSTONE_ORE": - return "深层红石矿石"; - case "NETHER_GOLD_ORE": - return "下界金矿石"; - case "NETHER_QUARTZ_ORE": - return "下界石英矿石"; - case "ANCIENT_DEBRIS": - return "远古残骸"; - case "COAL": - return "煤炭"; - case "RAW_IRON": - return "粗铁"; - case "RAW_GOLD": - return "粗金"; - case "RAW_COPPER": - return "粗铜"; - case "DIAMOND": - return "钻石"; - case "EMERALD": - return "绿宝石"; - case "LAPIS_LAZULI": - return "青金石"; - case "REDSTONE": - return "红石"; - case "QUARTZ": - return "石英"; - default: - return name; - } - } - - /** - * 中文物种名 → Bukkit EntityType 名 - * 支持击杀泛解析的模糊匹配 - */ - private String chineseMobToEntity(String cn) { - String n = cn.toLowerCase().trim(); - - // 直接别名表 - Map mobMap = new HashMap<>(); - mobMap.put("骷髅", "SKELETON"); - mobMap.put("僵尸", "ZOMBIE"); - mobMap.put("苦力怕", "CREEPER"); - mobMap.put("蜘蛛", "SPIDER"); - mobMap.put("洞穴蜘蛛", "CAVE_SPIDER"); - mobMap.put("小白", "SKELETON"); - mobMap.put("末影人", "ENDERMAN"); - mobMap.put("enderman", "ENDERMAN"); - mobMap.put("猪灵", "PIGLIN"); - mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE"); - mobMap.put("恶魂", "GHAST"); - mobMap.put("凋灵骷髅", "WITHER_SKELETON"); - mobMap.put("烈焰人", "BLAZE"); - mobMap.put("史莱姆", "SLIME"); - mobMap.put("岩浆怪", "MAGMA_CUBE"); - mobMap.put("女巫", "WITCH"); - mobMap.put("蠹虫", "SILVERFISH"); - mobMap.put("潜影贝", "SHULKER"); - mobMap.put("守卫者", "GUARDIAN"); - mobMap.put("远古守卫者", "ELDER_GUARDIAN"); - mobMap.put("溺尸", "DROWNED"); - mobMap.put("僵尸村民", "ZOMBIE_VILLAGER"); - mobMap.put("尸壳", "HUSK"); - mobMap.put("流浪者", "STRAY"); - mobMap.put("幻翼", "PHANTOM"); - mobMap.put("掠夺者", "PILLAGER"); - mobMap.put("卫道士", "VINDICATOR"); - mobMap.put("唤魔者", "EVOKER"); - mobMap.put("恼鬼", "VEX"); - mobMap.put("劫掠兽", "RAVAGER"); - mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY"); - mobMap.put("凋灵", "WITHER"); - mobMap.put("末影龙", "ENDER_DRAGON"); - - // 精确匹配 - if (mobMap.containsKey(n)) { - return mobMap.get(n); - } - - // 模糊匹配(包含关系) - for (Map.Entry en - : mobMap.entrySet()) { - if (n.contains(en.getKey()) - || en.getKey().contains(n)) { - return en.getValue(); - } - } - - // 尝试直接用英文名 - try { - return org.bukkit.entity.EntityType - .valueOf(n.toUpperCase() - .replace(" ", "_")) - .name(); - } catch (Exception e) { - return ""; - } - } - - public List checkInBiome( - String player, - String rawBiomeKey, - String worldName, - double x, double y, double z) { - List matched = - new ArrayList<>(); - - // 32格去重 - String posKey = - player + "_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - String lastPos = - lastCheckInPos.get(posKey); - if (curPos.equals(lastPos)) { - plugin.getLogger().info( - "[QuestTracker] 打卡忽略: " - + player - + " 32格内重复"); - return matched; - } - lastCheckInPos.put(posKey, curPos); - - String category = - mapBiomeCategory( - rawBiomeKey, worldName); - // 结构检测(覆盖纯生物群系分类) - // 地下检测:Y低于表面则归类为地下 -// 覆盖废弃矿井/试炼密室/要塞/远古城市等 - try { - org.bukkit.World uw = - Bukkit.getWorld(worldName); - if (uw != null) { - int surfaceY = uw - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - } - } catch (Exception ignored) {} - - - // 记录总打卡 - int allCur = getCounter( - player, "biome_ALL"); - setCounter(player, "biome_ALL", - allCur + 1); - - // 记录分类打卡 - if (!category.isEmpty() - && !"主世界" - .equals(category)) { - String catKey = - "biomecat_" + category; - int catCur = getCounter( - player, catKey); - setCounter(player, catKey, - catCur + 1); - } - - // 记录具体群系 - String bioKey = - "biome_" + rawBiomeKey; - int bioCur = getCounter( - player, bioKey); - setCounter(player, bioKey, - bioCur + 1); - - // 检查所有任务条件是否匹配 - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - for (String cat : categories) { - List quests = - getQuests(cat); - for (QuestFile qf : quests) { - if (isStageCompleted( - player, qf)) - continue; - for (String cond - : qf.conditions) { - if (isOptional(cond)) - continue; - String condKey = - stripOptional(cond); - if (!condKey - .contains("打卡") - || !condKey - .contains("群系")) - continue; - String target = - extractBiome(condKey); - boolean hit = false; - if ("ALL".equals(target) - || "风景点" - .equals(target)) { - hit = true; - } else if (!target.isEmpty() - && target.equals( - category)) { - hit = true; - } - if (hit && !matched - .contains(cond)) { - matched.add(cond); - } - } - } - } - - return matched; - } - - /* - * 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录 - */ - public String checkInAtPosition( - String player, String worldName, - double x, double y, double z) { - - List history = - loadCheckInPositions(player); - for (double[] pos : history) { - double dist = Math.sqrt( - Math.pow(x - pos[0], 2) - + Math.pow(y - pos[1], 2) - + Math.pow(z - pos[2], 2)); - if (dist <= 32) { - return "§c距离上次打卡点太近(" - + String.format( - "%.0f", dist) - + "格)"; - } - } - - // ===== 获取群系和分类 ===== - String biome = "minecraft:plains"; - String category = "平原"; - try { - org.bukkit.World w = - Bukkit.getWorld(worldName); - if (w != null) { - org.bukkit.Location loc = - new org.bukkit.Location( - w, - (int) Math.floor(x), - (int) Math.floor(y), - (int) Math.floor(z)); - biome = loc.getBlock() - .getBiome().getKey() - .toString(); - category = mapBiomeCategory( - biome, worldName); - - int surfaceY = w - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - - if ("下界".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.NETHER) { - category = "下界"; - } else if ("末地".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.THE_END) { - category = "末地"; - } - } - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 群系读取失败: " - + ex.getMessage()); - } - - plugin.getLogger().info( - "[QuestTracker] 打卡: " - + player - + " world=" + worldName - + " biome=" + biome - + " cat=" + category - + " pos=" - + String.format( - "%.0f,%.0f,%.0f", - x, y, z)); - - saveCheckInPosition( - player, x, y, z); - recordCheckIn( - player, biome, category, - worldName, x, y, z); - List matched = - checkInBiome( - player, biome, - worldName, x, y, z); - - StringBuilder sb = new StringBuilder(); - sb.append("§a打卡成功!群系: §e") - .append(category); - if (!matched.isEmpty()) { - sb.append("\n§a完成任务条件:"); - for (String m : matched) { - String qn = - findQuestName(m); - sb.append("\n§7• "); - if (qn != null) { - sb.append("§e[") - .append(qn) - .append("§7] §f"); - } - sb.append(m); - } - } - return sb.toString(); - } - - - /** - * 根据条件文本查找所属任务名 - */ - private String findQuestName( - String condition) { - for (String cat : Arrays.asList( - "新人任务", - "主线任务", - "支线任务")) { - for (QuestFile qf : - getQuests(cat)) { - if (qf.conditions.contains( - condition)) { - return qf.displayName; - } - } - } - return null; - } - - - /** - * 加载玩家所有历史打卡坐标 - * 格式: x1,y1,z1;x2,y2,z2;... - */ - private List loadCheckInPositions( - String player) { - List list = new ArrayList<>(); - Object val = storage.getCondition( - player, "checkin_all_pos"); - if (val == null) return list; - String data = val.toString(); - if (data.isEmpty()) return list; - String[] records = data.split(";"); - for (String r : records) { - String[] parts = r.split(","); - if (parts.length >= 3) { - try { - list.add(new double[]{ - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2])}); - } catch (NumberFormatException e) { - // 跳过损坏数据 - } - } - } - return list; - } - - /** - * 保存打卡坐标(追加到已有记录) - */ - private void saveCheckInPosition( - String player, double x, - double y, double z) { - List list = - loadCheckInPositions(player); - list.add(new double[]{x, y, z}); - StringBuilder sb = new StringBuilder(); - for (double[] pos : list) { - if (sb.length() > 0) sb.append(";"); - sb.append(pos[0]).append(",") - .append(pos[1]).append(",") - .append(pos[2]); - } - storage.setConditionRaw( - player, - "checkin_all_pos", - sb.toString()); - } - // ========== 中文生物名 → EntityType 映射 ========== - private static final Map - MOB_NAME_MAP = new HashMap<>(); - static { - MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER"); - MOB_NAME_MAP.put("骷髅", "SKELETON"); - MOB_NAME_MAP.put("僵尸", "ZOMBIE"); - MOB_NAME_MAP.put("苦力怕", "CREEPER"); - MOB_NAME_MAP.put("蜘蛛", "SPIDER"); - MOB_NAME_MAP.put("末影人", "ENDERMAN"); - MOB_NAME_MAP.put("女巫", "WITCH"); - MOB_NAME_MAP.put("烈焰人", "BLAZE"); - MOB_NAME_MAP.put("僵尸猪灵", - "ZOMBIFIED_PIGLIN"); - MOB_NAME_MAP.put("猪灵", "PIGLIN"); - MOB_NAME_MAP.put("恶魂", "GHAST"); - MOB_NAME_MAP.put("凋灵骷髅", - "WITHER_SKELETON"); - MOB_NAME_MAP.put("史莱姆", "SLIME"); - MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE"); - MOB_NAME_MAP.put("溺尸", "DROWNED"); - MOB_NAME_MAP.put("尸壳", "HUSK"); - MOB_NAME_MAP.put("流髑", "STRAY"); - MOB_NAME_MAP.put("幻翼", "PHANTOM"); - MOB_NAME_MAP.put("守卫者", "GUARDIAN"); - MOB_NAME_MAP.put("远古守卫者", - "ELDER_GUARDIAN"); - MOB_NAME_MAP.put("掠夺者", "PILLAGER"); - MOB_NAME_MAP.put("卫道士", "VINDICATOR"); - MOB_NAME_MAP.put("唤魔者", "EVOKER"); - MOB_NAME_MAP.put("恼鬼", "VEX"); - MOB_NAME_MAP.put("劫掠兽", "RAVAGER"); - MOB_NAME_MAP.put("潜影贝", "SHULKER"); - MOB_NAME_MAP.put("末影螨", "ENDERMITE"); - MOB_NAME_MAP.put("凋灵", "WITHER"); - MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON"); - MOB_NAME_MAP.put("监守者", "WARDEN"); - MOB_NAME_MAP.put("嗅探兽", "SNIFFER"); - MOB_NAME_MAP.put("僵尸村民", - "ZOMBIE_VILLAGER"); - } - - /** - * 从条件文本中提取所有匹配的生物类型 - * 按名称长度降序匹配,避免短名误匹配 - */ - private List extractMobTypes( - String text) { - List types = new ArrayList<>(); - List> sorted = - new ArrayList<>( - MOB_NAME_MAP.entrySet()); - sorted.sort((a, b) -> - b.getKey().length() - - a.getKey().length()); - String remaining = text; - for (Map.Entry entry - : sorted) { - if (remaining.contains( - entry.getKey())) { - if (!types.contains( - entry.getValue())) { - types.add(entry.getValue()); - } - remaining = remaining.replace( - entry.getKey(), ""); - } - } - return types; - } - - /** - * EntityType → 中文名 - */ - private String reverseMobName( - String entityType) { - for (Map.Entry entry - : MOB_NAME_MAP.entrySet()) { - if (entry.getValue() - .equals(entityType)) { - return entry.getKey(); - } - } - return entityType; - } - - /** - * 从条件文本中提取矿物名 - * "采集任意矿物(煤炭除外)1组" - * → [COAL_ORE, IRON_ORE, GOLD_ORE, ...] - */ - private List extractMaterialTypes( - String text) { - List types = new ArrayList<>(); - // 排除列表 - List excludes = - new ArrayList<>(); - if (text.contains("煤炭除外")) { - excludes.add("COAL"); - excludes.add("COAL_ORE"); - excludes.add("DEEPSLATE_COAL_ORE"); - } - // 通配符:"矿物" "矿石" - if (text.contains("矿物") - || text.contains("矿石")) { - String[] ores = { - "COAL_ORE", - "DEEPSLATE_COAL_ORE", - "IRON_ORE", - "DEEPSLATE_IRON_ORE", - "COPPER_ORE", - "DEEPSLATE_COPPER_ORE", - "GOLD_ORE", - "DEEPSLATE_GOLD_ORE", - "DIAMOND_ORE", - "DEEPSLATE_DIAMOND_ORE", - "EMERALD_ORE", - "DEEPSLATE_EMERALD_ORE", - "LAPIS_ORE", - "DEEPSLATE_LAPIS_ORE", - "REDSTONE_ORE", - "DEEPSLATE_REDSTONE_ORE", - "NETHER_GOLD_ORE", - "NETHER_QUARTZ_ORE", - "ANCIENT_DEBRIS" - }; - for (String ore : ores) { - if (!excludes.contains(ore)) { - types.add(ore); - } - } - return types; - } - // 单个矿物名 - if (text.contains("煤炭")) - types.add("COAL"); - if (text.contains("铁矿") - || text.contains("铁锭")) - types.add("RAW_IRON"); - if (text.contains("金矿") - || text.contains("金锭")) - types.add("RAW_GOLD"); - if (text.contains("钻石")) - types.add("DIAMOND"); - if (text.contains("绿宝石")) - types.add("EMERALD"); - if (text.contains("青金石")) - types.add("LAPIS_LAZULI"); - if (text.contains("红石")) - types.add("REDSTONE"); - if (text.contains("铜")) - types.add("RAW_COPPER"); - if (text.contains("下界石英") - || text.contains("石英")) - types.add("QUARTZ"); - return types; - } - - - /** - * 从条件文本中提取目标群系分类 - * "打卡上报地狱任意群系" → "下界" - * "打卡任意海洋群系" → "海洋" - * "打卡任意群系" → "ALL" - * "打卡任意风景点" → "" - */ - private String extractBiome(String text) { - if (text == null) return ""; - // 逐个匹配分类关键词 - for (Map.Entry en - : CATEGORY_KEYWORDS.entrySet()) { - if (text.contains(en.getKey())) { - return en.getValue(); - } - } - if (text.contains("风景点") - || text.contains("风景")) { - return "ALL"; - } - - // 有"群系"但无具体分类 → ALL - if (text.contains("群系")) { - return "ALL"; - } - return ""; - } - - - // ========== 标记条件 ========== - - public void markConditionComplete( - String player, - String conditionName) { - if (player == null - || conditionName == null) - return; - storage.setCondition( - player, conditionName); - } - - // ========== 事件回调 ========== - - public void onBlockMined( - String player, String material) { - String key = "MINE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - // 总计数 - setCounter(player, "MINE:ALL", - getCounter(player, "MINE:ALL") - + 1); - } - public boolean onReportVillage( - String player, - String worldName, - double x, double y, double z) { - String dbKey = "village_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - Object lastObj = - storage.getCondition( - player, dbKey); - String lastPos = lastObj != null - ? lastObj.toString() : ""; - if (curPos.equals(lastPos)) { - return false; - } - storage.setConditionRaw( - player, dbKey, curPos); - - String key = "REPORT_VILLAGE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - return true; - } - - public void onMobKill( - String player, String mobType) { - String key = "KILL:" + mobType; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayerKill( - String player) { - String key = "KILL_PLAYER"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onItemCrafted( - String player) { - String key = "CRAFT"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onFishCaught( - String player) { - String key = "FISH"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayTime( - String player, long ms) { - String key = "PLAYTIME"; - long cur = getCounterLong( - player, key); - setCounter(player, key, cur + ms); - } - - // ========== 内部:条件判定 ========== - - private boolean isStageComplete( - String player, - List conditions) { - if (conditions == null - || conditions.isEmpty()) - return true; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) - return false; - } - return true; - } - - public boolean isConditionMet( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return false; - - String key = stripOptional(rawCond); - - // 无条件类文本直接通过 - if (key.equals("无前级条件") - || key.equals("无条件") - || key.equals("无") - || key.equals("none") - || key.equals("无需条件") - || key.equals("无解锁条件")) { - return true; - } - - if (key.contains("打卡") - && (key.contains("群系") - || key.contains("风景点"))) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return total >= 1; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return catCount >= 1; - } - return getCounter( - player, "biome_ALL") >= 1; - } - - if (key.contains("完成") - && key.contains("阶段")) { - return checkStageDependency( - player, key); - } - - if ("完成注册登录".equals(key)) { - return getDb() != null - && getDb().userExists( - player); - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - return val != null - && !val.toString().isEmpty(); - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return cur >= needed * 3600000L; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - return getCounter( - player, "CHAT") >= 1; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return total >= needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return streak >= needed; - } - - if (key.startsWith("MINE:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "MINE:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "KILL:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "VILLAGER_TRADE") - >= needed; - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter(player, - "KILL_PLAYER") >= n; - } - - if (key.startsWith("CRAFT")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "CRAFT") >= n; - } - - if (key.startsWith("FISH")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "FISH") >= n; - } - - if (key.startsWith("PLAYTIME")) { - String[] p = key.split(":"); - long n = p.length >= 2 - ? Long.parseLong(p[1]) : 0; - return getCounterLong(player, - "PLAYTIME") >= n; - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "REPORT_VILLAGE") - >= needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - return getCounter( - player, "REPORT_VILLAGE") - >= 1; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return streak >= needed; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - // ===== 泛解析:击杀类中文条件 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - for (String mobType : mobTypes) { - total += getCounter(player, - "KILL:" + mobType); - } - return total >= needed; - } - } - - // ===== 泛解析:村民交易条件 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "TRADE") - >= needed; - } - - // ===== 泛解析:采集/挖矿类条件 ===== - if (key.contains("采集") || key.contains("挖")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - for (String mat : matTypes) { - total += getCounter(player, - "MINE:" + mat); - } - return total >= needed; - } - } - - // ===== 泛解析:合成类条件 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "CRAFT") - >= needed; - } - - // ===== 泛解析:钓鱼类条件 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "FISH") - >= needed; - } - - return false; - } - - public void debugLogin(String player) { - try { - var user = getDb().getUser(player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "=== " + player - + " 数据库全部字段 ==="); - for (var entry - : user.entrySet()) { - plugin.getLogger().info( - "[QuestTracker] " - + entry.getKey() - + " = " - + entry.getValue() - + " (" - + (entry.getValue() - != null - ? entry.getValue() - .getClass() - .getSimpleName() - : "null") - + ")"); - } - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "getUser返回null: " - + player); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "debug异常: " - + e.getMessage()); - } - } - - // ========== 内部:计数器 ========== - - private int getCounter( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - return 0; - } - private int getIntField( - String player, String field) { - try { - Object val = getDb().getField( - player, field); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - if (val != null) { - return Integer.parseInt( - val.toString().trim()); - } - } catch (Exception e) { - // 忽略 - } - return 0; - } - - public void onVillagerTrade(String player) { - String key = "VILLAGER_TRADE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - private long getCounterLong( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).longValue(); - } - return 0; - } - - private void setCounter( - String player, String key, - int value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - private void setCounter( - String player, String key, - long value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - // ========== 工具 ========== - - private boolean toBool(Object val) { - if (val == null) return false; - if (val instanceof Boolean) - return (Boolean) val; - if (val instanceof Number) - return ((Number) val) - .intValue() != 0; - String s = String.valueOf(val); - return "1".equals(s) - || "true" - .equalsIgnoreCase(s); - } - - public boolean isOptional(String cond) { - if (cond == null) return false; - String c = cond; - return c.contains("(进阶)") - || c.contains("(可选)") - || c.contains(",可选)") - || c.contains(",可选)") - || c.contains("【可选】") - || c.contains("【进阶】"); - } - - - private String stripOptional(String cond) { - if (cond == null) return ""; - return cond.replace("(进阶)", "") - .replace("(可选)", "") - .replace(",可选)", "") - .replace(",可选)", "") - .replace("【可选】", "") - .replace("【进阶】", "") - .trim(); - } - - private int parseInt(String s) { - - try { - return Integer.parseInt(s.trim()); - } catch (Exception e) { - return 0; - } - } - private Object getDbField( - String player, String field) { - Main main = (Main) plugin; - return main.getDb().getField( - player, field); - } - - /** - * 从文本中提取第一个数字 - * "在线时间≥1小时" → 1 - * "破坏/放置方块100个" → 100 - */ - private int parseNumberFromText( - String text) { - java.util.regex.Matcher m = - java.util.regex.Pattern - .compile("(\\d+)") - .matcher(text); - if (m.find()) { - try { - return Integer.parseInt( - m.group(1)); - } catch (Exception e) { - return 0; - } - } - return 0; - } - public void onBlockPlaced( - String player, String material) { - String key = "PLACE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - setCounter(player, "PLACE:ALL", - getCounter(player, "PLACE:ALL") - + 1); - } - - public void onPlayerChat( - String player) { - String key = "CHAT"; - setCounter(player, key, - getCounter(player, key) + 1); - } - - private String formatMs(long ms) { - long sec = ms / 1000; - long min = sec / 60; - long hr = min / 60; - if (hr > 0) - return hr + "小时" - + (min % 60) + "分"; - if (min > 0) - return min + "分" - + (sec % 60) + "秒"; - return sec + "秒"; - } - - // ========== 数据结构 ========== - - public static class QuestFile { - public String displayName = ""; - public String fileName = ""; - public List details = - new ArrayList<>(); - public List rewards = - new ArrayList<>(); - public List conditions = - new ArrayList<>(); - - public boolean hasConditions() { - return conditions != null - && !conditions.isEmpty(); - } - } -} diff --git a/backup/src/main/java/Sdf1_login/RadioDownloadListener.java b/backup/src/main/java/Sdf1_login/RadioDownloadListener.java deleted file mode 100644 index d3e744f..0000000 --- a/backup/src/main/java/Sdf1_login/RadioDownloadListener.java +++ /dev/null @@ -1,144 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player - .PlayerResourcePackStatusEvent; - -import java.util.UUID; -import java.util.concurrent - .ConcurrentHashMap; - -public class RadioDownloadListener - implements Listener { - - private final Main plugin; - private final java.util.logging.Logger log; - - private final ConcurrentHashMap - failCount = new ConcurrentHashMap<>(); - private final ConcurrentHashMap - blocked = new ConcurrentHashMap<>(); - - private static final int MAX_FAILED = 3; - - public RadioDownloadListener(Main plugin) { - this.plugin = plugin; - this.log = plugin.getLogger(); - } - - public void onPlayerQuit(UUID uuid) { - failCount.remove(uuid); - blocked.remove(uuid); - } - - @EventHandler - public void onStatus( - PlayerResourcePackStatusEvent e) { - Player p = e.getPlayer(); - UUID uuid = p.getUniqueId(); - String status = - e.getStatus().name(); - - log.info("[Radio] === 资源包状态 ==="); - log.info("[Radio] 玩家: " - + p.getName()); - log.info("[Radio] UUID: " + uuid); - log.info("[Radio] 状态: " - + status); - log.info("[Radio] 原始状态枚举: " - + e.getStatus().getClass() - .getSimpleName() - + "." - + e.getStatus().name()); - - if (blocked.containsKey(uuid)) { - log.info("[Radio] 已熔断,跳过: " - + p.getName()); - return; - } - - switch (status) { - case "SUCCESS": - log.info("[Radio] " - + p.getName() - + " ✓ 资源包加载成功"); - failCount.remove(uuid); - blocked.remove(uuid); - break; - - case "ACCEPTED": - log.info("[Radio] " - + p.getName() - + " 客户端已接受," - + "等待下载..."); - break; - - case "DECLINED": - log.warning("[Radio] " - + p.getName() - + " ✗ 拒绝了资源包"); - log.warning("[Radio] " - + "可能原因: " - + "1.客户端设置禁用资源包" - + " 2.已缓存拒绝记录" - + " 3.SSL证书不匹配"); - handleFailure(p, uuid); - break; - - case "FAILED_DOWNLOAD": - log.warning("[Radio] " - + p.getName() - + " ✗ 下载失败"); - log.warning("[Radio] " - + "可能原因: " - + "1.URL无法访问" - + " 2.SSL证书错误" - + " 3.网络超时" - + " 4.文件不存在(404)"); - handleFailure(p, uuid); - break; - - default: - log.info("[Radio] " - + p.getName() - + " 未知状态: " - + status); - break; - } - } - - private void handleFailure(Player p, UUID uuid) { - int failNum = failCount.getOrDefault(uuid, 0) + 1; - failCount.put(uuid, failNum); - - log.warning("[Radio] " + p.getName() - + " 失败次数: " + failNum + "/" + MAX_FAILED); - - if (failNum >= MAX_FAILED) { - blocked.put(uuid, true); - log.severe("[Radio] " + p.getName() - + " 已达最大失败次数,停止重试," - + "玩家继续留在服务器"); - // 不踢出,只记录熔断,玩家正常游玩 - p.sendMessage("§e§l[Radio] 资源包加载失败" - + ",已取消下载资源包,部分画面音频可能出现问题" - + "\n§d§l若想继续下载,请退出重进。若持续失败,请上报管理员"); - - return; - } -// 打了包没测试 - p.sendMessage("§e§l[Radio] 下载失败," - + "重试... (" + failNum + "/" + MAX_FAILED + ")"); - - // 5秒后重试 - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline() && plugin.radio != null) { - plugin.radio.sendResourcePack(p); - } - }, 100L); - } -} \ No newline at end of file diff --git a/backup/src/main/java/Sdf1_login/RadioManager.java b/backup/src/main/java/Sdf1_login/RadioManager.java deleted file mode 100644 index 6fe5eda..0000000 --- a/backup/src/main/java/Sdf1_login/RadioManager.java +++ /dev/null @@ -1,1040 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; - -import java.io.*; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.zip.*; -import java.util.Collections; -import org.bukkit.NamespacedKey; -import java.util.Set; - - -public class RadioManager { - - private final Main plugin; - private File radioDir; - private File oggDir; - private File packDir; - private BukkitTask mainRadioTask; - private final Map loginPlayers = - new ConcurrentHashMap<>(); - private String externalUrl = ""; - private int httpPort = 443; - private final Set pendingPack = - Collections.newSetFromMap( - new ConcurrentHashMap<>()); - private String lastPlayedSound = ""; - - private int radioIndex = 0; - - // ========== 登录阶段推送(管理员手动触发) ========== - public void startLoginRadio(Player p) { - loginPlayers.put(p.getName(), true); - } - - public void stopLoginRadio(Player p) { - loginPlayers.remove(p.getName()); - } - - public void stopAllLoginRadio() { - loginPlayers.clear(); - } - - private final List mainFiles = new ArrayList<>(); - private final List loginFiles = new ArrayList<>(); - - public RadioManager(Main plugin) { - this.plugin = plugin; - this.radioDir = new File( - plugin.getDataFolder(), "radio"); - this.oggDir = new File( - plugin.getDataFolder(), "radio/ogg"); - this.packDir = new File( - plugin.getDataFolder(), "radiopack"); - radioDir.mkdirs(); - oggDir.mkdirs(); - packDir.mkdirs(); - loadRadioConfig(); - } - // ========== 读取 radio_config.txt ========== - private void loadRadioConfig() { - File cfg = new File( - plugin.getDataFolder(), - "radio_config.txt"); - - // 不存在则创建默认配置 - if (!cfg.exists()) { - createRadioConfig(cfg); - plugin.getLogger().info( - "[Radio] 已生成 radio_config.txt"); - } - - // 读取 - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(cfg), - java.nio.charset - .StandardCharsets - .UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("#") - || line.trim().isEmpty()) - continue; - int eq = line.indexOf('='); - if (eq < 0) continue; - String key = line.substring(0, eq) - .trim(); - String val = line.substring(eq + 1) - .trim(); - if (key.equals("resource-pack-url")) { - externalUrl = val; - } else if (key.equals("http-port")) { - try { - httpPort = Integer.parseInt( - val); - } catch (NumberFormatException ignored) { - } - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 读取radio_config.txt失败: " - + e.getMessage()); - } - - // 443是HTTPS端口,内嵌HTTP不能用 - if (httpPort == 443) httpPort = 8080; - - plugin.getLogger().info( - "[Radio] 配置: externalUrl=[" - + externalUrl + "] httpPort=" - + httpPort); - } - - private void createRadioConfig(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - java.nio.charset - .StandardCharsets - .UTF_8))) { - pw.println("# ============ Sdf1 Radio Config ============"); - pw.println("#"); - pw.println("# resource-pack-url"); - pw.println("# 填外部地址(HTTP或HTTPS均可)"); - pw.println("# 例: https://example.com/radiopack.zip"); - pw.println("# 留空 = 使用内置HTTP服务"); - pw.println("#"); - pw.println("# http-port"); - pw.println("# 内置HTTP端口(仅resource-pack-url为空时生效)"); - pw.println("#"); - pw.println("# ============ Settings ==============="); - pw.println(); - pw.println("resource-pack-url="); - pw.println("http-port=8080"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 创建radio_config.txt失败: " - + e.getMessage()); - } - } - - // ========== 初始化 ========== - public void init() { - loadRadioConfig(); - convertNonOgg(); - loadAllOggs(); - buildResourcePack(); - startHttpServer(); // 启动HTTP - plugin.getLogger().info( - "[Radio] 初始化完成: 全服=" - + mainFiles.size() - + " 登录=" + loginFiles.size()); - } - - - // ========== 转换非ogg文件 ========== - private void convertNonOgg() { - convertDir(radioDir); - convertDir(new File(radioDir, "login")); - convertDir(new File(radioDir, "reg")); - } - - private void convertDir(File src) { - if (!src.exists() || !src.isDirectory()) return; - File[] files = src.listFiles((d, n) -> { - String l = n.toLowerCase(); - return l.endsWith(".mp3") - || l.endsWith(".wav") - || l.endsWith(".flac"); - }); - if (files == null) return; - - for (File f : files) { - String oggName = f.getName() - .replaceAll("\\.(mp3|wav|flac)$", - ".ogg"); - File oggFile = new File(src, oggName); - if (oggFile.exists() - && oggFile.lastModified() - >= f.lastModified()) { - continue; - } - // 调用ffmpeg转换 - if (convertWithFfmpeg(f, oggFile)) { - plugin.getLogger().info( - "[Radio] 转换成功: " - + f.getName()); - } else { - plugin.getLogger().warning( - "[Radio] 转换失败: " - + f.getName() - + " (需要安装ffmpeg)"); - } - } - } - - private boolean convertWithFfmpeg( - File input, File output) { - try { - ProcessBuilder pb = new ProcessBuilder( - "ffmpeg", "-y", - "-i", input.getAbsolutePath(), - "-c:a", "libvorbis", - "-q:a", "5", - output.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - boolean finished = - p.waitFor(30, TimeUnit.SECONDS); - if (!finished) { - p.destroyForcibly(); - return false; - } - return output.exists() - && output.length() > 0; - } catch (Exception e) { - return false; - } - } - - // ========== 加载ogg文件列表 ========== - private void loadAllOggs() { - mainFiles.clear(); - loginFiles.clear(); - loadOggsFlat(radioDir, mainFiles); - loadOggsFlat( - new File(radioDir, "login"), - loginFiles); - loadOggsFlat( - new File(radioDir, "reg"), - loginFiles); - } - - private void loadOggsFlat( - File dir, List list) { - if (!dir.exists() || !dir.isDirectory()) - return; - File[] files = dir.listFiles( - (d, n) -> n.toLowerCase() - .endsWith(".ogg")); - if (files == null) return; - Arrays.sort(files); - for (File f : files) list.add(f); - } - /** - * 解析 pack.png:优先读取插件目录, - * 没有则搜索图片并用ffmpeg转码 - */ - private File resolvePackIcon() { - File dataDir = plugin.getDataFolder(); - File packPng = new File(dataDir, "pack.png"); - - if (packPng.exists() - && packPng.length() > 0) { - plugin.getLogger().info( - "[Radio] 找到 pack.png (" - + packPng.length() - + " bytes)"); - return packPng; - } - - // 搜索可转换的图片文件 - String[] exts = {"png", "jpg", "jpeg", - "bmp", "webp", "tiff", "gif"}; - File[] found = dataDir.listFiles( - (dir, name) -> { - String low = name.toLowerCase(); - for (String ext : exts) { - if (low.endsWith("." + ext) - && !name.equals( - "pack.png")) { - return true; - } - } - return false; - }); - - if (found != null && found.length > 0) { - File src = found[0]; - plugin.getLogger().info( - "[Radio] 找到图片: " - + src.getName() - + " → 转码为 pack.png"); - if (convertToPackPng(src, packPng)) { - return packPng; - } - plugin.getLogger().warning( - "[Radio] 转码失败"); - return null; - } - - plugin.getLogger().warning( - "[Radio] 未找到 pack.png 或可转换图片"); - return null; - } - - /** - * ffmpeg 将任意图片转为 256x256 PNG - */ - private boolean convertToPackPng( - File input, File output) { - try { - ProcessBuilder pb = new ProcessBuilder( - "ffmpeg", "-y", - "-i", input.getAbsolutePath(), - "-vf", - "scale='min(256,iw)'" - + ":'min(256,ih)':" - + "force_original_aspect_ratio" - + "=decrease," - + "pad=256:256:" - + "(ow-iw)/2:" - + "(oh-ih)/2:" - + "color=0x00000000", - "-c:v", "png", - output.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - boolean ok = p.waitFor( - 30, TimeUnit.SECONDS); - if (!ok) { - p.destroyForcibly(); - return false; - } - return output.exists() - && output.length() > 0; - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] ffmpeg转码失败: " - + e.getMessage()); - return false; - } - } - - // ========== 资源包构建 ========== - private void buildResourcePack() { - deleteDir(packDir); - packDir.mkdirs(); - - List allOggs = new ArrayList<>(); - collectOggs(radioDir, allOggs); - - if (allOggs.isEmpty()) { - plugin.getLogger().warning( - "[Radio] radio/ 目录下" - + "无 .ogg 文件,跳过资源包"); - return; - } - - // 正确路径:assets/minecraft/sounds/custom/ - File soundsDir = new File(packDir, - "assets/minecraft/sounds/custom"); - soundsDir.mkdirs(); - - // 生成 sounds.json - StringBuilder json = - new StringBuilder("{\n"); - for (int i = 0; i < allOggs.size(); i++) { - File ogg = allOggs.get(i); - String simpleName = - ogg.getName() - .replace(".ogg", ""); - File dest = new File(soundsDir, - simpleName + ".ogg"); - try { - Files.copy(ogg.toPath(), - dest.toPath(), - StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - // ignore - } - // 音频事件名: custom.xxx - // 文件路径: custom/xxx - json.append(" \"custom.") - .append(simpleName) - .append("\": {\n"); - json.append(" \"sounds\": [{\n"); - json.append(" \"name\":" - + " \"custom/") - .append(simpleName) - .append("\",\n"); - json.append( - " \"stream\": true\n"); - json.append(" }]\n"); - json.append(" }"); - if (i < allOggs.size() - 1) - json.append(","); - json.append("\n"); - } - json.append("}"); - - // sounds.json 放在 assets/minecraft/ 下 - try { - Files.write(new File(packDir, - "assets/minecraft/sounds.json") - .toPath(), - json.toString() - .getBytes("UTF-8")); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] sounds.json写入失败"); - } -// sounds.json 写入后... - -// ===== 新增:打包 pack.png 作为资源包图标 ===== - File iconPng = resolvePackIcon(); - if (iconPng != null && iconPng.exists()) { - try { - Files.copy(iconPng.toPath(), - new File(packDir, "pack.png") - .toPath(), - StandardCopyOption - .REPLACE_EXISTING); - plugin.getLogger().info( - "[Radio] pack.png 已加入资源包"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] pack.png复制失败: " - + e.getMessage()); - } - } - - // pack.mcmeta(pack_format=54) - try { - Files.write(new File(packDir, - "pack.mcmeta").toPath(), - ("{\n \"pack\": {\n" - + " \"pack_format\": 54,\n" - + " \"description\":" - + " \"§d§l草原探险服务器资源包,请勿拒绝资源包\"\n }\n}") - .getBytes("UTF-8")); - } catch (Exception e) { - // ignore - } - - try { - File zipFile = new File( - plugin.getDataFolder(), - "radiopack.zip"); - packZip(packDir, zipFile); - plugin.getLogger().info( - "[Radio] 资源包生成: " - + allOggs.size() + " 个音频"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] zip打包失败: " - + e.getMessage()); - } - } - - private void deleteDir(File dir) { - if (!dir.exists()) return; - File[] files = dir.listFiles(); - if (files != null) { - for (File f : files) { - if (f.isDirectory()) deleteDir(f); - else f.delete(); - } - } - dir.delete(); - } - - private void collectOggs( - File dir, List list) { - File[] files = dir.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) - collectOggs(f, list); - else if (f.getName().endsWith(".ogg")) - list.add(f); - } - } - - private void packZip(File root, - File zipFile) throws Exception { - try (ZipOutputStream zos = - new ZipOutputStream( - new FileOutputStream( - zipFile))) { - zipDir(root, root, zos); - } - } - - private void zipDir(File root, - File current, - ZipOutputStream zos) - throws Exception { - File[] files = current.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) { - zipDir(root, f, zos); - } else { - String path = root.toURI() - .relativize(f.toURI()) - .getPath(); - zos.putNextEntry(new ZipEntry(path)); - zos.write(Files.readAllBytes( - f.toPath())); - zos.closeEntry(); - } - } - } - - // ========== 资源包发送 ========== - - /** - * 发送资源包(带限流,不禁重试) - */ - private final Map lastSendTime = - new ConcurrentHashMap<>(); - - public void sendResourcePack(Player p) { - UUID uuid = p.getUniqueId(); - - // 最短间隔 5秒(不是10秒的全锁死) - Long last = lastSendTime.get(uuid); - if (last != null - && System.currentTimeMillis() - last - < 5000L) { - plugin.getLogger().info( - "[Radio] 跳过(5秒内): " - + p.getName()); - return; - } - lastSendTime.put(uuid, - System.currentTimeMillis()); - - String url = getResourcePackUrl(); - if (url == null || url.isEmpty()) { - plugin.getLogger().warning( - "[Radio] URL为空,跳过: " - + p.getName()); - return; - } - - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) { - plugin.getLogger().warning( - "[Radio] radiopack.zip不存在," - + "跳过: " + p.getName()); - return; - } - - try { - byte[] data = - Files.readAllBytes(zip.toPath()); - String hash = sha1(data); - long fileSize = zip.length(); - - plugin.getLogger().info( - "[Radio] 发送: " + p.getName() - + " url=" + url - + " size=" + fileSize - + " hash=" + hash); - - sendPackWithHash(p, url, hash); - - } catch (Exception e) { - plugin.getLogger().severe( - "[Radio] 发送异常: " - + p.getName()); - e.printStackTrace(); - } - } - - - - private void sendPackWithHash( - Player p, - String url, - String hash) { - - UUID uuid = UUID.fromString( - "a1b2c3d4-e5f6-7890-abcd-" - + "ef1234567890"); - - // 尝试5参 + Component - try { - Object component = - net.kyori.adventure.text - .Component.text( - "§d§l请下载自定义资源包"); - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class, - net.kyori.adventure - .text.Component - .class, - boolean.class) - .invoke( - p, uuid, url, - hash, component, - true); - plugin.getLogger().info( - "[Radio] 成功(5参): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // 尝试5参 + String描述 - try { - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class, - String.class, - boolean.class) - .invoke( - p, uuid, url, - hash, - "Sdf1 Radio", - true); - plugin.getLogger().info( - "[Radio] 成功(5参String): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // 尝试3参 - try { - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class) - .invoke( - p, uuid, url, - hash); - plugin.getLogger().info( - "[Radio] 成功(3参): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // ★ 全部失败 → 通知玩家 + 熔断 - plugin.getLogger().severe( - "[Radio] 所有签名失败: " - + p.getName() - + " 客户端版本: " - + p.getProtocolVersion()); - p.sendMessage( - "§e§l[Radio] §f资源包发送失败," - + "§7部分功能可能异常"); - p.sendMessage( - "§7可尝试: §e/rejoin §7或重启客户端"); - } - - - - private void sendPackFromLocal( - Player p, String url) { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) return; - try { - byte[] data = - Files.readAllBytes( - zip.toPath()); - String hash = sha1(data); - sendPackWithHash(p, url, hash); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 降级发送失败"); - } - } - - // 从URL下载字节 - private byte[] downloadBytes(String urlStr) - throws Exception { - java.net.URL url = - new java.net.URL(urlStr); - java.net.HttpURLConnection conn = - (java.net.HttpURLConnection) - url.openConnection(); - conn.setConnectTimeout(10000); - conn.setReadTimeout(30000); - try (java.io.InputStream is = - conn.getInputStream()) { - return is.readAllBytes(); - } - } - - // SHA-1计算 - private String sha1(byte[] data) - throws Exception { - java.security.MessageDigest md = - java.security.MessageDigest - .getInstance("SHA-1"); - md.update(data); - byte[] hash = md.digest(); - StringBuilder sb = - new StringBuilder(); - for (byte b : hash) - sb.append(String.format( - "%02x", b)); - return sb.toString(); - } - - public void clearPending(UUID uuid) { - lastSendTime.remove(uuid); - } - - - - // ========== 内嵌HTTP服务 ========== - private com.sun.net.httpserver.HttpServer - httpServer; - - private void startHttpServer() { - // 如果配置了外部地址则不启动 - if (plugin.getConfig2() != null - && !plugin.getConfig2() - .broadcastServerUrl - .isEmpty()) { - return; - } - try { - int port = 8080; - if (plugin.getConfig2() != null) { - port = plugin.getConfig2().httpPort; - } - httpServer = - com.sun.net.httpserver - .HttpServer.create( - new java.net - .InetSocketAddress( - port), 0); - httpServer.createContext( - "/radiopack.zip", - exchange -> { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (zip.exists()) { - byte[] data = - Files.readAllBytes( - zip.toPath()); - exchange.getResponseHeaders() - .set("Content-Type", - "application/zip"); - exchange.getResponseHeaders() - .set("Access-Control" - + "-Allow-Origin", - "*"); - exchange.sendResponseHeaders( - 200, data.length); - exchange.getResponseBody() - .write(data); - exchange.getResponseBody() - .close(); - } else { - byte[] empty = - "not found".getBytes(); - exchange.sendResponseHeaders( - 404, empty.length); - exchange.getResponseBody() - .write(empty); - exchange.getResponseBody() - .close(); - } - }); - httpServer.setExecutor(null); - httpServer.start(); - plugin.getLogger().info( - "[Radio] HTTP服务: http://0.0.0.0:" - + port + "/radiopack.zip"); - } catch (Exception e) { - plugin.getLogger().severe( - "[Radio] HTTP启动失败: " - + e.getMessage()); - } - } - - private void stopHttpServer() { - if (httpServer != null) { - httpServer.stop(0); - httpServer = null; - } - } - - public String getResourcePackUrl() { - // 有外部配置 → 随机生成URL - if (externalUrl != null - && !externalUrl.trim().isEmpty()) { - return generateRandomUrl(externalUrl); - } - // 无配置 → 用内置HTTP - int port = 8080; - if (plugin.getConfig2() != null) { - port = plugin.getConfig2().httpPort; - } - return "http://127.0.0.1:" - + port + "/radiopack.zip"; - } - - - /** - * 从配置的域名列表中随机选一个 - * 生成随机、带时间戳的完整资源包URL - * - * 规则: - * *.example.com - * → 随机子域+时间戳 - * 例: a3f7k2.example.com/pack.zip?t=xxx&v=b9c1e4 - * - * cdn.example.com (无*) - * → 直接使用+时间戳 - * 例: cdn.example.com/pack.zip?t=xxx - */ - private String generateRandomUrl( - String raw) { - String[] parts = raw.split("\\|"); - String pick = - parts[(int)(Math.random() - * parts.length)].trim(); - - // 去掉协议 https:// http:// - String cleaned = pick - .replaceFirst("^https?://", ""); - // 去掉路径 /path/a?v=1 - if (cleaned.contains("/")) { - cleaned = cleaned.substring( - 0, cleaned.indexOf('/')); - } - - // 随机子域 - String hex = Long.toHexString( - Double.doubleToLongBits( - Math.random())); - String rand = hex.length() > 6 - ? hex.substring(2, 8) : hex; - rand = rand.replaceAll( - "[^a-z0-9]", "x"); - - String host; - if (cleaned.startsWith("*")) { - host = rand - + cleaned.substring(1); - } else { - host = cleaned; - } - - String ts = String.valueOf( - System.currentTimeMillis()); - String token = Integer.toHexString( - (int)(Math.random() - * 0xFFFFFF)); - - String url = "https://" + host - + "/radiopack.zip" - + "?t=" + ts - + "&v=" + token; - - plugin.getLogger().info( - "[Radio] 生成URL: " - + pick + " → " + url); - return url; - } - - - - // ========== 全服广播 ========== - public void startMainRadio() { - stopMainRadio(); - if (mainFiles.isEmpty()) { - plugin.getLogger().warning( - "[Radio] 无 .ogg 文件"); - return; - } - plugin.getLogger().info( - "[Radio] 启动全服广播 (" - + mainFiles.size() + " 个文件)"); - mainRadioTask = new BukkitRunnable() { - @Override - public void run() { - playNext(); - } - }.runTaskTimer(plugin, 100L, 2400L); - } - - private void playNext() { - if (mainFiles.isEmpty()) return; - File file = mainFiles.get( - radioIndex % mainFiles.size()); - String name = file.getName() - .replace(".ogg", ""); - lastPlayedSound = "custom." + name; - String key = "minecraft:" + lastPlayedSound; - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" + name); - radioIndex++; - } - - // ========== 按名称播放 ========== - public void playToAll(String name) { - String key = findSongKey(name); - if (key == null) return; - lastPlayedSound = "custom." + name; - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" + name); - } - - - public void playToPlayer( - Player p, String name) { - String key = findSongKey(name); - if (key == null) { - p.sendMessage("§c未找到: " + name); - return; - } - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - - public void playToLoginPlayers(String name) { - String key = findSongKey(name); - if (key == null) return; - for (String pName - : loginPlayers.keySet()) { - Player lp = Bukkit.getPlayer(pName); - if (lp != null && lp.isOnline()) { - lp.playSound(lp.getLocation(), - key, - SoundCategory.RECORDS, - 1.5f, 1.0f); - } - } - } - - - private String findSongKey(String name) { - List all = new ArrayList<>(); - collectOggs(radioDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - return "minecraft:custom." + name; - } - } - plugin.getLogger().warning( - "[Radio] 未找到歌曲: " + name); - return null; - } - - - public boolean hasSong(String name) { - return findSongKey(name) != null; - } - - // ========== 手动播放 ========== - public boolean playSpecific( - Player sender, String name) { - List all = new ArrayList<>(); - collectOggs(radioDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - String key = "sdf1radio:" + name; - for (Player p - : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), - key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" - + name); - return true; - } - } - if (sender != null) - sender.sendMessage("§c未找到: " + name); - return false; - } - public void stopMainRadio() { - if (mainRadioTask != null) { - mainRadioTask.cancel(); - mainRadioTask = null; - } - if (lastPlayedSound != null - && !lastPlayedSound.isEmpty()) { - for (Player p : Bukkit.getOnlinePlayers()) { - p.stopSound(lastPlayedSound, - SoundCategory.RECORDS); - } - lastPlayedSound = ""; - } - } - -// 在 init() 或 registerEvents 中保存引用: -// this.listener = new RadioDownloadListener(plugin); -private RadioDownloadListener radioListener; - - - - // ========== 重载 ========== - public void reload() { - stopMainRadio(); - stopAllLoginRadio(); - stopHttpServer(); - loadAllOggs(); - buildResourcePack(); - startHttpServer(); - loadRadioConfig(); - } -} \ No newline at end of file diff --git a/backup/src/main/java/Sdf1_login/RefundRecord.java b/backup/src/main/java/Sdf1_login/RefundRecord.java deleted file mode 100644 index c0a3070..0000000 --- a/backup/src/main/java/Sdf1_login/RefundRecord.java +++ /dev/null @@ -1,62 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; -import java.util.UUID; - -public class RefundRecord { - public final UUID playerUUID; - public final String playerName; - public final String itemId; - public final String itemName; - public final Material material; - public final Material shulkerMaterial; - public int amount; - public int totalPaid; - public final long timestamp; - - // 普通购买 - public RefundRecord(UUID uuid, String name, - String itemId, String itemName, - Material material, int amount, - int totalPaid, long timestamp) { - this.playerUUID = uuid; - this.playerName = name; - this.itemId = itemId; - this.itemName = itemName; - this.material = material; - this.shulkerMaterial = null; - this.amount = amount; - this.totalPaid = totalPaid; - this.timestamp = timestamp; - } - - // 盒装购买 - public RefundRecord(UUID uuid, String name, - String itemId, String itemName, - Material material, - Material shulkerMat, - int amount, int totalPaid, - long timestamp) { - this.playerUUID = uuid; - this.playerName = name; - this.itemId = itemId; - this.itemName = itemName; - this.material = material; - this.shulkerMaterial = shulkerMat; - this.amount = amount; - this.totalPaid = totalPaid; - this.timestamp = timestamp; - } - - public boolean isExpired() { - return System.currentTimeMillis() - - timestamp > 300000L; - } - - public int getSecondsLeft() { - long left = 300000L - - (System.currentTimeMillis() - - timestamp); - return (int) Math.max(0, left / 1000); - } -} diff --git a/backup/src/main/java/Sdf1_login/RiskControlManager.java b/backup/src/main/java/Sdf1_login/RiskControlManager.java deleted file mode 100644 index 4ddedcc..0000000 --- a/backup/src/main/java/Sdf1_login/RiskControlManager.java +++ /dev/null @@ -1,248 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 风控管理器:检测异常登录行为,记录尝试次数,触发安全报警 - */ -public class RiskControlManager { - - private final Main plugin; - - // 每个玩家名 -> 连续失败次数 - private final Map failAttempts = - new ConcurrentHashMap<>(); - - // 每个IP -> 关联的可疑玩家列表 - private final Map> ipSuspicious = - new ConcurrentHashMap<>(); - - // 每个玩家 -> 上次验证时间(用于冷却) - private final Map verifyCooldown = - new ConcurrentHashMap<>(); - - // 配置项 - private static final int MAX_FAIL_ATTEMPTS = 5; - private static final int MAX_ACCOUNTS_PER_IP = 5; - private static final long COOLDOWN_MS = 60000L; - - public RiskControlManager(Main plugin) { - this.plugin = plugin; - } - - /** - * 记录一次失败的登录尝试 - * - * @param playerName 玩家名 - * @return 剩余尝试次数 - */ - public int recordFailAttempt(String playerName) { - int current = failAttempts - .getOrDefault(playerName, 0) + 1; - failAttempts.put(playerName, current); - return Math.max(0, MAX_FAIL_ATTEMPTS - current); - } - - /** - * 重置指定玩家的失败计数(登录成功时调用) - */ - public void resetFailAttempts(String playerName) { - failAttempts.remove(playerName); - } - - /** - * 获取指定玩家的失败次数 - */ - public int getFailAttempts(String playerName) { - return failAttempts - .getOrDefault(playerName, 0); - } - - /** - * 判断玩家是否已被锁定(失败次数达到上限) - */ - public boolean isLocked(String playerName) { - return failAttempts - .getOrDefault(playerName, 0) - >= MAX_FAIL_ATTEMPTS; - } - - /** - * 检查IP是否达到注册上限 - * 直接查数据库,不依赖 DatabaseManager 的方法 - */ - public boolean canRegisterFromIP(String ip) { - if (ip == null || ip.isEmpty()) return true; - try { - java.sql.Connection conn = - plugin.getDb().getDb(); - if (conn == null) return true; - java.sql.PreparedStatement ps = - conn.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address = ?"); - ps.setString(1, ip); - java.sql.ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count < 5; - } catch (Exception e) { - e.printStackTrace(); - return true; - } - } - - - /** - * 记录IP关联的可疑行为 - */ - public void recordSuspiciousIP(String ip, - String playerName) { - if (ip == null || ip.isEmpty()) return; - ipSuspicious - .computeIfAbsent(ip, - k -> ConcurrentHashMap.newKeySet()) - .add(playerName); - } - - /** - * 获取某IP关联的所有可疑玩家 - */ - public Set getSuspiciousAccounts(String ip) { - return ipSuspicious - .getOrDefault(ip, Collections.emptySet()); - } - - /** - * 检查是否在验证冷却期内 - */ - public boolean isOnCooldown(String playerName) { - Long last = verifyCooldown.get(playerName); - if (last == null) return false; - return System.currentTimeMillis() - last - < COOLDOWN_MS; - } - - /** - * 设置验证冷却时间 - */ - public void setCooldown(String playerName) { - verifyCooldown.put(playerName, - System.currentTimeMillis()); - } - - /** - * 当检测到可疑登录时触发报警 - * - 全服广播 - * - 有邮箱则发邮件 - * - 记录到数据库 - */ - public void triggerAlert(Player player, - String suspiciousIP) { - String name = player.getName(); - - // 记录到数据库 - plugin.getDb().recordSecurityAlert( - name, suspiciousIP); - - // 记录IP可疑行为 - recordSuspiciousIP(suspiciousIP, name); - - // 全服广播 - String serverMsg = "§c§l[安全警告] §e" + name - + " §f的账号疑似遭到 §c" + suspiciousIP - + " §f的盗号尝试, 请各位玩家注意账号安全!"; - Bukkit.broadcastMessage(serverMsg); - - // 有邮箱则发邮件 - String emailAddr = (String) plugin.getDb() - .getField(name, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String subject = - "[安全警告] 您的账号疑似遭到盗号尝试"; - String body = "尊敬的玩家 " + name - + ":\n\n" - + "您的账号在 " - + new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()) - + " 疑似遭到 IP: " + suspiciousIP - + " 的盗号尝试。\n\n" - + "建议您:\n" - + "1. 立即修改密码" - + "(使用 /sdf1_login pw)\n" - + "2. 如非本人操作," - + "请联系管理员处理\n\n" - + "--Sdf1_login 安全系统"; - plugin.getEmail().sendTempPassword( - emailAddr, name, body); - } - } - - /** - * 处理登录失败时的风控逻辑 - * - * @param player 玩家 - * @param suspiciousIP 可疑来源IP - * @return true=已被锁定(应踢出), false=仍可继续尝试 - */ - public boolean handleLoginFailure(Player player, - String suspiciousIP) { - String name = player.getName(); - int remaining = recordFailAttempt(name); - - if (remaining <= 0) { - // 达到上限,触发报警 - triggerAlert(player, suspiciousIP); - player.kickPlayer( - "§c§l[风控] §f登录失败次数过多," - + " 账号已被临时锁定, 请联系管理员"); - return true; - } - - // 未达上限,提示剩余次数 - player.sendMessage( - "§c§l[风控] §f密码错误! " - + "剩余" + remaining + "次机会"); - return false; - } - - /** - * 登录成功后重置风控状态 - */ - public void onLoginSuccess(String playerName) { - resetFailAttempts(playerName); - } - - /** - * 清理过期的冷却记录(可由定时任务调用) - */ - public void cleanup() { - long now = System.currentTimeMillis(); - verifyCooldown.entrySet().removeIf( - e -> now - e.getValue() > COOLDOWN_MS * 10); - } - - /** - * 获取所有玩家的失败尝试记录(调试用) - */ - public Map getAllFailAttempts() { - return Collections.unmodifiableMap(failAttempts); - } - - /** - * 清除所有风控数据(管理员调试用) - */ - public void clearAll() { - failAttempts.clear(); - ipSuspicious.clear(); - verifyCooldown.clear(); - } -} diff --git a/backup/src/main/java/Sdf1_login/SalesStatsManager.java b/backup/src/main/java/Sdf1_login/SalesStatsManager.java deleted file mode 100644 index 9d06a22..0000000 --- a/backup/src/main/java/Sdf1_login/SalesStatsManager.java +++ /dev/null @@ -1,170 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class SalesStatsManager { - - private final Main plugin; - - private final Map sessionSales = - new ConcurrentHashMap<>(); - private int sessionRevenue = 0; - private int sessionOrders = 0; - private int sessionItems = 0; - private int sessionRefunds = 0; - private int sessionRefundAmount = 0; - private long sessionStart = System.currentTimeMillis(); - - // 状态 - private boolean waitingForEmpty = false; - private boolean hasReported = false; - private BukkitRunnable waitTask = null; - - public SalesStatsManager(Main plugin) { - this.plugin = plugin; - startMonitor(); - } - - // ★ 每10秒检测一次 - private void startMonitor() { - new BukkitRunnable() { - @Override - public void run() { - int online = Bukkit.getOnlinePlayers().size(); - - if (online > 0) { - // 有玩家在线 → 重置等待状态 - if (waitingForEmpty) { - // 刚有人进来了,清除已汇报标记 - // 下次没人时会重新汇报 - } - waitingForEmpty = false; - hasReported = false; - cancelWaitTask(); - } else if (!waitingForEmpty && !hasReported) { - // 没人了,且还没汇报过 → 开始1分钟倒计时 - waitingForEmpty = true; - startWaitCountdown(); - } - } - }.runTaskTimer(plugin, 200L, 200L); - } - - // ★ 1分钟等待 - private void startWaitCountdown() { - cancelWaitTask(); - waitTask = new BukkitRunnable() { - @Override - public void run() { - int online = Bukkit.getOnlinePlayers().size(); - - if (online > 0) { - // 等待期间有人进来 → 取消,等下次没人 - waitingForEmpty = false; - hasReported = false; - waitTask = null; - cancel(); - return; - } - - // 1分钟没人 → 汇报一次 - if (!hasReported) { - hasReported = true; - report(); - } - waitingForEmpty = false; - waitTask = null; - cancel(); - } - }; - waitTask.runTaskLater(plugin, 1200L); // 60秒 - } - - private void cancelWaitTask() { - if (waitTask != null) { - waitTask.cancel(); - waitTask = null; - } - } - - // ===== 记录 ===== - - public void recordSale(String itemName, int price) { - sessionSales.merge(itemName, 1, Integer::sum); - sessionRevenue += price; - sessionOrders++; - sessionItems++; - } - - public void recordRefund(int amount) { - sessionRefunds++; - sessionRefundAmount += amount; - } - - // ===== 汇报 ===== - - public void report() { - if (sessionItems == 0 && sessionRefunds == 0) { - // plugin.getLogger().info("[Sales] 无数据,跳过"); - return; - } - - long minutes = - (System.currentTimeMillis() - sessionStart) - / 60000; - if (minutes < 1) minutes = 1; - - StringBuilder sb = new StringBuilder(); - sb.append("§6§l========== 销量汇总 =========="); - sb.append("\n§7时长: §f").append(minutes).append("分钟"); - sb.append("\n§7订单: §f").append(sessionOrders).append("笔"); - sb.append("\n§7商品: §f").append(sessionItems).append("件"); - sb.append("\n§7成交额: §6").append(sessionRevenue).append("枚"); - sb.append("\n§7退款: §c").append(sessionRefunds) - .append("次(§c").append(sessionRefundAmount) - .append("枚§7)"); - sb.append("\n§6§l------------------------------"); - - if (!sessionSales.isEmpty()) { - sb.append("\n§7销量排行:"); - List> sorted = - new ArrayList<>(sessionSales.entrySet()); - sorted.sort((a, b) -> - b.getValue() - a.getValue()); - int rank = 1; - for (Map.Entry e : sorted) { - if (rank > 10) break; - sb.append("\n§e").append(rank).append(". ") - .append(e.getKey()) - .append(" §7x").append(e.getValue()); - rank++; - } - } - sb.append("\n§6§l=============================="); - - String msg = sb.toString(); - for (Player p : Bukkit.getOnlinePlayers()) { - if (p.isOp()) { - for (String line : msg.split("\n")) - p.sendMessage(line); - } - } - plugin.getLogger().info("[Sales] 汇报完成"); - resetSession(); - } - - public void resetSession() { - sessionSales.clear(); - sessionRevenue = 0; - sessionOrders = 0; - sessionItems = 0; - sessionRefunds = 0; - sessionRefundAmount = 0; - sessionStart = System.currentTimeMillis(); - } -} diff --git a/backup/src/main/java/Sdf1_login/ShopCategory.java b/backup/src/main/java/Sdf1_login/ShopCategory.java deleted file mode 100644 index 715e9ad..0000000 --- a/backup/src/main/java/Sdf1_login/ShopCategory.java +++ /dev/null @@ -1,32 +0,0 @@ -package Sdf1_login; - -import java.util.ArrayList; -import java.util.List; - -public class ShopCategory { - private final String name; - private final String fileName; - private final List items; - - public ShopCategory(String name, String fileName) { - this.name = name; - this.fileName = fileName; - this.items = new ArrayList<>(); - } - - public String getName() { return name; } - public String getFileName() { return fileName; } - public List getItems() { return items; } - - public ShopItem getItem(String id) { - for (ShopItem item : items) { - if (item.getId().equalsIgnoreCase(id)) - return item; - } - return null; - } - - public void addItem(ShopItem item) { - items.add(item); - } -} diff --git a/backup/src/main/java/Sdf1_login/ShopItem.java b/backup/src/main/java/Sdf1_login/ShopItem.java deleted file mode 100644 index d336c7c..0000000 --- a/backup/src/main/java/Sdf1_login/ShopItem.java +++ /dev/null @@ -1,49 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; - -public class ShopItem { - private final String id; - private final String displayName; - private final Material material; - private final int buyPrice; - private final int sellPrice; - private int stock; - private int hourlySales; - private int totalSales; - public ShopItem(String id, String displayName, - Material material, int buyPrice, - int sellPrice, int stock, - int hourlySales, int totalSales) { - this.id = id; - this.displayName = displayName; - this.material = material; - this.buyPrice = buyPrice; - this.sellPrice = sellPrice; - this.stock = stock; - this.hourlySales = hourlySales; - this.totalSales = totalSales; - } - - public int getEffectiveSellPrice() { - if (sellPrice <= 0) return 0; - return sellPrice; - } - - public String getId() { return id; } - public String getDisplayName() { return displayName; } - public Material getMaterial() { return material; } - public int getBuyPrice() { return buyPrice; } - public int getSellPrice() { return sellPrice; } - public int getStock() { return stock; } - public int getHourlySales() { return hourlySales; } - public int getTotalSales() { return totalSales; } - - public void setStock(int s) { this.stock = s; } - public void setHourlySales(int s) { this.hourlySales = s; } - public void setTotalSales(int s) { this.totalSales = s; } - public void addSales(int n) { - this.hourlySales += n; - this.totalSales += n; - } -} diff --git a/backup/src/main/java/Sdf1_login/ShopManager.java b/backup/src/main/java/Sdf1_login/ShopManager.java deleted file mode 100644 index cdcc045..0000000 --- a/backup/src/main/java/Sdf1_login/ShopManager.java +++ /dev/null @@ -1,2610 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.Base64; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.inventory.meta.PotionMeta; - - - - -public class ShopManager implements Listener { - - private final Main plugin; - private final List categories = - new ArrayList<>(); - private final List refundRecords = - new ArrayList<>(); - private final Map activeDiscount = - new HashMap<>(); - private final Map viewCat = - new HashMap<>(); - private final Map viewPage = - new HashMap<>(); - private final Map> shoppingCarts = - new HashMap<>(); - private final Set cartModeEnabled = - new HashSet<>(); - private final java.util.Random rng = - new java.util.Random(); - private final Map> pendingCheckout = - new HashMap<>(); - private final Set couponListening = - new HashSet<>(); - private final Map pendingCouponCode = - new HashMap<>(); - // ===== 物品NBT数据 ===== - private final Map itemNbtData = - new HashMap<>(); - private final Map itemStackCache = - new HashMap<>(); - // 替换字段 - private final Map couponApplyTime = new HashMap<>(); - - - - private static final Material[] SHULKER_COLORS = { - Material.SHULKER_BOX, - Material.WHITE_SHULKER_BOX, - Material.ORANGE_SHULKER_BOX, - Material.MAGENTA_SHULKER_BOX, - Material.LIGHT_BLUE_SHULKER_BOX, - Material.YELLOW_SHULKER_BOX, - Material.LIME_SHULKER_BOX, - Material.PINK_SHULKER_BOX, - Material.GRAY_SHULKER_BOX, - Material.LIGHT_GRAY_SHULKER_BOX, - Material.CYAN_SHULKER_BOX, - Material.PURPLE_SHULKER_BOX, - Material.BLUE_SHULKER_BOX, - Material.BROWN_SHULKER_BOX, - Material.GREEN_SHULKER_BOX, - Material.RED_SHULKER_BOX, - Material.BLACK_SHULKER_BOX - }; - private static final Map EFFECT_MAP = new HashMap<>(); - static { - // ===== 基础效果 ===== - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("速度", PotionEffectType.SPEED); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); // 1.21+ 改名 - EFFECT_MAP.put("治疗", PotionEffectType.INSTANT_HEALTH); // 1.21+ 改名 - EFFECT_MAP.put("治愈", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("伤害", PotionEffectType.INSTANT_DAMAGE); // 1.21+ 改名 - EFFECT_MAP.put("剧毒", PotionEffectType.POISON); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("回复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("再生", PotionEffectType.REGENERATION); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性", PotionEffectType.RESISTANCE); // 1.21+ 改名 - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); // 1.21+ 改名 - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); // 1.21+ 改名 - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); // ★ 补充 - EFFECT_MAP.put("迟缓", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("不祥", PotionEffectType.UNLUCK); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("生命加成", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("海豚关怀", PotionEffectType.DOLPHINS_GRACE); - EFFECT_MAP.put("跳跃", PotionEffectType.JUMP_BOOST); - // ★ 神龟药水是双效果,特殊处理,这里只做名称匹配入口 - EFFECT_MAP.put("神龟", PotionEffectType.RESISTANCE); - } - - public static class CartItem { - String itemId; - String displayName; - Material material; - int unitPrice; - int quantity; - } - - - private static final int PAGE_SIZE = 28; - private static final int[] ITEM_SLOTS = { - 10,11,12,13,14,15,16, - 19,20,21,22,23,24,25, - 28,29,30,31,32,33,34, - 37,38,39,40,41,42,43 - }; - - public ShopManager(Main plugin) { - this.plugin = plugin; - loadCategories(); - startHourlyTask(); - loadItemNbtData(); - startCleanupTask(); - } - // ===== 切换购物车模式 ===== - // ===== 切换购物车模式 ===== - public boolean toggleCartMode(Player p) { - UUID uuid = p.getUniqueId(); - if (cartModeEnabled.contains(uuid)) { - cartModeEnabled.remove(uuid); - p.sendMessage("§7购物车模式已关闭,恢复正常购买"); - return false; - } else { - cartModeEnabled.add(uuid); - p.sendMessage("§a购物车模式已开启,点击商品加入购物车"); - return true; - } - } - - public boolean isCartMode(Player p) { - return cartModeEnabled.contains(p.getUniqueId()); - } - private String itemToBase64(ItemStack item) { - try { - ByteArrayOutputStream os = - new ByteArrayOutputStream(); - BukkitObjectOutputStream bos = - new BukkitObjectOutputStream(os); - bos.writeObject(item); - bos.close(); - return Base64.getEncoder() - .encodeToString(os.toByteArray()); - } catch (Exception e) { - return ""; - } - } - - private ItemStack base64ToItem(String b64) { - try { - byte[] data = - Base64.getDecoder().decode(b64); - ByteArrayInputStream is = - new ByteArrayInputStream(data); - BukkitObjectInputStream bis = - new BukkitObjectInputStream(is); - ItemStack item = - (ItemStack) bis.readObject(); - bis.close(); - return item; - } catch (Exception e) { - return null; - } - } - - private void loadItemNbtData() { - itemNbtData.clear(); - itemStackCache.clear(); - File f = new File(plugin.getDataFolder(), - "shop/item_nbt.properties"); - if (!f.exists()) return; - try { - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() - || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq < 0) continue; - String id = - line.substring(0, eq).trim(); - String b64 = - line.substring(eq + 1).trim(); - itemNbtData.put(id, b64); - ItemStack stack = base64ToItem(b64); - if (stack != null) { - itemStackCache.put(id, stack); - } - } - br.close(); - // plugin.getLogger().info("[Shop] NBT数据: " - // + itemStackCache.size() + "条"); - } catch (Exception ignored) {} - } - - private void saveItemNbtData() { - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - File f = new File(dir, "item_nbt.properties"); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 商品完整数据(自动生成)"); - for (Map.Entry e - : itemNbtData.entrySet()) { - pw.println(e.getKey() + "=" - + e.getValue()); - } - pw.flush(); - pw.close(); - } catch (Exception ignored) {} - } - - // ===== 加入/移除购物车 ===== - public boolean addToCart(Player p, ShopItem item) { - UUID uuid = p.getUniqueId(); - List cart = shoppingCarts - .computeIfAbsent( - uuid, k -> new ArrayList<>()); - // ★ 始终用原价,折扣在结算时统一应用 - int price = item.getBuyPrice(); - for (CartItem ci : cart) { - if (ci.itemId.equals(item.getId())) { - ci.quantity += 1; - p.sendMessage("§a+1 §e" - + item.getDisplayName() - + " §7购物车共§f" - + getCartCount(p) - + "件 §6需" - + getCartTotal(p) + "枚"); - return true; - } - } - CartItem ci = new CartItem(); - ci.itemId = item.getId(); - ci.displayName = item.getDisplayName(); - ci.material = item.getMaterial(); - ci.unitPrice = price; - ci.quantity = 1; - cart.add(ci); - p.sendMessage("§a+1 §e" - + item.getDisplayName() - + " §7购物车共§f" - + getCartCount(p) - + "件 §6需" - + getCartTotal(p) + "枚"); - return true; - } - - - public int getCartCount(Player p) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null) return 0; - int c = 0; - for (CartItem ci : cart) c += ci.quantity; - return c; - } - - public int getCartTotal(Player p) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null) return 0; - int t = 0; - for (CartItem ci : cart) - t += ci.unitPrice * ci.quantity; - return t; - } - - public void cartAddOne(Player p, int index) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null || index < 0 - || index >= cart.size()) return; - cart.get(index).quantity += 1; - } - - public void cartRemoveOne(Player p, int index) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null || index < 0 - || index >= cart.size()) return; - CartItem ci = cart.get(index); - ci.quantity -= 1; - if (ci.quantity <= 0) cart.remove(index); - } - - public void clearCart(Player p) { - shoppingCarts.remove(p.getUniqueId()); - } - - // ===== 打开购物车页面 ===== - public void openCart(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§a§l购物车"); - List cart = shoppingCarts - .getOrDefault(p.getUniqueId(), - new ArrayList<>()); - for (int i = 0; i < cart.size() && i < 25; i++) { - CartItem ci = cart.get(i); - ItemStack is = new ItemStack(ci.material); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e" + ci.displayName - + " §7x" + ci.quantity); - im.setLore(java.util.Arrays.asList( - "§7单价: §f" + ci.unitPrice + "枚", - "§7小计: §6" - + (ci.unitPrice * ci.quantity) - + "枚", - "", - "§a左键+1 §c右键-1")); - is.setItemMeta(im); - } - g.setItem(10 + i, is); - } - if (cart.isEmpty()) { - g.setItem(22, mkItem(Material.BARRIER, - "§7购物车为空")); - } - int total = getCartTotal(p); - int bal = plugin.getBonds().getBonds(p.getName()); - g.setItem(49, mkItem(Material.EMERALD, - "§6§l结算: §e" + total + " §6枚债券", - "§7余额: §f" + bal + "枚", - bal >= total ? "§a余额充足" : "§c余额不足", - "", "§a点击结算")); - g.setItem(48, mkItem( - Material.LIME_STAINED_GLASS_PANE, - "§7清空购物车")); - g.setItem(53, mkItem(Material.ARROW, "§7返回商店")); - - p.openInventory(g); - } - - // ===== 购物车点击处理 ===== - public boolean handleCartClick(Player p, int raw, - boolean left, - boolean right) { - if (raw == 53) { openShopMain(p); return true; } - if (raw == 48) { - clearCart(p); - p.sendMessage("§7购物车已清空"); - openCart(p); - return true; - } - if (raw == 49) { - checkoutCartInit(p); - return true; - } - if (raw >= 10 && raw < 35) { - int idx = raw - 10; - List cart = shoppingCarts - .getOrDefault(p.getUniqueId(), - new ArrayList<>()); - if (idx < cart.size()) { - if (right) cartRemoveOne(p, idx); - else cartAddOne(p, idx); - openCart(p); - } - return true; - } - return true; - } - - // ===== 结算入口:判断打包方式 ===== - private void checkoutCartInit(Player p) { - UUID uuid = p.getUniqueId(); - List cart = shoppingCarts.get(uuid); - if (cart == null || cart.isEmpty()) { - p.sendMessage("§c购物车为空"); - return; - } - int total = getCartTotal(p); - int bal = plugin.getBonds() - .getBonds(p.getName()); - if (bal < total) { - p.sendMessage("§c余额不足,需要 §e" - + total + "§c枚"); - return; - } - int totalQty = 0; - for (CartItem ci : cart) totalQty += ci.quantity; - if (cart.size() == 1 && totalQty <= 64) { - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - double factor = 1.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - deductAndGive(p, cart, - (int) Math.round(total * factor), - false, null, "", 0, - ecoPct, couponPct); - return; - } - pendingCheckout.put(uuid, - new ArrayList<>(cart)); - openPackingConfirm(p, total); - } - - - - // ===== 执行扣款+发货 ===== - private boolean deductAndGive(Player p, - List cart, int baseTotal, - boolean useShulker, Material color, - String packNote, int packFee, - int ecoPct, int couponPct) { - - UUID uuid = p.getUniqueId(); - int originalTotal = 0; - for (CartItem ci : cart) - originalTotal += ci.unitPrice * ci.quantity; - - String[] names = new String[cart.size()]; - int[] subs = new int[cart.size()]; - for (int i = 0; i < cart.size(); i++) { - CartItem ci = cart.get(i); - names[i] = ci.displayName; - subs[i] = ci.unitPrice * ci.quantity; - } - - if (baseTotal < originalTotal) { - double ratio = - (double) baseTotal / originalTotal; - int sum = 0; - for (int i = 0; i < subs.length; i++) { - if (i == subs.length - 1) { - subs[i] = baseTotal - sum; - } else { - subs[i] = (int) (subs[i] * ratio); - sum += subs[i]; - } - } - } - - boolean ok = plugin.getBonds().checkoutCart( - p.getName(), names, subs); - if (!ok) { - p.sendMessage("§c扣款失败"); - return false; - } - - if (packFee > 0) { - plugin.getBonds().deductBonds( - p.getName(), packFee, - "shop_packing", "", - "商店系统", "打包费"); - } - - for (CartItem ci : cart) { - ShopItem si = findItemById(ci.itemId); - if (si != null) { - if (si.getStock() >= 0) - si.setStock(si.getStock() - - ci.quantity); - si.addSales(ci.quantity); - } - } - - int newBal = plugin.getBonds() - .getBonds(p.getName()); - - - // ===== 优惠券码 ===== - String usedCouponCode = - pendingCouponCode.get(uuid); - - // ===== 记录订单 ===== - OrderManager.OrderRecord latestOrder = null; - try { - List orderItems = - new ArrayList<>(); - for (CartItem ci : cart) { - orderItems.add( - new OrderManager.OrderItem( - ci.displayName, - ci.material.name(), - ci.unitPrice, - ci.unitPrice, - ci.quantity)); - } - String dType = "none"; - String cCode = ""; - if (usedCouponCode != null) { - dType = "coupon"; - cCode = usedCouponCode; - } else if (ecoPct > 0) { - dType = "eco"; - } - plugin.getOrderManager().recordOrder( - p, orderItems, originalTotal, - baseTotal + packFee, - originalTotal - baseTotal, - dType, cCode, packFee, - useShulker ? (packFee > 0 - ? "custom" : "default") - : "none", - color != null ? color.name() : ""); - // 获取最新订单 - synchronized (plugin.getOrderManager() - .getOrders()) { - List all = - plugin.getOrderManager() - .getOrders(); - if (!all.isEmpty()) - latestOrder = - all.get(all.size() - 1); - } - // 散装打印小票 - if (!useShulker && latestOrder != null) { - plugin.getOrderManager() - .sendReceiptChat(p, latestOrder); - } - } catch (Exception ex) { - // plugin.getLogger().warning( - // "[Shop] 记录订单失败: " - // + ex.getMessage()); - } - -// ===== 发货 ===== - if (!useShulker) { - for (CartItem ci : cart) { - ShopItem si = findItemById(ci.itemId); - ItemStack give = (si != null) - ? getShopStack(si, ci.quantity) - : new ItemStack(ci.material, ci.quantity); - HashMap left2 = - p.getInventory().addItem(give); - for (ItemStack drop : left2.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - } - } else { - List shulkers = - packShulkers(cart, color); - - // ★ 给潜影盒打订单ID标签 - if (latestOrder != null) { - for (ItemStack sh : shulkers) { - ItemMeta shm = sh.getItemMeta(); - if (shm != null) { - List lore = shm.getLore() != null - ? new ArrayList<>(shm.getLore()) - : new ArrayList<>(); - lore.add("§8#" + latestOrder.orderId); - shm.setLore(lore); - sh.setItemMeta(shm); - } - } - } - - // 小票书塞入 - if (!shulkers.isEmpty() - && latestOrder != null) { - ItemStack book = plugin.getOrderManager() - .createReceiptBook(latestOrder); - plugin.getOrderManager() - .addBookToShulker(shulkers.get(0), book); - } - // 发货 - for (ItemStack sh : shulkers) { - HashMap left2 = - p.getInventory().addItem(sh); - for (ItemStack drop : left2.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - } - } - - - // ===== 退款记录 ===== - long now = System.currentTimeMillis(); - if (!useShulker) { - for (CartItem ci : cart) { - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - ci.itemId, ci.displayName, - ci.material, null, - ci.quantity, - ci.unitPrice * ci.quantity, - now)); - } - } else { - String summary = ""; - for (CartItem ci : cart) { - if (!summary.isEmpty()) summary += "+"; - summary += ci.displayName - + "x" + ci.quantity; - } - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - "cart_" + now, summary, - Material.SHULKER_BOX, color, - 1, baseTotal, now)); - } - - // ===== 核销优惠券 ===== - if (usedCouponCode != null) { - activeDiscount.remove(uuid); - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - m.invoke(sdf1, usedCouponCode); - } - } catch (Exception ignored) {} - p.sendMessage("§7优惠券 §e" + usedCouponCode - + " §7已核销"); - } - - shoppingCarts.remove(uuid); - pendingCheckout.remove(uuid); - return true; - } - - @EventHandler - public void onBlockPlace(org.bukkit.event.block.BlockPlaceEvent e) { - ItemStack item = e.getItemInHand(); - if (item == null) return; - if (!item.getType().name().contains("SHULKER")) return; - markShulkerUsed(item); - } - - // ★ 公共方法:标记潜影盒为已使用 - private void markShulkerUsed(ItemStack item) { - if (item == null) return; - ItemMeta meta = item.getItemMeta(); - if (meta == null || meta.getLore() == null) return; - for (String line : meta.getLore()) { - if (line.startsWith("§8#")) { - try { - long orderId = Long.parseLong( - line.replace("§8#", "").trim()); - // shulkerUsedOrders.add(orderId); - } catch (NumberFormatException ignored) {} - } - } - } - - @EventHandler - public void onPlayerDrop(org.bukkit.event.player.PlayerDropItemEvent e) { - ItemStack item = e.getItemDrop().getItemStack(); - if (item != null && item.getType().name().contains("SHULKER")) { - markShulkerUsed(item); - } - } - - - // ===== 潠影盒打包(指定颜色)===== - private List packShulkers( - List items, Material color) { - List result = new ArrayList<>(); - List all = new ArrayList<>(); - for (CartItem ci : items) { - int rem = ci.quantity; - while (rem > 0) { - int sz = Math.min(rem, 64); - all.add(new ItemStack(ci.material, sz)); - rem -= sz; - } - } - while (!all.isEmpty()) { - ItemStack shulker = new ItemStack(color); - org.bukkit.inventory.meta.BlockStateMeta meta = - (org.bukkit.inventory.meta.BlockStateMeta) - shulker.getItemMeta(); - if (meta != null) { - org.bukkit.block.BlockState state = - meta.getBlockState(); - if (state instanceof org.bukkit.block.Container) { - org.bukkit.block.Container c = - (org.bukkit.block.Container) state; - List remain = new ArrayList<>(); - for (ItemStack stack : all) { - HashMap left = - c.getInventory().addItem(stack); - for (ItemStack l : left.values()) - remain.add(l); - } - all = remain; - meta.setBlockState(c); - shulker.setItemMeta(meta); - } - } - result.add(shulker); - } - - return result; - } - - // ===== 打包确认GUI ===== - public void openPackingConfirm(Player p, int baseTotal) { - Inventory g = Bukkit.createInventory( - null, 27, "§6§l选择包装方式"); - g.setItem(10, mkItem(Material.CHEST, - "§a§l不打包", - "§7直接放入背包", - "§7适合少量物品")); - g.setItem(13, mkItem(Material.SHULKER_BOX, - "§6§l默认打包", - "§7随机颜色潜影盒", - "§7加收 §e5 §6枚债券")); - g.setItem(16, mkItem(Material.ENDER_CHEST, - "§b§l自选颜色", - "§7选择你喜欢的潜影盒颜色", - "§7加收 §e5 §6枚债券")); - g.setItem(22, mkItem(Material.PAPER, - "§7消费金额: §e" + baseTotal + "枚")); - - p.openInventory(g); - } - - public boolean handlePackingConfirm( - Player p, int raw) { - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.remove(uuid); - if (cart == null) return false; - - int originalTotal = 0; - for (CartItem ci : cart) - originalTotal += ci.unitPrice * ci.quantity; - - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - - if (raw == 10) { - ecoPct = 2; - } - - double factor = 1.0; - if (ecoPct > 0) - factor *= (100 - ecoPct) / 100.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - int discounted = (int) Math.round( - originalTotal * factor); - - int totalQty = 0; - for (CartItem ci : cart) - totalQty += ci.quantity; - boolean multiAndBig = (cart.size() > 1 - && originalTotal >= 100); - - if (raw == 10) { - deductAndGive(p, cart, discounted, - false, null, "", 0, - ecoPct, couponPct); - return true; - } - if (raw == 13) { - Material c = Material.SHULKER_BOX; - if (multiAndBig) { - deductAndGive(p, cart, discounted, - true, c, "", 0, - ecoPct, couponPct); - } else { - deductAndGive(p, cart, discounted, - true, c, "默认打包", 5, - ecoPct, couponPct); - } - return true; - } - if (raw == 16) { - pendingCheckout.put(uuid, cart); - openColorSelect(p, discounted); - return true; - } - return true; - } - - - // ===== 自选颜色GUI ===== - public void openColorSelect(Player p, int baseTotal) { - Inventory g = Bukkit.createInventory( - null, 54, "§b§l选择潜影盒颜色"); - String[] colorNames = { - "白色","橙色","品红","淡蓝", - "黄色","淡绿","粉色","灰色", - "淡灰","青色","紫色","蓝色", - "棕色","绿色","红色","黑色","原色" - }; - for (int i = 0; i < SHULKER_COLORS.length; i++) { - ItemStack is = new ItemStack(SHULKER_COLORS[i]); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e" + colorNames[i] - + "§6潜影盒"); - im.setLore(java.util.Arrays.asList( - "§7加收 §e5 §6枚债券", - "", - "§a点击选择")); - is.setItemMeta(im); - } - g.setItem(i, is); - } - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - g.setItem(45, mkItem(Material.PAPER, - "§7消费: §e" + baseTotal - + "枚 + §e5§6打包费")); - - p.openInventory(g); - } - - public boolean handleColorSelect( - Player p, int raw) { - if (raw == 49) { - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.get(uuid); - if (cart != null) { - int total = 0; - for (CartItem ci : cart) - total += ci.unitPrice * ci.quantity; - openPackingConfirm(p, total); - } - return true; - } - if (raw < 0 || raw >= SHULKER_COLORS.length) - return false; - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.remove(uuid); - if (cart == null) return false; - int total = 0; - for (CartItem ci : cart) - total += ci.unitPrice * ci.quantity; - - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - double factor = 1.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - int discounted = (int) Math.round( - total * factor); - - Material selectedColor = SHULKER_COLORS[raw]; - if (selectedColor == Material.SHULKER_BOX) { - deductAndGive(p, cart, discounted, true, - Material.SHULKER_BOX, - "原色打包", 0, - ecoPct, couponPct); - } else { - deductAndGive(p, cart, discounted, true, - selectedColor, "自选颜色", 5, - ecoPct, couponPct); - } - return true; - } - - - - // ===== 加载分类 ===== - - public void loadCategories() { - // plugin.getLogger().info("[Shop] === loadCategories 被调用 ==="); // 过滤调试日志 - categories.clear(); - viewCat.clear(); // ★ 清缓存 - viewPage.clear(); - - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - - convertTxtToMd(dir); - - File[] allMd = dir.listFiles( - (d, n) -> n.endsWith(".md") - && !n.equals("coupons.md")); - if (allMd == null || allMd.length == 0) { - createDefaultCategories(dir); - allMd = dir.listFiles( - (d, n) -> n.endsWith(".md") - && !n.equals("coupons.md")); - } - if (allMd == null) { - // plugin.getLogger().warning( - // "[Shop] shop目录为空"); - return; - } - - /* plugin.getLogger().info("[Shop] === 开始加载 ==="); - plugin.getLogger().info("[Shop] 扫描到 " - + allMd.length + " 个md文件"); -*/ - for (File f : allMd) { - /* plugin.getLogger().info("[Shop] 读取: " - + f.getName());*/ - ShopCategory cat = parseFile(f); - if (cat != null) { - /* plugin.getLogger().info("[Shop] 分类: " - + cat.getName() - + " 商品数: " - + cat.getItems().size());*/ - if (!cat.getItems().isEmpty()) { - categories.add(cat); - } - } - } - - int totalItems = 0; - for (ShopCategory c : categories) - totalItems += c.getItems().size(); - - plugin.getLogger().info("[Shop] === 加载完成 ===" - + " 分类=" + categories.size() - + " 商品=" + totalItems); - } - - -// ===== txt → md 自动迁移 ===== - - private void convertTxtToMd(File dir) { - File[] txtFiles = dir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("coupons.txt")); - if (txtFiles == null) return; - for (File txt : txtFiles) { - String mdName = txt.getName() - .replace(".txt", ".md"); - File mdFile = new File(dir, mdName); - if (mdFile.exists()) continue; - - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(txt), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) - lines.add(line); - br.close(); - - String catName = txt.getName() - .replace(".txt", ""); - List out = new ArrayList<>(); - out.add("# " + catName); - out.add(""); - out.add("| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |"); - out.add("| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |"); - - for (String l : lines) { - l = l.trim(); - if (l.isEmpty() || l.startsWith("#")) - continue; - // 旧格式: ID | 名称 | 材质 | ... - // 保持原样转换为 md 表格行 - out.add("| " + l.replace("|", "|") - + " |"); - } - - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(mdFile), - StandardCharsets.UTF_8)); - for (String o : out) - pw.println(o); - pw.flush(); - pw.close(); - - txt.delete(); - // plugin.getLogger().info( - // "[Shop] 已迁移: " - // + txt.getName() - // + " → " + mdName); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 迁移失败: " - // + txt.getName()); - } - } - } - -// ===== 解析 md 文件 ===== - - private ShopCategory parseFile(File f) { - String name = f.getName() - .replace(".md", ""); - ShopCategory cat = - new ShopCategory(name, f.getName()); - try { - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean headerPassed = false; - boolean separatorPassed = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - - // 空行跳过 - if (line.isEmpty()) continue; - - // 标题行:# 分类名 - if (line.startsWith("#")) { - String t = line.replaceFirst( - "^#+\\s*", "").trim(); - if (!t.isEmpty()) { - // 更新分类名(用文件内标题) - // cat.name 是 final,不改 - } - continue; - } - - // 表格分隔行:| --- | --- | - if (line.contains("---")) { - separatorPassed = true; - continue; - } - - // 表头行:| ID | 品名 | ... - if (line.startsWith("|")) { - if (!separatorPassed) { - headerPassed = true; - continue; - } - // 数据行 - ShopItem item = - parseTableRow(line); - if (item != null) - cat.addItem(item); - } - } - br.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 解析失败: " + f.getName() - // + " 原因: " + e.getMessage()); - return null; - } - return cat; - } - -// ===== 解析表格行 ===== - - private ShopItem parseTableRow(String row) { - // plugin.getLogger().info("[Shop] 行: " + row); // 过滤调试日志 - - String trimmed = row.trim(); - if (trimmed.startsWith("|")) - trimmed = trimmed.substring(1); - if (trimmed.endsWith("|")) - trimmed = trimmed.substring(0, - trimmed.length() - 1); - - String[] p = trimmed.split("\\|"); - // plugin.getLogger().info("[Shop] 列数=" + p.length); // 过滤调试日志 - - if (p.length < 6) { - // plugin.getLogger().warning( - // "[Shop] ✗ 列数不足, 跳过"); - return null; - } - - String rawId = p[0].trim(); - String rawName = p[1].trim(); - String rawMat = p[2].trim().toUpperCase(); - String rawBuy = p[3].trim(); - String rawSell = p[4].trim(); - String rawStock = p[5].trim(); - - // plugin.getLogger().info("[Shop] ID=" - // + rawId + " 名=" + rawName - // + " 材质=" + rawMat - // + " 购=" + rawBuy + " 售=" + rawSell - // + " 库=" + rawStock); // 过滤调试日志 - - // 检查材质是否存在 - Material mat = Material.matchMaterial(rawMat); - if (mat == null) { - // plugin.getLogger().warning( - // "[Shop] ✗ 无效材质: " + rawMat - // + " (行: " + row + ")"); - return null; - } - - try { - ShopItem item = new ShopItem( - rawId, rawName, mat, - Integer.parseInt(rawBuy), - Integer.parseInt(rawSell), - Integer.parseInt(rawStock), - p.length > 6 - ? Integer.parseInt( - p[6].trim()) : 0, - p.length > 7 - ? Integer.parseInt( - p[7].trim()) : 0); - // plugin.getLogger().info("[Shop] ✓ 解析成功: " + rawId); // 过滤调试日志 - return item; - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] ✗ 数字解析失败: " - // + e.getMessage()); - return null; - } - } - - -// ===== 保存为 md ===== - - public void saveCategory(ShopCategory cat) { - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - File f = new File(dir, cat.getFileName()); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# " + cat.getName()); - pw.println(""); - pw.println("| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |"); - pw.println("| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |"); - for (ShopItem item : cat.getItems()) { - pw.println("| " - + item.getId() - + " | " + item.getDisplayName() - + " | " + item.getMaterial().name() - + " | " + item.getBuyPrice() - + " | " + item.getSellPrice() - + " | " + item.getStock() - + " | " + item.getHourlySales() - + " | " + item.getTotalSales() - + " |"); - } - pw.flush(); - pw.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 保存失败: " - // + cat.getFileName()); - } - } - -// ===== 创建示例分类 ===== - - private void createDefaultCategories(File dir) { - writeMd(dir, "建筑材料.md", - "# 建筑材料\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| STONE | 石头 | STONE" - + " | 10 | -1 | -1 | 0 | 0 |\n" - + "| COBBLESTONE | 圆石 | COBBLESTONE" - + " | 5 | -1 | -1 | 0 | 0 |\n" - + "| OAK_PLANKS | 橡木木板 | OAK_PLANKS" - + " | 8 | 3 | -1 | 0 | 0 |\n" - + "| BRICKS | 砖块 | BRICKS" - + " | 12 | -1 | -1 | 0 | 0 |\n" - + "| GLASS | 玻璃 | GLASS" - + " | 15 | 6 | -1 | 0 | 0 |\n" - + "| QUARTZ_BLOCK | 石英块 | QUARTZ_BLOCK" - + " | 20 | 8 | -1 | 0 | 0 |\n"); - - writeMd(dir, "装备工具.md", - "# 装备工具\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| IRON_PICKAXE | 铁镐 | IRON_PICKAXE" - + " | 50 | 20 | -1 | 0 | 0 |\n" - + "| DIAMOND_PICKAXE | 钻石镐 | DIAMOND_PICKAXE" - + " | 500 | 200 | -1 | 0 | 0 |\n" - + "| IRON_SWORD | 铁剑 | IRON_SWORD" - + " | 40 | 16 | -1 | 0 | 0 |\n" - + "| DIAMOND_SWORD | 钻石剑 | DIAMOND_SWORD" - + " | 400 | 160 | -1 | 0 | 0 |\n" - + "| ELYTRA | 鞘翅 | ELYTRA" - + " | 2000 | 800 | -1 | 0 | 0 |\n"); - - writeMd(dir, "食物补给.md", - "# 食物补给\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| COOKED_BEEF | 熟牛排 | COOKED_BEEF" - + " | 15 | 6 | -1 | 0 | 0 |\n" - + "| GOLDEN_APPLE | 金苹果 | GOLDEN_APPLE" - + " | 500 | 200 | -1 | 0 | 0 |\n" - + "| BREAD | 面包 | BREAD" - + " | 8 | 3 | -1 | 0 | 0 |\n"); - - // plugin.getLogger().info( - // "[Shop] 已创建3个示例分类(md)"); - } - - private void writeMd(File dir, String name, - String content) { - File f = new File(dir, name); - if (f.exists()) return; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.print(content); - pw.flush(); - pw.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 创建失败: " + name); - } - } - - /** - * 从商品名称+材质智能推断完整物品 - * - * "夜视药水" + POTION - * → PotionEffect(NIGHT_VISION, 600, 0) - * - * "喷溅型治疗药水" + SPLASH_POTION - * → PotionEffect(HEALING, 600, 0) - * - * "长效夜视之箭" + TIPPED_ARROW - * → PotionEffect(NIGHT_VISION, 1800, 0) - * - * "强效力量喷溅药水" + SPLASH_POTION - * → PotionEffect(STRENGTH, 600, 1) - */ - private ItemStack inferFromName( - String name, Material mat) { - // 只处理药水和箭 - if (mat != Material.POTION - && mat != Material.SPLASH_POTION - && mat != Material.LINGERING_POTION - && mat != Material.TIPPED_ARROW) { - return null; - } - - // 1. 找效果类型 - PotionEffectType effectType = null; - for (Map.Entry e - : EFFECT_MAP.entrySet()) { - if (name.contains(e.getKey())) { - effectType = e.getValue(); - break; - } - } - if (effectType == null) return null; - - // 2. 找时长 - int duration = 600; - if (name.contains("长效") || name.contains("长时") - || name.contains("持久") || name.contains("长命")) { - duration = 1800; - } -// ★ 瞬间效果药水:duration = 1 - if (name.contains("治疗") || name.contains("治愈") - || name.contains("伤害")) { - duration = 1; - } - - // 3. 找等级 - int amplifier = 0; - if (name.contains("强效") - || name.contains("强化") - || name.contains("加强")) { - amplifier = 1; - } - - // 4. 构造完整物品 - try { - ItemStack item = new ItemStack(mat); - PotionMeta meta = - (PotionMeta) item.getItemMeta(); - - // ★ 神龟药水:双效果 - if (name.contains("神龟")) { - meta.addCustomEffect( - new PotionEffect( - PotionEffectType.RESISTANCE, - duration, amplifier + 2), - true); - meta.addCustomEffect( - new PotionEffect( - PotionEffectType.SLOWNESS, - duration, amplifier + 3), - true); - } else { - // 普通药水:单效果 - meta.addCustomEffect( - new PotionEffect(effectType, - duration, amplifier), - true); - } - - item.setItemMeta(meta); - return item; - } catch (Exception e) { - return null; - } - } - - public void saveAll() { - for (ShopCategory cat : categories) - saveCategory(cat); - } - private ItemStack getShopStack( - ShopItem item, int amount) { - // 优先级1: 手动捕获的NBT数据 - ItemStack cached = - itemStackCache.get(item.getId()); - if (cached != null) { - ItemStack copy = cached.clone(); - copy.setAmount(amount); - return copy; - } - // 优先级2: 根据名称智能推断 - ItemStack inferred = inferFromName( - item.getDisplayName(), - item.getMaterial()); - if (inferred != null) { - ItemStack copy = inferred.clone(); - copy.setAmount(amount); - return copy; - } - // 优先级3: 纯材质(默认) - return new ItemStack( - item.getMaterial(), amount); - } - - // ===== 购买 ===== - - public boolean buyItem(Player p, ShopItem item, - int amount) { - // plugin.getLogger().info("[Shop] ★ buyItem item=" + (item != null ? item.getId() : "null") + " amount=" + amount); - // plugin.getLogger().info("buyItem被调用"); - if (item == null) return false; - if (item.getStock() >= 0 - && item.getStock() < amount) { - p.sendMessage("§c库存不足,剩余: " - + item.getStock()); - return false; - } - if (p.getInventory().firstEmpty() == -1 - && !p.getInventory().contains( - item.getMaterial(), 1)) { - p.sendMessage("§c背包已满"); - return false; - } - - int unit = item.getBuyPrice(); - int disc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int total = Math.max(0, - unit * amount * (100 - disc) / 100); - - int balance = plugin.getBonds() - .getBonds(p.getName()); - if (balance < total) { - p.sendMessage("§c债券不足,需要 §e" - + total + "§c枚,当前 §e" - + balance + "§c枚"); - return false; - } - - boolean ok = plugin.getBonds().deductBonds( - p.getName(), total, - "shop_buy", item.getId(), - "商店系统", "购买" - + item.getDisplayName()); - if (!ok) { - p.sendMessage("§c扣款失败"); - return false; - } - - ItemStack give = getShopStack(item, amount); - HashMap left = - p.getInventory().addItem(give); - for (ItemStack drop : left.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - - if (item.getStock() >= 0) - item.setStock( - item.getStock() - amount); - item.addSales(amount); - - mergeOrAddRefund(p, item, amount, total); - Long applyTime = couponApplyTime.get(p.getUniqueId()); - if (applyTime != null - && System.currentTimeMillis() - applyTime > 300000L) { - // 超过5分钟,自动失效 - pendingCouponCode.remove(p.getUniqueId()); - activeDiscount.remove(p.getUniqueId()); - couponApplyTime.remove(p.getUniqueId()); - p.sendMessage("§7优惠券已过期,请重新输入"); - disc = 0; // 重置折扣 - } - - - String discMsg = disc > 0 - ? " §7(优惠" + disc + "%)" : ""; - p.sendMessage("§a购买成功: §e" - + item.getDisplayName() - + " x" + amount - + " §a-" + total + "枚债券" - + discMsg); - if (disc > 0) - activeDiscount.remove( - p.getUniqueId()); - // ★ 直购成功后核销优惠券 - String usedCode = pendingCouponCode - .remove(p.getUniqueId()); - if (usedCode != null) { - activeDiscount.remove(p.getUniqueId()); - couponApplyTime.remove(p.getUniqueId()); - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - m.invoke(sdf1, usedCode); - } - } catch (Exception ignored) {} - p.sendMessage("§7优惠券 §e" + usedCode - + " §7已核销"); - couponApplyTime.remove(p.getUniqueId()); - } - // ★ 记录订单(包裹try-catch,失败不影响购买) - try { - String usedCouponCode = - pendingCouponCode.get(p.getUniqueId()); - List orderItems = - new ArrayList<>(); - orderItems.add(new OrderManager.OrderItem( - item.getDisplayName(), - item.getMaterial().name(), - item.getBuyPrice(), - item.getBuyPrice(), amount)); - plugin.getOrderManager().recordOrder( - p, orderItems, - item.getBuyPrice() * amount, total, - item.getBuyPrice() * amount - total, - usedCouponCode != null ? "coupon" : "none", - usedCouponCode != null ? usedCouponCode : "", - 0, "none", ""); - // 打印小票 - List all = - plugin.getOrderManager().getOrders(); - if (!all.isEmpty()) { - plugin.getOrderManager().sendReceiptChat( - p, all.get(all.size() - 1)); - } - } catch (Exception ex) { - // plugin.getLogger().warning( - // "[Shop] 记录订单失败: " + ex.getMessage()); - } - - return true; - } - - - public void handleRefundItemClick(Player p, int raw) { - if (raw < 10 || raw >= 45) return; - int target = raw - 10; - int cnt = 0; - synchronized (refundRecords) { - Iterator it = - refundRecords.iterator(); - while (it.hasNext()) { - RefundRecord r = it.next(); - if (!r.playerUUID.equals(p.getUniqueId())) - continue; - if (r.isExpired()) { it.remove(); continue; } - if (cnt == target) { - requestRefund(p, r); - return; - } - cnt++; - } - } - } - - - // ===== 出售 ===== - - public boolean sellItem(Player p, ShopItem item, - int amount) { - if (item == null) return false; - - // ★ 先判断售价,禁止出售的不收物品 - if (item.getEffectiveSellPrice() <= 0) { - p.sendMessage("§c该商品不可出售"); - return false; - } - - if (!p.getInventory().containsAtLeast( - getShopStack(item, 1), amount)) { - p.sendMessage("§c你没有足够的 §e" - + item.getDisplayName()); - return false; - } - - int total = item.getEffectiveSellPrice() * amount; - - p.getInventory().removeItem( - getShopStack(item, amount)); - plugin.getBonds().addBonds( - p.getName(), total, "shop_sell", - item.getId(), "商店系统", - "出售" + item.getDisplayName()); - if (item.getStock() >= 0) - item.setStock(item.getStock() + amount); - p.sendMessage("§a出售成功: §e" - + item.getDisplayName() - + " x" + amount - + " §a+" + total + "枚债券"); - return true; - } - - // ===== 退款合并:30秒内同商品合并为一个订单 ===== - private boolean mergeOrAddRefund(Player p, ShopItem item, - int amount, int total) { - long now = System.currentTimeMillis(); - synchronized (refundRecords) { - for (int i = refundRecords.size() - 1; i >= 0; i--) { - RefundRecord r = refundRecords.get(i); - boolean same = r.playerUUID.equals(p.getUniqueId()) - && r.itemId.equals(item.getId()) - && (now - r.timestamp) < 30000L; - if (same) { - // final 字段不能改,只能删旧建新 - int newAmount = r.amount + amount; - int newTotal = r.totalPaid + total; - refundRecords.remove(i); - refundRecords.add(new RefundRecord( - r.playerUUID, r.playerName, - r.itemId, r.itemName, - r.material, newAmount, - newTotal, now)); - return true; - } - } - } - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - item.getId(), item.getDisplayName(), - item.getMaterial(), amount, total, now)); - return false; - } - - - - // ===== 退款 ===== - - public boolean requestRefund(Player p, RefundRecord rec) { - if (rec == null) return false; - if (!rec.playerUUID.equals(p.getUniqueId())) { - p.sendMessage("§c这不是你的购买记录"); - return false; - } - if (rec.isExpired()) { - p.sendMessage("§c已超过5分钟退款期限"); - synchronized (refundRecords) { - refundRecords.remove(rec); - } - return false; - } - - // 检测物品/潜影盒是否还在 - synchronized (refundRecords) { - if (rec.shulkerMaterial != null) { - int boxCount = countShulkers(p, rec.shulkerMaterial); - if (boxCount < 1) { - p.sendMessage("§c潜影盒已移出背包,无法退款"); - refundRecords.remove(rec); - return false; - } - } else { - int have = countDirect(p, rec.material); - if (have < rec.amount) { - p.sendMessage("§c物品已转移或消耗,无法退款"); - refundRecords.remove(rec); - return false; - } - } - } - - // 退款:扣物品/潜影盒 - if (rec.shulkerMaterial != null) { - // 盒装:扣一个潜影盒 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == rec.shulkerMaterial) { - p.getInventory().clear(i); - break; - } - } - } else { - // 普通:扣商品 - p.getInventory().removeItem( - new ItemStack(rec.material, rec.amount)); - } - - // 退款:加债券 - plugin.getBonds().addBonds( - p.getName(), rec.totalPaid, - "refund", rec.itemId, - "商店系统", "退款" + rec.itemName); - - // 恢复库存 - ShopItem si = findItemById(rec.itemId); - if (si != null && si.getStock() >= 0) - si.setStock(si.getStock() + rec.amount); - - synchronized (refundRecords) { - refundRecords.remove(rec); - } - - p.sendMessage("§a退款成功: §e" + rec.itemName - + " §a+" + rec.totalPaid + "枚债券"); - return true; - } - - - - - public void setPlayerDiscount(Player p, - int pct) { - activeDiscount.put( - p.getUniqueId(), pct); - p.sendMessage("§a已设置 " + pct - + "% 折扣,下次购买生效"); - } - - // ===== 管理员操作 ===== - - public boolean addItemToCategory( - String catName, String id, - String displayName, Material mat, - int buyPrice, int sellPrice, - int stock) { - ShopCategory cat = - findCategory(catName); - if (cat == null) return false; - if (cat.getItem(id) != null) return false; - cat.addItem(new ShopItem(id, displayName, - mat, buyPrice, sellPrice, - stock, 0, 0)); - saveCategory(cat); - return true; - } - - public boolean removeItem(String id) { - for (ShopCategory cat : categories) { - ShopItem item = cat.getItem(id); - if (item != null) { - cat.getItems().remove(item); - saveCategory(cat); - return true; - } - } - return false; - } - - public boolean setStock(String id, int stock) { - ShopItem item = findItemById(id); - if (item == null) return false; - item.setStock(stock); - for (ShopCategory cat : categories) { - if (cat.getItem(id) != null) { - saveCategory(cat); - return true; - } - } - return false; - } - - public ShopCategory findCategory(String n) { - for (ShopCategory c : categories) - if (c.getName().equals(n)) return c; - return null; - } - - public ShopItem findItemById(String id) { - for (ShopCategory c : categories) { - ShopItem item = c.getItem(id); - if (item != null) return item; - } - return null; - } - - public List getCategories() { - return categories; - } - - // ===== 命令处理 ===== - - public boolean handleCommand( - CommandSender s, String[] a) { - if (a.length <= 1) { - if (s instanceof Player) { - openShopMain((Player) s); - } else { - s.sendMessage("§c仅玩家可使用"); - } - return true; - } - - String sub = a[1].toLowerCase(); - - if (sub.equals("reload")) { - loadCategories(); - s.sendMessage("§a商店已重载,共 " - + categories.size() - + " 个分类"); - return true; - } - - if (sub.equals("add")) { - if (a.length < 7) { - s.sendMessage( - "§e用法: shop add <分类>" - + " <名称> <材质>" - + " <购入价>" - + " [售出价-1自动]" - + " [库存-1无限]"); - return true; - } - Material mat; - try { - mat = Material.valueOf( - a[5].toUpperCase()); - } catch (Exception e) { - s.sendMessage( - "§c无效材质: " + a[5]); - return true; - } - int price = Integer.parseInt(a[6]); - int sell = a.length > 7 - ? Integer.parseInt(a[7]) : -1; - int stock = a.length > 8 - ? Integer.parseInt(a[8]) : -1; - if (addItemToCategory(a[2], a[3], - a[4], mat, price, sell, stock)) { - s.sendMessage("§a已添加: " - + a[4] + " → " + a[2]); - } else { - s.sendMessage("§c添加失败" - + "(分类不存在或ID重复)"); - } - return true; - } - - if (sub.equals("remove")) { - if (a.length < 3) { - s.sendMessage( - "§e用法: shop remove "); - return true; - } - if (removeItem(a[2])) { - s.sendMessage( - "§a已移除: " + a[2]); - } else { - s.sendMessage( - "§c未找到: " + a[2]); - } - return true; - } - - if (sub.equals("set")) { - if (a.length < 4) { - s.sendMessage( - "§e用法: shop set " - + " <库存数(-1无限)>"); - return true; - } - int stock = Integer.parseInt(a[3]); - if (setStock(a[2], stock)) { - s.sendMessage("§a已设置 " - + a[2] + " 库存为 " - + stock); - } else { - s.sendMessage( - "§c未找到: " + a[2]); - } - return true; - } - - if (sub.equals("pro")) { - if (a.length < 4) { - s.sendMessage( - "§e用法: shop pro" - + " <玩家名> <折扣%>"); - return true; - } - Player t = - Bukkit.getPlayerExact(a[2]); - if (t == null) { - s.sendMessage("§c玩家不在线"); - return true; - } - int pct = Integer.parseInt(a[3]); - setPlayerDiscount(t, pct); - s.sendMessage("§a已对 " + a[2] - + " 设置 " + pct + "% 折扣"); - return true; - } - - - s.sendMessage("§e商店子命令:" - + " reload / add / remove" - + " / set / pro "); - return true; - } - @EventHandler(priority = EventPriority.LOW) - public void onCouponChat( - org.bukkit.event.player.AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); - UUID uuid = p.getUniqueId(); - if (!couponListening.contains(uuid)) return; - e.setCancelled(true); - couponListening.remove(uuid); - String code = e.getMessage().trim(); - if (code.isEmpty()) { - p.sendMessage("§c已取消输入"); - return; - } - // ★ 预处理:标准化输入 - String normalized = normalizeCouponInput(code); - if (normalized.isEmpty()) { - p.sendMessage("§c优惠券码无效(输入为空或仅含特殊字符)"); - couponListening.remove(uuid); - return; - } - - p.sendMessage("§7正在验证优惠券: §e" + code); - - int discount = -1; - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "validateCoupon", - String.class); - Object result = m.invoke(sdf1, normalized); - discount = (result != null) - ? (Integer) result : -1; - } - } catch (Exception ex) { - p.sendMessage("§c验证异常: " - + ex.getMessage()); - return; - } - if (discount <= 0) { - p.sendMessage("§c优惠券无效或已使用"); - return; - } - int offPct = 100 - discount; - int current = activeDiscount - .getOrDefault(uuid, 0); - if (offPct > current) { - pendingCouponCode.put(uuid, normalized); - activeDiscount.put(uuid, offPct); - couponApplyTime.put(uuid, System.currentTimeMillis()); - String zhe; - if (discount % 10 == 0) - zhe = (discount / 10) + "折"; - else - zhe = (discount / 10.0) + "折"; - p.sendMessage("§6§l[优惠券] §a验证通过!"); - p.sendMessage("§7折扣: §e" + zhe - + " §7(减" + offPct + "%)"); - p.sendMessage("§7结算时自动核销"); - final UUID u = uuid; - Bukkit.getScheduler().runTask(plugin, () -> { - Player pl = Bukkit.getPlayer(u); - if (pl != null && pl.isOnline()) { - openShopMain(pl); - } - }); - } else { - String curZhe; - if ((100 - current) % 10 == 0) - curZhe = ((100 - current) / 10) + "折"; - else - curZhe = ((100 - current) / 10.0) + "折"; - p.sendMessage("§c当前已有更优折扣: " - + curZhe + ",本次不生效"); - } - } - - /** - * 优惠券输入标准化 - * 全角→半角、去分隔符、转小写 - * "AAA-BBB-CCCC" → "aaaabbbbcccc" - * "AAA-BBB" → "aaabbb" - * "aaa_bbb_cccc" → "aaabbbcccc" - */ - private String normalizeCouponInput(String s) { - if (s == null) return ""; - StringBuilder sb = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - // 全角数字→半角 - if (c >= '\uFF10' && c <= '\uFF19') { - sb.append((char)(c - 0xFF10 + '0')); - } - // 全角字母→半角 - else if (c >= '\uFF21' && c <= '\uFF3A') { - sb.append((char)(c - 0xFF21 + 'A')); - } - else if (c >= '\uFF41' && c <= '\uFF5A') { - sb.append((char)(c - 0xFF41 + 'a')); - } - // ★ 保留中文字符(Unicode CJK统一汉字范围) - else if ((c >= '\u4E00' && c <= '\u9FFF') - || (c >= '\u3400' && c <= '\u4DBF')) { - sb.append(c); - } - // 保留半角字母和数字 - else if ((c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9')) { - sb.append(c); - } - // 分隔符(- _ 空格等)丢弃 - } - return sb.toString().toLowerCase(); - } - - - - - - private String formatZhe(int discount) { - if (discount % 10 == 0) { - return (discount / 10) + "折"; - } - return (discount / 10.0) + "折"; - } - - public boolean redeemCoupon(Player p, String code) { - int discount = -1; - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - Object result = m.invoke(sdf1, code); - discount = (result != null) - ? (Integer) result : -1; - } - } catch (Exception e) { - return false; - } - if (discount <= 0) return false; - int offPct = 100 - discount; - int cur = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (offPct > cur) { - activeDiscount.put(p.getUniqueId(), offPct); - } - return true; - } - - /** - * 商店主界面点击"优惠券"按钮后调用 - * 开启聊天监听,等待玩家输入优惠券码 - */ - public void openCouponInput(Player p) { - UUID uuid = p.getUniqueId(); - // 先取消已有的待核销优惠券 - pendingCouponCode.remove(uuid); - couponListening.add(uuid); - // 关闭UI - Bukkit.getScheduler().runTask(plugin, () -> { - if (p.isOnline()) p.closeInventory(); - }); - p.sendMessage("§6§l[优惠券] §e请在聊天栏输入优惠券码"); - p.sendMessage("§715秒内输入,过期自动取消"); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (couponListening.remove(uuid)) { - Player pl = Bukkit.getPlayer(uuid); - if (pl != null && pl.isOnline()) { - pl.sendMessage("§c优惠券输入已超时取消"); - } - } - }, 300L); - } - - - // ===== GUI: 主界面 ===== - - public void openShopMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l商店"); - int si = 0; - for (ShopCategory cat : categories) { - if (si >= ITEM_SLOTS.length) break; - ItemStack is = new ItemStack( - Material.CHEST); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName( - "§e§l" + cat.getName()); - im.setLore(Arrays.asList( - "§7商品: §f" - + cat.getItems().size() - + " 种", - "", - "§a点击进入")); - is.setItemMeta(im); - } - g.setItem(ITEM_SLOTS[si], is); - si++; - } - // ★ 改为跳转订单中心 - g.setItem(49, mkItem(Material.CLOCK, - "§6§l订单中心", - "§7查看订单记录、申请退款", - "", "§a点击进入")); - - // 在 openShopMain 方法中,原来的 slot 49/53 附近加: - - g.setItem(48, mkItem( - isCartMode(p) - ? Material.LIME_WOOL - : Material.GRAY_WOOL, - isCartMode(p) - ? "§a§l购物车模式 §7[开]" - : "§7§l购物车模式 §7[关]", - isCartMode(p) - ? "§a当前: 点击商品加入购物车" - : "§7当前: 直接购买", - "§7点击切换")); - g.setItem(50, mkItem(Material.CHEST, - "§6§l购物车 §7[" + getCartCount(p) + "件]", - "§7总价: §6" + getCartTotal(p) + "枚", - "", - "§a点击打开购物车")); - - // 在现有按钮之后,p.openInventory(g) 之前加: - g.setItem(49, mkItem(Material.CLOCK, - "§6§l订单中心", - "§7查看购物订单记录", - "", "§a点击进入")); - - // 优惠券按钮 - slot 47 - String discStatus = "§7"; - int curDisc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (curDisc > 0) { - discStatus = "§a当前: " + formatZhe(100 - curDisc); - } - g.setItem(47, mkItem(Material.NAME_TAG, - "§6§l优惠券", - discStatus, - "", - "§a点击输入优惠券码", - "§7一次性使用,核销后失效")); - - g.setItem(53, mkItem(Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - // ===== GUI: 分类 ===== - public void openCategory(Player p, - ShopCategory cat, - int page) { - // ★ 每次重新获取最新对象 ★ - ShopCategory fresh = null; - for (ShopCategory c : categories) { - if (c.getName().equals(cat.getName())) { - fresh = c; - break; - } - } - if (fresh == null) { - openShopMain(p); - return; - } - cat = fresh; - viewCat.put(p.getUniqueId(), cat); - - int total = (int) Math.ceil( - (double) cat.getItems().size() - / PAGE_SIZE); - if (total < 1) total = 1; - if (page < 0) page = 0; - if (page >= total) page = total - 1; - viewPage.put(p.getUniqueId(), page); - - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l" + cat.getName()); - - - int start = page * PAGE_SIZE; - int end = Math.min(start + PAGE_SIZE, - cat.getItems().size()); - - for (int i = start; i < end; i++) { - int idx = i - start; - if (idx >= ITEM_SLOTS.length) break; - - // ★ item 保留原名,代表 ShopItem 数据 - ShopItem item = cat.getItems().get(i); - - // ★ is 是带完整元数据的 ItemStack(用于GUI图标) - ItemStack is = getShopStack(item, 1); - ItemMeta im = is.getItemMeta(); - - if (im != null) { - im.setDisplayName("§e§l" - + item.getDisplayName()); - List lore = new ArrayList<>(); - lore.add("§7购入: §a" - + item.getBuyPrice() - + "§7枚债券"); - lore.add("§7售出: §e" - + item.getEffectiveSellPrice() - + "§7枚债券"); - if (item.getStock() >= 0) - lore.add("§7库存: §f" - + item.getStock()); - else - lore.add("§7库存: §a无限"); - lore.add(""); - int disc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (disc > 0) { - int dis = item.getBuyPrice() - * (100 - disc) / 100; - lore.add("§7优惠价: §6" - + dis + "枚" - + " §7(-" + disc + "%)"); - } - lore.add("§a左键买1 §6右键卖1"); - lore.add("§aShift+左键买64" - + " §6Shift+右键卖64"); - im.setLore(lore); - is.setItemMeta(im); - } - - // ★ 用 is(ItemStack)放入GUI格子 - g.setItem(ITEM_SLOTS[idx], is); - } - - - g.setItem(4, mkItem(Material.BOOK, - "§e§l" + cat.getName(), - "§7共 " + cat.getItems().size() - + " 种商品")); - g.setItem(48, page > 0 - ? mkItem(Material.ARROW, - "§7上一页") - : mkItem(Material - .BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(49, mkItem(Material.PAPER, - "§e" + (page + 1) + "/" + total)); - g.setItem(50, page < total - 1 - ? mkItem(Material.ARROW, - "§7下一页") - : mkItem(Material - .BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(53, mkItem(Material.ARROW, - "§7返回商店")); - - p.openInventory(g); - } - - // ===== GUI: 退款 ===== - - public void openRefundPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§c§l退款中心"); - - // 位移检测:比对上次记录的背包内容 - validateRefunds(p); - - // 过期清理 - synchronized (refundRecords) { - refundRecords.removeIf(RefundRecord::isExpired); - } - - int idx = 0; - synchronized (refundRecords) { - for (RefundRecord r : refundRecords) { - if (idx >= 45) break; - if (!r.playerUUID.equals(p.getUniqueId())) continue; - if (r.isExpired()) continue; - - // 盒装用潜影盒图标,普通用商品图标 - Material iconMat = (r.shulkerMaterial != null) - ? r.shulkerMaterial : r.material; - - ItemStack is = new ItemStack(iconMat); - ItemMeta im = is.getItemMeta(); - if (im != null) { - int secs = r.getSecondsLeft(); - im.setDisplayName("§e" + r.itemName - + (r.shulkerMaterial != null - ? " §7[盒装]" : " x" + r.amount)); - - List lore = new ArrayList<>(); - lore.add("§7订单: §f#" + r.timestamp); - lore.add("§7支付: §a" + r.totalPaid - + "§7枚债券"); - - if (r.shulkerMaterial != null) { - int boxCount = countShulkers(p, - r.shulkerMaterial); - lore.add("§7潜影盒: §f" - + (boxCount >= 1 - ? "§a在背包中" - : "§c已移出")); - } else { - int have = countDirect(p, r.material); - lore.add("§7物品: §f" + have - + "/" + r.amount); - } - - lore.add("§7剩余: §e" + secs + "秒"); - lore.add(""); - lore.add("§c点击退款"); - im.setLore(lore); - } - is.setItemMeta(im); - g.setItem(10 + idx, is); - idx++; - } - } - - if (idx == 0) { - g.setItem(22, mkItem(Material.BARRIER, - "§7暂无可退款记录")); - } - - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - - p.openInventory(g); - } - // 统计背包中某物品数量 - private int countInInventory(Player p, Material mat) { - int count = 0; - for (ItemStack is : p.getInventory().getContents()) { - if (is != null && is.getType() == mat) { - count += is.getAmount(); - } - } - // 潜影盒也要检查里面 - for (ItemStack is : p.getInventory().getContents()) { - if (is != null - && is.getType().name() - .endsWith("_SHULKER_BOX")) { - count += is.getAmount(); - } - } - return count; - } - - // 只统计背包中直接可见的数量(不含盒内) - // 新代码:增加按完整物品匹配的方法 - private int countDirect(Player p, Material mat) { - int count = 0; - for (ItemStack is : - p.getInventory().getContents()) { - if (is != null && is.getType() == mat) - count += is.getAmount(); - } - return count; - } - - private int countDirectByStack(Player p, - ItemStack target) { - int count = 0; - for (ItemStack is : - p.getInventory().getContents()) { - if (is != null && is.isSimilar(target)) - count += is.getAmount(); - } - return count; - } - // 统计背包中潜影盒数量 - private int countShulkers(Player p, Material shulker) { - if (shulker == null) return Integer.MAX_VALUE; - int count = 0; - for (ItemStack is : p.getInventory().getContents()) { - if (is != null && is.getType() == shulker) - count += is.getAmount(); - } - return count; - } - - // 退款检测:位移 OR 消耗 → 不能退 - private void validateRefunds(Player p) { - synchronized (refundRecords) { - Iterator it = - refundRecords.iterator(); - while (it.hasNext()) { - RefundRecord r = it.next(); - if (!r.playerUUID.equals(p.getUniqueId())) - continue; - if (r.isExpired()) { - it.remove(); - continue; - } - - if (r.shulkerMaterial != null) { - // 盒装:检测潜影盒是否还在背包 - int now = countShulkers( - p, r.shulkerMaterial); - if (now < 1) { - it.remove(); - p.sendMessage("§c[退款] " + r.itemName - + " 潜影盒已移出背包,无法退款"); - } - } else { - // 普通:检测商品是否还在背包 - int now = countDirect(p, r.material); - if (now < r.amount) { - it.remove(); - p.sendMessage("§c[退款] " + r.itemName - + " 物品已被使用或移出,无法退款"); - } - } - } - } - } - - - public int getPlayerPage(java.util.UUID uuid) { - return viewPage.getOrDefault(uuid, 0); - } - - public ShopCategory getPlayerCategory(java.util.UUID uuid) { - return viewCat.get(uuid); - } - - // ===== 供GUIManager调用:商店主界面点击 ===== - public boolean handleMainShopClick(Player p, int raw) { - if (raw == 49) { plugin.getOrderManager().openOrderCenter(p); return true; } - - if (raw == 48) { - toggleCartMode(p); - openShopMain(p); - return true; - } - if (raw == 50) { - openCart(p); - return true; - } - if (raw == 53) { - plugin.getGui().openMain(p); - return true; - } - - // ★ 新增:优惠券按钮 - if (raw == 47) { - openCouponInput(p); - return true; - } - if (raw == 49) { plugin.getOrderManager().openOrderCenter(p); return true;} - int idx = slotToIndex(raw); - if (idx < 0 || idx >= categories.size()) - return true; - openCategory(p, categories.get(idx), 0); - return true; - } - - // ===== 供GUIManager调用:退款中心点击 ===== - public boolean handleRefundClick(Player p, int raw) { - if (raw == 49) { openShopMain(p); return true; } - return true; - } - - // ===== 供GUIManager调用:分类页点击 ===== - public boolean handleCategoryClick(Player p, String title, - int raw, boolean left, - boolean shift) { - // plugin.getLogger().info("[Shop] ★ handleCategoryClick raw=" + raw); - - ShopManager sm = this; - ShopCategory match = null; - for (ShopCategory c : categories) { - if (title.equals("§6§l" + c.getName())) { - match = c; - break; - } - } - if (match == null) { - // plugin.getLogger().info("[Shop] ★ 匹配失败, title=[" + title + "]"); - return false; - } - // plugin.getLogger().info("[Shop] ★ 匹配到: " + match.getName()); - - viewCat.put(p.getUniqueId(), match); - - if (raw == 53) { openShopMain(p); return true; } - int pg = getPlayerPage(p.getUniqueId()); - if (raw == 48) { openCategory(p, match, pg - 1); return true; } - if (raw == 50) { openCategory(p, match, pg + 1); return true; } - - int itemIdx = slotToIndex(raw); - if (itemIdx < 0) { - // plugin.getLogger().info("[Shop] ★ slot=" + raw + " 不是商品格"); - return true; - } - int actual = itemIdx + pg * PAGE_SIZE; - if (actual < 0 || actual >= match.getItems().size()) { - // plugin.getLogger().info("[Shop] ★ 超出范围 actual=" + actual + " size=" + match.getItems().size()); - return true; - } - ShopItem si = match.getItems().get(actual); - // plugin.getLogger().info("[Shop] ★ 准备购买: " + si.getId() + " left=" + left); - - int amount = shift ? 64 : 1; - if (left) { - if (shift && isCartMode(p)) { addToCart(p, si); } - else if (shift) { buyItem(p, si, 64); } - else if (isCartMode(p)) { addToCart(p, si); } - else { buyItem(p, si, 1); } - } else { - if (shift) { sellAllOf(p, si); } - else { sellItem(p, si, 1); } - } - openCategory(p, match, pg); - return true; - } - - // ===== 批量出售某商品全部数量 ===== - private void sellAllOf(Player p, ShopItem item) { - if (item == null) return; - // ★ 补上禁止出售检查 - if (item.getEffectiveSellPrice() <= 0) { - p.sendMessage("§c该商品不可出售"); - return; - } - if (item == null) return; - Material mat = item.getMaterial(); - int total = 0; - int count = 0; - // 遍历背包清点 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - total += is.getAmount(); - count++; - } - } - if (total <= 0) { - p.sendMessage("§c你没有可出售的 §e" - + item.getDisplayName()); - return; - } - // 清除背包中该物品 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - p.getInventory().clear(i); - } - } - int money = item.getEffectiveSellPrice() * total; - plugin.getBonds().addBonds( - p.getName(), money, "shop_sell", - item.getId(), "商店系统", - "批量出售" + item.getDisplayName() - + " x" + total); - if (item.getStock() >= 0) - item.setStock(item.getStock() + total); - p.sendMessage("§a批量出售: §e" - + item.getDisplayName() - + " x" + total - + " §a+" + money + "枚债券"); - } - - - // ===== 定时 ===== - - private void startHourlyTask() { - new BukkitRunnable() { - @Override - public void run() { - // ★ 汇报交给 SalesStatsManager - if (plugin.getSalesStats() != null) { - plugin.getSalesStats().report(); - } - // 重置小时销量 - for (ShopCategory cat : categories) - for (ShopItem item : cat.getItems()) - item.setHourlySales(0); - saveAll(); - } - }.runTaskTimer(plugin, 72000L, 72000L); - } - - - private void startCleanupTask() { - new BukkitRunnable() { - @Override - public void run() { - synchronized (refundRecords) { - refundRecords.removeIf( - RefundRecord::isExpired); - } - } - }.runTaskTimer(plugin, 1200L, 1200L); - } - - // ===== 工具 ===== - - private int slotToIndex(int slot) { - for (int i = 0; i < ITEM_SLOTS.length; i++) - if (ITEM_SLOTS[i] == slot) return i; - return -1; - } - - private ItemStack mkItem(Material mat, - String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/src/main/java/Sdf1_login/TicketManager.java b/backup/src/main/java/Sdf1_login/TicketManager.java deleted file mode 100644 index c21edc2..0000000 --- a/backup/src/main/java/Sdf1_login/TicketManager.java +++ /dev/null @@ -1,1644 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class TicketManager { - - private final Main plugin; - - public static final String T_TICKET_MAIN = - "§6§l工单中心"; - public static final String T_TICKET_CREATE = - "§e§l创建工单"; - public static final String T_TICKET_MY = - "§b§l我的工单"; - public static final String T_TICKET_ADMIN = - "§c§l工单管理"; - public static final String T_TICKET_DETAIL = - "§a§l工单详情"; - - public static final String TYPE_BUG = - "bug反馈"; - public static final String TYPE_HELP = - "求助"; - public static final String TYPE_REPORT = - "举报"; - public static final String TYPE_APPLY = - "申请"; - public static final String TYPE_OTHER = - "其他"; - public static final String TYPE_DELETE = - "删号"; - public static final String TYPE_PASSWORD = - "修改密码"; - - - - private static final Map - PRICES = new LinkedHashMap<>(); - static { - PRICES.put(TYPE_BUG, 10); - PRICES.put(TYPE_HELP, 5); - PRICES.put(TYPE_REPORT, 8); - PRICES.put(TYPE_APPLY, 15); - PRICES.put(TYPE_OTHER, 5); - PRICES.put(TYPE_DELETE, 0); - PRICES.put(TYPE_PASSWORD, 0); - } - - - public TicketManager(Main plugin) { - this.plugin = plugin; - } - - // ========== GUI ========== - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_MAIN); - fillBg(g); - g.setItem(10, mk(Material.PAPER, - "§e创建工单", - "§7提交bug、求助、举报等")); - g.setItem(12, mk(Material.BOOK, - "§b我的工单", - "§7查看已提交的工单")); - g.setItem(14, mk(Material.CHEST, - "§6工单记录", - "§7查看历史工单")); - if (adm(p)) { - g.setItem(16, mk(Material.REDSTONE, - "§c工单管理", - "§7管理员专用")); - } - if (plugin.getDb() - .isServiceProvider( - p.getName())) { - g.setItem(18, mk(Material.EMERALD, - "§a服务商面板", - "§7接单、处理工单")); - } - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openCreate(Player p) { - Inventory g = Bukkit.createInventory( - null, 36, T_TICKET_CREATE); - fillBg(g); - g.setItem(10, mk(Material.BOOK, - "§cbug反馈")); - g.setItem(11, mk(Material.EMERALD, - "§a求助")); - g.setItem(12, mk(Material.BLAZE_POWDER, - "§6举报")); - g.setItem(13, mk(Material.NAME_TAG, - "§e申请")); - g.setItem(14, mk(Material.PAPER, - "§7其他")); - g.setItem(20, mk(Material.BARRIER, - "§c申请删号", - "§7申请删除账号")); - g.setItem(21, mk(Material.NETHER_WART, - "§d申请改密", - "§7请求管理员协助改密")); - g.setItem(31, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyTickets(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_MY); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByPlayer( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openDetail(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_DETAIL); - fillBg(g); - - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = sv(t, "requester"); - String title = sv(t, "title"); - String desc = sv(t, "description"); - String assigned = sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - int confirmed = nv(t, "admin_confirmed"); - - g.setItem(10, mk(Material.PAPER, - "§e§l" + (title.isEmpty() - ? type : title), - "§7类型: §f" + type, - "§7状态: " + stxt(status), - "§7提交者: §f" + requester, - "§7奖励: §e" + reward + " 积分")); - - g.setItem(12, mk(Material.BOOK, - "§7详细描述", - desc.isEmpty() ? "§7无描述" - : "§7" + (desc.length() > 40 - ? desc.substring(0, 40) - + "..." : desc))); - - if (!assigned.isEmpty()) { - g.setItem(14, mk(Material.PLAYER_HEAD, - "§7处理人: §f" + assigned, - confirmed == 1 - ? "§7评分: §e" + score - : "§7处理中")); - } else { - g.setItem(14, mk(Material.BARRIER, - "§7未分配")); - } - - // 驳回原因 - if ("closed".equals(status)) { - String reason = sv(t, "reject_reason"); - if (!reason.isEmpty()) { - g.setItem(13, mk(Material.BARRIER, - "§c驳回原因", - "§7" + reason)); - } - } - - // 回复按钮(非关闭/已完结状态) - if (!"closed".equals(status) - && !"withdrawn".equals(status) - && !"resolved".equals(status)) { - g.setItem(15, mk( - Material.WRITABLE_BOOK, - "§b回复工单", - "§7向对方发送消息")); - g.setItem(16, mk(Material.BOOK, - "§7查看回复记录")); - } - - // ===== 管理员按钮 ===== - if (adm(p)) { - if ("pending".equals(status)) { - // 待分配:可分配给服务商 - g.setItem(19, mk(Material.EMERALD, - "§a分配工单", - "§7指定服务商处理")); - // 也可自己处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && !assigned.equals("admin") - && !assigned.isEmpty()) { - // 服务商在处理:可取消指派收回 - g.setItem(19, mk(Material.BARRIER, - "§c取消指派", - "§7取消服务商指派,收回工单")); - // 也可自己接手处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && assigned.equals("admin")) { - // 管理员自己在处理:可标记完结 - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - if ("replied".equals(status) - && assigned.equals("admin")) { - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - // 驳回(非已关闭/已完结状态) - if (!"closed".equals(status) - && !"resolved".equals(status) - && !"withdrawn".equals(status)) { - g.setItem(21, mk(Material.RED_DYE, - "§c驳回工单", - "§7驳回并关闭此工单")); - } - } - - // ===== 服务商按钮 ===== - if (plugin.getDb() - .isServiceProvider(p.getName()) - && !TYPE_DELETE.equals(type) - && !TYPE_PASSWORD.equals(type)) { - if ("pending".equals(status) - && assigned.isEmpty()) { - // 待分配无人:抢单 - g.setItem(19, mk( - Material.GOLD_INGOT, - "§6抢单", - "§7接取此工单")); - } - if (assigned.equals(p.getName()) - && ("processing".equals(status) - || "replied".equals(status))) { - // 我在处理:标记完结/上报异常 - g.setItem(19, mk( - Material.LIME_DYE, - "§a标记完结", - "§7上报处理完成")); - g.setItem(20, mk( - Material.REDSTONE, - "§c上报异常", - "§7报告处理中的问题")); - } - } - - g.setItem(22, mk(Material.ARROW, - "§7返回")); - - ItemStack info = g.getItem(10); - if (info != null) { - ItemMeta im = info.getItemMeta(); - if (im != null) { - im.setCustomModelData(tid); - info.setItemMeta(im); - } - } - p.openInventory(g); - } - - - public void openAdminList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_ADMIN); - fillBg(g); - g.setItem(45, mk(Material.EMERALD, - "§a邀请服务商")); - g.setItem(46, mk(Material.BARRIER, - "§c移除服务商")); - g.setItem(47, mk(Material.CLOCK, - "§e服务商列表")); - g.setItem(48, mk(Material.NETHER_STAR, - "§e直接派单", - "§7管理员直接创建并派单")); - List> all = - new ArrayList<>(); - all.addAll(plugin.getDb() - .getTicketsByStatus("pending")); - all.addAll(plugin.getDb() - .getTicketsByStatus("processing")); - all.addAll(plugin.getDb() - .getTicketsByStatus("replied")); - all.addAll(plugin.getDb() - .getTicketsByStatus("completed")); - all.addAll(plugin.getDb() - .getTicketsByStatus("resolved")); - all.addAll(plugin.getDb() - .getTicketsByStatus("closed")); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - - public void openProviderPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, "§d§l服务商面板"); - fillBg(g); - g.setItem(10, mk(Material.CHEST, - "§e抢单大厅", - "§7查看可接工单")); - g.setItem(12, mk(Material.CLOCK, - "§b我的工单", - "§7我正在处理的工单")); - g.setItem(14, mk(Material.PAPER, - "§6历史工单", - "§7已完成的工单")); - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openGrabList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l抢单大厅"); - fillBg(g); - List> pending = - plugin.getDb() - .getTicketsByStatus( - "pending"); - List> list = - new ArrayList<>(); - for (Map t : pending) { - if (sv(t, "assigned_to").isEmpty()) { - list.add(t); - } - } - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyAssigned(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§b§l我处理中的工单"); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByProvider( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - // ========== 业务 ========== - - public int submitTicket(Player p, - String type, String title, - String desc) { - int price = PRICES - .getOrDefault(type, 5); - int id = plugin.getDb().createTicket( - type, "pending", "normal", - p.getName(), title, desc, - price, ""); - if (id > 0) { - if (TYPE_DELETE.equals(type) - || TYPE_PASSWORD.equals(type)) { - plugin.getDb().updateTicketField( - id, "assigned_to", "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage( - "§a§l[工单] §f工单 #" - + id + " 已提交," - + "等待管理员处理"); - } else { - p.sendMessage( - "§a§l[工单] §f创建成功!" - + " #§e" + id - + " 奖励: §e" - + price + "积分"); - } - notifyAdmins(p.getName(), id); - } else { - p.sendMessage("§c创建失败"); - } - return id; - } - - - public void adminCreateAndAssign( - Player p, String type, - String title, String desc, - String provider, int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - int id = plugin.getDb().createTicket( - type, "processing", "normal", - p.getName(), title, desc, - price, ""); - if (id <= 0) { - p.sendMessage("§c创建失败"); - return; - } - plugin.getDb().assignTicket( - id, provider); - if (price > 0) { - plugin.getDb().updateTicketField( - id, "reward_amount", price); - } - p.sendMessage("§a工单 #" + id - + " 已创建并分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f管理员指派工单 #" - + id + " 给您"); - playAssignedSound(tp); - } - } - - public void grabTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!"pending".equals( - sv(t, "status"))) { - p.sendMessage("§c该工单已被接单"); - return; - } - if (!sv(t, "assigned_to").isEmpty()) { - p.sendMessage("§c该工单已被分配"); - return; - } - plugin.getDb().assignTicket( - tid, p.getName()); - p.sendMessage("§a抢单成功!工单 #" + tid); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f您的工单 #" - + tid + " 已被 " - + p.getName() - + " 接单"); - } - } - - public void assignToProvider(Player p, - int tid, String provider, - int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - plugin.getDb().assignTicket( - tid, provider); - if (price > 0) { - plugin.getDb().updateTicketStatus( - tid, "processing"); - plugin.getDb().updateTicketField( - tid, "reward_amount", price); - } - p.sendMessage("§a工单 #" + tid - + " 已分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f您有新工单: #" - + tid); - playAssignedSound(tp); - } - } - - public void confirmAndScore(Player p, - int tid, int score) { - if (score < 1 || score > 5) { - p.sendMessage("§c评分1-5"); - return; - } - plugin.getDb().confirmTicket( - tid, score); - p.sendMessage("§a工单 #" + tid - + " 已确认,评分: " + score); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已确认完结"); - } - } - settleTicket(tid); - } - - public void completeAsProvider(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "assigned_to") - .equals(p.getName())) { - p.sendMessage("§c这不是你的工单"); - return; - } - plugin.getDb().completeTicket(tid); - p.sendMessage("§a工单 #" + tid - + " 已标记完结,等待确认"); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已处理完成"); - } - } - - public void rejectTicket(Player p, - int tid, String reason) { - plugin.getDb().updateTicketStatus( - tid, "closed"); - plugin.getDb().updateTicketField( - tid, "reject_reason", reason); - p.sendMessage("§c工单 #" + tid - + " 已驳回并关闭"); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 被驳回: " - + reason); - } - } - } - - public void replyTicket(Player p, - int tid, String message) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - String status = sv(t, "status"); - if ("closed".equals(status) - || "withdrawn".equals(status) - || "resolved".equals(status)) { - p.sendMessage("§c该工单已关闭"); - return; - } - plugin.getDb().addTicketReply( - tid, p.getName(), message); - p.sendMessage("§a回复成功"); - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - String sender = p.getName(); - if (!requester.equals(sender)) { - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - if (!assigned.isEmpty() - && !assigned.equals(sender)) { - Player ap = Bukkit.getPlayer( - assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - } - - public void showReplies(Player p, - int tid) { - List> replies = - plugin.getDb() - .getTicketReplies(tid); - if (replies.isEmpty()) { - p.sendMessage("§7暂无回复"); - return; - } - p.sendMessage("§e§l=== 回复记录 ==="); - SimpleDateFormat sdf = - new SimpleDateFormat( - "MM-dd HH:mm"); - for (Map r : replies) { - String sender = sv(r, "sender"); - String msg2 = sv(r, "message"); - long time = ((Number) r.get( - "create_time")).longValue(); - p.sendMessage("§7[" + sdf.format( - new Date(time)) + "] §f" - + sender + "§7: " + msg2); - } - p.sendMessage( - "§e§l================="); - } - - public void withdrawTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "requester") - .equals(p.getName()) - && !adm(p)) { - p.sendMessage("§c无权操作"); - return; - } - plugin.getDb().updateTicketStatus( - tid, "withdrawn"); - p.sendMessage("§a工单 #" + tid - + " 已撤回"); - } - - public void showProviderList(Player p) { - List> list = - plugin.getDb() - .getAllServiceProviders(); - if (list.isEmpty()) { - p.sendMessage("§7暂无服务商"); - return; - } - p.sendMessage( - "§e§l=== 服务商列表 ==="); - for (Map pro : list) { - p.sendMessage("§7- §f" - + pro.get("player_name") - + " §7角色: §e" - + pro.get("role")); - } - p.sendMessage( - "§e§l==================="); - } - - public void autoProcessCompleted() { - long timeoutMs = 10 * 60 * 1000L; - - // 自动处理删号工单(5分钟自动通过) - List> deleteTickets = - plugin.getDb() - .getTicketsByStatus( - "processing"); - for (Map t : deleteTickets) { - String dtype = sv(t, "type"); - if (!TYPE_DELETE.equals(dtype)) continue; - int did = nv(t, "id"); - int dconf = nv(t, "admin_confirmed"); - // 已经处理过了跳过 - if (dconf != 0) continue; - String dasg = sv(t, "assigned_to"); - // 只处理分配给admin的 - if (!"admin".equals(dasg)) continue; - long createTime = ((Number) t.get( - "create_time")).longValue(); - if (System.currentTimeMillis() - - createTime >= 5 * 60 * 1000L) { - processDeleteAccount(did, t); - } - } - - - // 已完结未确认→10分钟自动完结 - List> list = - plugin.getDb() - .getUnconfirmedCompleted( - timeoutMs); - for (Map t : list) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 已自动完结"); - String assigned = - sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) - pp.sendMessage( - "§e§l[工单] §f工单 #" - + id - + " 已自动完结"); - } - settleTicket(id); - } - - // 已回复但10分钟无互动→自动完结 - List> stale = - plugin.getDb() - .getRepliedStaleTickets( - timeoutMs); - for (Map t : stale) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 超时自动完结"); - settleTicket(id); - } - } - - - - // ========== GUI点击 ========== - public boolean handleClick(Player p, - String title, int slot) { - - if (title.equals(T_TICKET_MAIN)) { - if (slot == 10) openCreate(p); - else if (slot == 12) - openMyTickets(p); - else if (slot == 14) - openMyTickets(p); - else if (slot == 16) { - if (adm(p)) openAdminList(p); - else p.sendMessage("§c权限不足"); - } - else if (slot == 18) { - if (plugin.getDb() - .isServiceProvider(p.getName())) { - openProviderPanel(p); - } else { - p.sendMessage("§c权限不足"); - } - } - else if (slot == 22) - plugin.getGui().openMain(p); - return true; - } - - if (title.equals(T_TICKET_CREATE)) { - if (slot == 31) { - openMain(p); - return true; - } - String type = null; - if (slot == 10) type = TYPE_BUG; - else if (slot == 11) type = TYPE_HELP; - else if (slot == 12) type = TYPE_REPORT; - else if (slot == 13) type = TYPE_APPLY; - else if (slot == 14) type = TYPE_OTHER; - else if (slot == 20) type = TYPE_DELETE; - else if (slot == 21) type = TYPE_PASSWORD; - if (type != null) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_TITLE; - plugin.getChatInput() - .getState(p) - .ticketType = type; - if (TYPE_DELETE.equals(type)) { - p.sendMessage("§e请描述删号原因:"); - } else if (TYPE_PASSWORD.equals(type)) { - p.sendMessage("§e请描述需要协助的内容:"); - } else { - p.sendMessage("§e请输入工单标题:"); - } - } - return true; - } - - - if (title.equals(T_TICKET_MY)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals(T_TICKET_DETAIL)) { - return handleClickDetail(p, slot); - } - - if (title.equals(T_TICKET_ADMIN)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot == 45) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_INVITE_PROVIDER; - p.sendMessage("§e输入服务商玩家名:"); - return true; - } - if (slot == 46) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REMOVE_PROVIDER; - p.sendMessage("§e输入要移除的服务商:"); - return true; - } - if (slot == 47) { - showProviderList(p); - return true; - } - if (slot == 48) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ADMIN_TYPE; - p.sendMessage("§e选择工单类型:"); - p.sendMessage("§7bug/求助/举报/申请/其他"); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§d§l服务商面板")) { - if (slot == 22) { - plugin.getGui().openMain(p); - return true; - } - if (slot == 10) openGrabList(p); - else if (slot == 12) openMyAssigned(p); - else if (slot == 14) openMyTickets(p); - return true; - } - - if (title.equals("§6§l抢单大厅")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§b§l我处理中的工单")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - return false; - } - - private boolean handleClickDetail(Player p, - int slot) { - if (slot == 22) { - openMyTickets(p); - return true; - } - int id = gtFromDetail(p); - if (id <= 0) return true; - Map t = - plugin.getDb().getTicket(id); - if (t == null) return true; - - String st = sv(t, "status"); - int conf = nv(t, "admin_confirmed"); - String asg = sv(t, "assigned_to"); - - // 回复按钮 - if (slot == 15) { - if (!"closed".equals(st) - && !"withdrawn".equals(st) - && !"resolved".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPLY; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入回复内容:"); - } - return true; - } - - // 查看回复记录 - if (slot == 16) { - showReplies(p, id); - return true; - } - - // slot 19 - if (slot == 19) { - // 管理员:分配工单 - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ASSIGN; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage( - "§e输入: 服务商名 价格"); - return true; - } - // 管理员:取消指派(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - cancelTicket(p, id); - openDetail(p, id); - return true; - } - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - Map tk = - plugin.getDb().getTicket(id); - String tkType = sv(tk, "type"); - if (TYPE_DELETE.equals(tkType)) { - approveDeleteTicket(p, id); - return true; - } - plugin.getDb().completeTicket(id); - p.sendMessage("§a工单 #" + id - + " 已标记完结"); - openDetail(p, id); - return true; - } - } - // 服务商:抢单 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && "pending".equals(st) - && asg.isEmpty()) { - grabTicket(p, id); - openDetail(p, id); - return true; - } - // 服务商:标记完结 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - completeAsProvider(p, id); - openDetail(p, id); - return true; - } - return true; - } - - // slot 20 - if (slot == 20) { - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:自己接手(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 服务商:上报异常 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPORT_ISSUE; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入异常描述:"); - return true; - } - return true; - } - - // slot 21:驳回 - if (slot == 21) { - if (adm(p) && !"closed".equals(st) - && !"resolved".equals(st) - && !"withdrawn".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REJECT; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入驳回原因:"); - } - return true; - } - - return true; - } - - // ========== 聊天输入 ========== - - public boolean handleChatInput(Player p, - String msg, - ChatInputManager.InputType type) { - switch (type) { - case TICKET_TITLE: { - plugin.getChatInput() - .getState(p) - .ticketTitle = msg; - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return true; - } - case TICKET_DESC: { - String tt = plugin.getChatInput() - .getState(p).ticketType; - String tl = plugin.getChatInput() - .getState(p).ticketTitle; - plugin.getChatInput().reset(p); - submitTicket(p, tt, tl, msg); - return true; - } - case TICKET_SCORE: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - Map tk = - plugin.getDb() - .getTicket(tid2); - String st = tk != null - ? sv(tk, "status") - : ""; - if ("completed".equals(st)) { - plugin.getChatInput() - .reset(p); - confirmAndScore( - p, tid2, sc); - } else { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c工单尚未处理完成"); - } - } catch ( - NumberFormatException e) { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return true; - } - case TICKET_REJECT: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - plugin.getChatInput().reset(p); - rejectTicket(p, tid2, msg); - return true; - } - case TICKET_ASSIGN: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - plugin.getChatInput().reset(p); - assignToProvider(p, tid2, - prov, price); - runSync(() -> - openDetail(p, tid2)); - return true; - } - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return true; - } - plugin.getDb() - .addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return true; - } - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .isServiceProvider( - tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return true; - } - plugin.getDb() - .removeServiceProvider( - tgt); - p.sendMessage( - "§a已移除 " + tgt); - return true; - } - default: - return false; - } - } - - // ========== 私有 ========== - - private void settleTicket(int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) return; - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - ConfigManager cfg = - plugin.getConfig2(); - if (reward > 0) { - plugin.getDb().addPoints( - requester, reward); - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f获得 " - + reward - + " 积分奖励"); - } - } - if (!assigned.isEmpty()) { - int pts = (int) (score - * cfg.providerPointsPerScore); - if (pts > 0) { - plugin.getDb().addPoints( - assigned, pts); - } - double money = - cfg.baseEconomyReward - + score - * cfg.providerEconomyPerScore; - boolean ok = false; - try { - if (plugin.getEconomy() != null) { - OfflinePlayer op = - Bukkit.getOfflinePlayer( - assigned); - plugin.getEconomy() - .depositPlayer( - op, money); - ok = true; - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 经济失败: " - + e.getMessage()); - } - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) { - pp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 确认完结"); - if (pts > 0) - pp.sendMessage("§7积分: §e" - + pts); - if (ok) - pp.sendMessage("§7经济: §a$" - + String.format( - "%.0f", money)); - } - } - } - - private void notifyAdmins(String creator, - int tid) { - String tag = - plugin.getConfig2().adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§e§l[工单] §f" + creator - + " 提交了新工单 #" - + tid); - } - } - } - - private void playAssignedSound(Player p) { - for (int i = 0; i < 3; i++) { - final int d = i * 15; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline()) { - 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); - } - }, d); - } - } - - private void runSync(Runnable task) { - Bukkit.getScheduler() - .runTask(plugin, task); - } - - private boolean adm(Player p) { - return p.getScoreboardTags().contains( - plugin.getConfig2().adminTag); - } - - private void fillBg(Inventory g) { - ItemStack glass = mk( - Material.GRAY_STAINED_GLASS_PANE, - " "); - for (int i = 0; i < g.getSize(); i++) { - if (g.getItem(i) == null - || g.getItem(i).getType() - == Material.AIR) { - g.setItem(i, glass); - } - } - } - - private void fillList(Inventory g, - List> list) { - if (list.isEmpty()) { - g.setItem(22, mk(Material.BARRIER, - "§7暂无工单")); - return; - } - int slot = 0; - for (Map t : list) { - if (slot >= 45) break; - g.setItem(slot, makeItem(t)); - slot++; - } - } - /** - * 执行删号流程 - */ - /** - * 执行删号流程 - */ - public void processDeleteAccount(int tid, - Map t) { - String requester = sv(t, "requester"); - plugin.getDb().updateTicketStatus( - tid, "resolved"); - plugin.getDb().updateTicketField(tid, - "admin_confirmed", 1); - Player target = - Bukkit.getPlayer(requester); - if (target != null && target.isOnline()) { - plugin.backupInventory(target); - target.sendMessage( - "§a§l[工单] §f您的删号请求已通过"); - target.sendMessage( - "§7物品已备份,下次登录可取回"); - target.kickPlayer( - "§a删号成功,物品已备份。\n" - + "请等待5分钟后重新注册取回物品。"); - } else { - // 离线:仅标记,不操作数据库 - plugin.getLogger().info( - "[Sdf1_login] " + requester - + " 删号工单已通过,等待上线执行"); - } - // 延迟执行删号 - final String name = requester; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - plugin.getDb().deleteUser(name); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 删号工单已执行"); - }, 100L); - } - - /** - * 管理员手动批准删号 - */ - public void approveDeleteTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!TYPE_DELETE.equals(sv(t, "type"))) { - p.sendMessage("§c这不是删号工单"); - return; - } - p.sendMessage("§a工单 #" + tid - + " 已批准,正在执行删号..."); - processDeleteAccount(tid, t); - } - - /** - * 管理员取消工单(服务商异常后) - */ - /** - * 管理员取消工单(解除服务商绑定,管理员可自行处理) - */ - public void cancelTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - // 解除服务商绑定,状态回退到pending - plugin.getDb().updateTicketField( - tid, "assigned_to", ""); - plugin.getDb().updateTicketStatus( - tid, "pending"); - p.sendMessage("§a工单 #" + tid - + " 已取消指派,回到待分配状态"); - // 通知原服务商 - String assigned = sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player ap = Bukkit.getPlayer(assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 已被管理员取消指派"); - } - } - } - - - /** - * 服务商查看历史工单 - */ - public void openProviderHistory(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l我的历史工单"); - fillBg(g); - List> all = - plugin.getDb() - .getTicketsByProviderAll( - p.getName()); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - private ItemStack makeItem( - Map t) { - int id = nv(t, "id"); - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = - sv(t, "requester"); - String title = sv(t, "title"); - int reward = nv(t, "reward_amount"); - Material mat; - switch (status) { - case "processing": - mat = Material.YELLOW_DYE; - break; - case "replied": - mat = Material.CYAN_DYE; - break; - case "completed": - mat = Material.LIME_DYE; - break; - case "resolved": - mat = Material.LIME_DYE; - break; - case "closed": - mat = Material.GRAY_DYE; - break; - case "rejected": - mat = Material.RED_DYE; - break; - case "withdrawn": - mat = Material.GRAY_DYE; - break; - default: - mat = Material.ORANGE_DYE; - break; - } - ItemStack item = new ItemStack(mat); - ItemMeta im = item.getItemMeta(); - if (im != null) { - im.setDisplayName("§e#" + id - + " " + (title.isEmpty() - ? type : title)); - List lore = - new ArrayList<>(); - lore.add("§7类型: §f" + type); - lore.add("§7状态: " - + stxt(status)); - lore.add("§7提交者: §f" - + requester); - lore.add("§7奖励: §e" + reward - + " 积分"); - lore.add(""); - lore.add("§7点击查看详情"); - im.setLore(lore); - im.setCustomModelData(id); - item.setItemMeta(im); - } - return item; - } - - private int gt(ItemStack item) { - if (item == null) return -1; - ItemMeta im = item.getItemMeta(); - if (im == null) return -1; - if (im.hasCustomModelData()) - return im.getCustomModelData(); - return -1; - } - - private int gtFromDetail(Player p) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(10); - return gt(it); - } - - private String stxt(String status) { - switch (status) { - case "pending": - return "§e待处理"; - case "processing": - return "§b处理中"; - case "replied": - return "§9已回复"; - case "completed": - return "§a待确认"; - case "resolved": - return "§a已完结"; - case "closed": - return "§7已关闭"; - case "rejected": - return "§c已驳回"; - case "withdrawn": - return "§7已撤回"; - default: - return "§7" + status; - } - } - - private String sv(Map m, - String key) { - Object v = m.get(key); - return v != null - ? String.valueOf(v) : ""; - } - - private int nv(Map m, - String key) { - Object v = m.get(key); - return v instanceof Number - ? ((Number) v).intValue() : 0; - } - - private ItemStack mk(Material mat, - String name, String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) { - List list = - new ArrayList<>(); - for (String l : lore) { - if (!l.isEmpty()) - list.add(l); - } - if (!list.isEmpty()) - im.setLore(list); - } - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/src/main/java/Sdf1_login/UpdateChecker.java b/backup/src/main/java/Sdf1_login/UpdateChecker.java deleted file mode 100644 index c5c01bb..0000000 --- a/backup/src/main/java/Sdf1_login/UpdateChecker.java +++ /dev/null @@ -1,523 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; - -public class UpdateChecker { - - private final Main plugin; - private final String currentVersion; - - // ===== 仓库信息 ===== - private static final String GITHUB_OWNER = - "ypsdf1"; - private static final String GITHUB_REPO = - "sdf1_login"; - private static final String GITEE_OWNER = - "nihaoshidifu"; - private static final String GITEE_REPO = - "sdf1_login"; - - - // API 地址 - private static final String GITHUB_API = - "https://api.github.com/repos/" - + GITHUB_OWNER + "/" - + GITHUB_REPO - + "/tags"; - private static final String GITEE_API = - "https://gitee.com/api/v5/repos/" - + GITEE_OWNER + "/" - + GITEE_REPO - + "/tags"; - - // 仓库页面链接(给用户看的) - private static final String GITHUB_LINK = - "https://github.com/" - + GITHUB_OWNER + "/" - + GITHUB_REPO + "/releases"; - private static final String GITEE_LINK = - "https://gitee.com/" - + GITEE_OWNER + "/" - + GITEE_REPO + "/releases"; - - - // 单次请求超时(毫秒) - private static final int TIMEOUT_MS = 3000; - - // 熔断阈值 - private static final int TRIP_THRESHOLD = 5; - - // ===== 熔断状态(内存态,重启自动清除) ===== - private volatile boolean circuitTripped = - false; - private volatile int failCount = 0; - - // ===== 自动检测禁用(持久化到文件) ===== - private volatile boolean autoCheckDisabled = - false; - private final File disabledFile; - - public UpdateChecker(Main plugin) { - this.plugin = plugin; - this.currentVersion = - plugin.getDescription() - .getVersion(); - this.disabledFile = new File( - plugin.getDataFolder(), - "update_disabled.dat"); - loadDisabledState(); - } - - // ========== 持久化禁用状态 ========== - - private void loadDisabledState() { - if (disabledFile.exists()) { - try (BufferedReader br = - new BufferedReader( - new FileReader( - disabledFile))) { - String line = br.readLine(); - if (line != null) { - autoCheckDisabled = - "true".equals( - line.trim()); - } - } catch (Exception ignored) { - } - } - } - - private void saveDisabledState() { - try (PrintWriter pw = - new PrintWriter( - new FileWriter( - disabledFile))) { - pw.println(autoCheckDisabled - ? "true" : "false"); - } catch (Exception ignored) { - } - } - - // ========== 公开方法 ========== - - /** - * onEnable 调用(异步静默) - * 熔断或禁用则直接跳过 - */ - public void checkOnEnable() { - if (autoCheckDisabled) return; - if (circuitTripped) return; - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doCheck(null, true)); - } - - /** - * /sdf1_login update 调用(异步) - * 忽略熔断和禁用状态,强制检测 - * 只有拿到版号才解除熔断 - */ - public void checkUpdate( - CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> - doCheck(sender, - false)); - } - - // ========== 核心检测逻辑 ========== - private void doCheck(CommandSender sender, - boolean isAuto) { - - if (isAuto) { - if (autoCheckDisabled - || circuitTripped) { - return; - } - } - - // 通道1:GitHub - CheckResult gh = - tryChannel(GITHUB_API, - "GitHub", GITHUB_LINK); - - // 通道2:Gitee - CheckResult ge = - tryChannel(GITEE_API, - "Gitee", GITEE_LINK); - - // 选择成功的结果 - CheckResult chosen = null; - String failMsg = null; - - if (gh.isSuccess()) { - chosen = gh; - } else if (ge.isSuccess()) { - chosen = ge; - } else { - // 双通道均失败 - failCount++; - // 拼接两个通道的失败原因 - StringBuilder sb = - new StringBuilder(); - if (gh.error != null) { - sb.append("§7GitHub: §c") - .append(gh.error); - } - if (ge.error != null) { - if (sb.length() > 0) - sb.append("\n"); - sb.append("§7Gitee: §c") - .append(ge.error); - } - if (failCount >= TRIP_THRESHOLD) { - circuitTripped = true; - plugin.getLogger().warning( - "[Sdf1_login] 更新检测" - + "已熔断,连续失败" - + failCount + "次"); - } - if (sender != null) { - final String msg = - sb.toString(); - final boolean tripped = - circuitTripped; - final int fc = failCount; - Bukkit.getScheduler() - .runTask(plugin, () -> { - sender.sendMessage( - "§c§l[更新] " - + "检测失败"); - sender.sendMessage(msg); - sender.sendMessage( - "§e失败次数: " - + fc + "/" - + TRIP_THRESHOLD); - if (tripped) { - sender.sendMessage( - "§c已熔断," - + "请重启服务器" - + "重置"); - } - }); - } - return; - } - - // 至少一个通道成功 - failCount = 0; - - // 检查禁用信号 - if (isDisableSignal(chosen.version)) { - autoCheckDisabled = true; - saveDisabledState(); - plugin.getLogger().info( - "[Sdf1_login] 远程版本信号" - + " \"" + chosen.version - + "\",自动更新检测已禁用"); - if (sender != null) { - Bukkit.getScheduler() - .runTask(plugin, () -> - sender.sendMessage( - "§e[更新] 远程已" - + "禁用自动" - + "更新检测")); - } - return; - } - - // 手动调用:成功才解除熔断和禁用 - if (!isAuto) { - if (circuitTripped) { - circuitTripped = false; - } - if (autoCheckDisabled) { - autoCheckDisabled = false; - saveDisabledState(); - } - } - - final String src = chosen.source; - final String lnk = chosen.link; - final String rv = chosen.version; - - Bukkit.getScheduler() - .runTask(plugin, () -> { - if (sender == null) return; - - sender.sendMessage( - "§e[更新] 检测成功" - + " via: §a" + src); - sender.sendMessage( - "§e[更新] 本地版本:" - + " §f" - + currentVersion); - sender.sendMessage( - "§e[更新] 远程版本:" - + " §f" + rv); - - if (isNewer(rv, - currentVersion)) { - sender.sendMessage( - "§c[更新] 发现新" - + "版本: " + rv); - sender.sendMessage( - "§a请前往仓库下载" - + "更新:"); - sender.sendMessage( - "§a" + lnk); - } else { - sender.sendMessage( - "§a[更新] 已是" - + "最新版本"); - } - - // 显示另一通道的失败原因 - // (仅信息,不影响结果) - CheckResult other = - (src.equals("GitHub")) - ? ge : gh; - if (!other.isSuccess() - && other.error != null) { - sender.sendMessage( - "§7[更新] " - + other.source - + "不可用: " - + other.error); - } - }); - } - - - // ========== 禁用信号判断 ========== - - private boolean isDisableSignal( - String version) { - if (version == null) return false; - String v = version.trim().toLowerCase(); - return "999".equals(v) - || "关闭".equals(v) - || "false".equals(v) - || "禁用".equals(v); - } - - // ========== HTTP 请求(带超时) ========== - - // ========== 检测结果 ========== - - private static class CheckResult { - final String version; - final String source; - final String link; - final String error; - - CheckResult(String version, - String source, - String link, - String error) { - this.version = version; - this.source = source; - this.link = link; - this.error = error; - } - - boolean isSuccess() { - return version != null; - } - } - -// ========== HTTP 请求 ========== - - private CheckResult tryChannel( - String apiUrl, - String sourceName, - String link) { - HttpURLConnection conn = null; - try { - URL url = new URL(apiUrl); - conn = (HttpURLConnection) - url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty( - "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; " - + "Win64; x64) AppleWebKit/537.36 " - + "(KHTML, like Gecko) " - + "Chrome/120.0.0.0 Safari/537.36"); - conn.setRequestProperty( - "Accept", - "*/*"); - conn.setRequestProperty( - "Accept-Language", - "zh-CN,zh;q=0.9"); - conn.setConnectTimeout(TIMEOUT_MS); - conn.setReadTimeout(TIMEOUT_MS); - conn.setInstanceFollowRedirects(true); - - int code = conn.getResponseCode(); - - if (code == 404) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 未找到 (HTTP 404)"); - } - if (code == 403) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 被限流 (HTTP 403)"); - } - if (code != 200) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": HTTP " + code); - } - - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - conn - .getInputStream())); - StringBuilder sb = - new StringBuilder(); - String line; - while ((line = reader.readLine()) - != null) { - sb.append(line); - } - reader.close(); - - String tag = - parseTagName(sb.toString()); - if (tag == null) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 无法解析版本号"); - } - - return new CheckResult(tag, - sourceName, link, null); - - } catch (java.net - .SocketTimeoutException e) { - return new CheckResult(null, - sourceName, link, - sourceName + ": 连接超时"); - } catch (java.net.ConnectException e) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 连接被拒绝"); - } catch (java.net - .UnknownHostException e) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": DNS解析失败"); - } catch (Exception e) { - return new CheckResult(null, - sourceName, link, - sourceName + ": " - + e.getMessage()); - } finally { - if (conn != null) { - conn.disconnect(); - } - } - } - - - - /** - * 从 JSON 中提取 tag_name - */ - private String parseTagName(String json) { - // tags API 返回数组: [{"name":"v1.1",...}] - // releases API 返回对象: {"tag_name":"v1.1"} - - // 尝试 tag_name(releases格式) - int idx = - json.indexOf("\"tag_name\""); - if (idx >= 0) { - int colon = - json.indexOf(":", idx); - int q1 = - json.indexOf("\"", colon + 1); - int q2 = - json.indexOf("\"", q1 + 1); - if (q2 > q1) { - return cleanTag( - json.substring( - q1 + 1, q2)); - } - } - - // 尝试 "name"(tags格式) - idx = json.indexOf("\"name\""); - if (idx >= 0) { - int colon = - json.indexOf(":", idx); - int q1 = - json.indexOf("\"", colon + 1); - int q2 = - json.indexOf("\"", q1 + 1); - if (q2 > q1) { - return cleanTag( - json.substring( - q1 + 1, q2)); - } - } - - return null; - } - - private String cleanTag(String tag) { - if (tag.startsWith("v") - || tag.startsWith("V")) { - tag = tag.substring(1); - } - return tag.isEmpty() ? null : tag; - } - - - // ========== 版本比较 ========== - - private boolean isNewer(String remote, - String local) { - String[] r = remote.split("\\."); - String[] l = local.split("\\."); - int len = Math.max(r.length, l.length); - for (int i = 0; i < len; i++) { - int ri = i < r.length - ? parseNum(r[i]) : 0; - int li = i < l.length - ? parseNum(l[i]) : 0; - if (ri > li) return true; - if (ri < li) return false; - } - return false; - } - - private int parseNum(String s) { - try { - return Integer.parseInt( - s.replaceAll("[^0-9]", "")); - } catch (Exception e) { - return 0; - } - } -} diff --git a/backup/src/main/java/Sdf1_login/UserGroupManager.java b/backup/src/main/java/Sdf1_login/UserGroupManager.java deleted file mode 100644 index adaf907..0000000 --- a/backup/src/main/java/Sdf1_login/UserGroupManager.java +++ /dev/null @@ -1,370 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.sql.*; -import java.util.*; -import java.util.Base64; -import java.util.concurrent.ConcurrentHashMap; - -public class UserGroupManager { - - private final Main plugin; - private Connection db; - private final Map groupConfigs = new ConcurrentHashMap<>(); - - public UserGroupManager(Main plugin) { - this.plugin = plugin; - initDB(); - loadGroupConfigs(); - startExpiryChecker(); - } - - // ===== DB ===== - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(plugin.getDataFolder(), "usergroup.db"); - db = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS user_group_config (" - + "group_name TEXT PRIMARY KEY," - + "display_name TEXT DEFAULT ''," - + "display_color TEXT DEFAULT '§f'," - + "display_emoji TEXT DEFAULT ''," - + "priority INTEGER DEFAULT 0," - + "is_permanent INTEGER DEFAULT 1," - + "duration_minutes INTEGER DEFAULT 0," - + "pack_url TEXT DEFAULT ''," - + "pack_hash TEXT DEFAULT ''," - + "pack_prompt TEXT DEFAULT 'Custom Resource Pack'," - + "pack_size INTEGER DEFAULT 0)"); - st.execute("CREATE TABLE IF NOT EXISTS user_group_member (" - + "player_name TEXT NOT NULL," - + "group_name TEXT NOT NULL," - + "added_by TEXT DEFAULT 'system'," - + "added_time INTEGER DEFAULT 0," - + "expiry_time INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, group_name))"); - st.close(); - } catch (Exception e) { - throw new RuntimeException("[UserGroup] DB init failed: " + e.getMessage(), e); - } - } - - // ===== 配置加载 ===== - - public void loadGroupConfigs() { - groupConfigs.clear(); - File dir = new File(plugin.getDataFolder(), "用户组"); - if (!dir.exists()) dir.mkdirs(); - File[] files = dir.listFiles((d, n) -> n.endsWith(".txt")); - if (files == null) return; - - for (File f : files) { - try { - List lines = java.nio.file.Files.readAllLines( - f.toPath(), StandardCharsets.UTF_8); - UserGroupConfig cfg = parseGroupConfig(lines); - if (cfg != null) { - groupConfigs.put(cfg.name, cfg); - saveGroupConfigToDB(cfg); - } - } catch (Exception e) { - plugin.getLogger().warning("[UserGroup] Failed to load: " + f.getName()); - } - } - } - - private UserGroupConfig parseGroupConfig(List lines) { - UserGroupConfig cfg = new UserGroupConfig(); - boolean inBlock = false; - - for (String raw : lines) { - String l = raw; - - // 块注释 - if (l.contains("")) { inBlock = false; continue; } - if (l.contains("/*")) { inBlock = true; continue; } - if (l.contains("*/")) { inBlock = false; continue; } - if (inBlock) continue; - - // 行注释 - l = l.trim(); - if (l.isEmpty()) continue; - if (l.startsWith("#") || l.startsWith("//")) continue; - - // 行内注释 - int h = l.indexOf('#'); - int s = l.indexOf("//"); - if (h >= 0) l = l.substring(0, h).trim(); - if (s >= 0 && (h < 0 || s < h)) l = l.substring(0, s).trim(); - if (l.isEmpty()) continue; - - // 解析键值对 - String key = l; - String val = ""; - int idx = l.indexOf('\uff1a'); - if (idx < 0) idx = l.indexOf(':'); - if (idx >= 0) { - key = l.substring(0, idx).trim(); - val = l.substring(idx + 1).trim(); - if (val.startsWith("\"") && val.endsWith("\"")) - val = val.substring(1, val.length() - 1); - } - - switch (key) { - case "组名": - case "name": cfg.name = val; break; - case "显示名": - case "display": cfg.displayName = val; break; - case "颜色": - case "color": cfg.displayColor = val; break; - case "符号": - case "emoji": cfg.displayEmoji = val; break; - case "优先级": - case "priority": - try { cfg.priority = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "类型": - case "type": - cfg.permanent = val.equalsIgnoreCase("永久") || val.equalsIgnoreCase("permanent"); - break; - case "时长": - case "duration": - try { cfg.durationMinutes = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "资源包URL": - case "pack_url": cfg.packUrl = val; break; - case "资源包Hash": - case "pack_hash": cfg.packHash = val; break; - case "资源包提示": - case "pack_prompt": cfg.packPrompt = val; break; - case "资源包大小": - case "pack_size": - try { cfg.packSize = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "本地文件": - case "local_file": cfg.localFile = val; break; - } - } - - if (cfg.name == null || cfg.name.isEmpty()) return null; - return cfg; - } - - private void saveGroupConfigToDB(UserGroupConfig cfg) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO user_group_config " - + "(group_name, display_name, display_color, display_emoji," - + " priority, is_permanent, duration_minutes," - + " pack_url, pack_hash, pack_prompt, pack_size)" - + " VALUES (?,?,?,?,?,?,?,?,?,?,?)"); - ps.setString(1, cfg.name); - ps.setString(2, cfg.displayName); - ps.setString(3, cfg.displayColor); - ps.setString(4, cfg.displayEmoji); - ps.setInt(5, cfg.priority); - ps.setInt(6, cfg.permanent ? 1 : 0); - ps.setInt(7, cfg.durationMinutes); - ps.setString(8, cfg.packUrl); - ps.setString(9, cfg.packHash); - ps.setString(10, cfg.packPrompt); - ps.setInt(11, cfg.packSize); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ===== 玩家操作 ===== - - public boolean addPlayer(String player, String groupName, String addedBy, long durationMinutes) { - UserGroupConfig cfg = groupConfigs.get(groupName); - if (cfg == null) return false; - long now = System.currentTimeMillis(); - long expiry = cfg.permanent ? 0 : now + durationMinutes * 60 * 1000; - if (!cfg.permanent && durationMinutes > 0) expiry = now + durationMinutes * 60 * 1000; - else if (!cfg.permanent) expiry = now + cfg.durationMinutes * 60 * 1000; - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO user_group_member " - + "(player_name, group_name, added_by, added_time, expiry_time)" - + " VALUES (?,?,?,?,?)"); - ps.setString(1, player); - ps.setString(2, groupName); - ps.setString(3, addedBy); - ps.setLong(4, now); - ps.setLong(5, expiry); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - Player p = plugin.getServer().getPlayerExact(player); - if (p != null) sendResourcePack(p); - return true; - } - - public boolean removePlayer(String player, String groupName) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM user_group_member WHERE player_name=? AND group_name=?"); - ps.setString(1, player); - ps.setString(2, groupName); - int rows = ps.executeUpdate(); - ps.close(); - return rows > 0; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } - - public List> getPlayerGroups(String player) { - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM user_group_member WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - long now = System.currentTimeMillis(); - while (rs.next()) { - Map row = new LinkedHashMap<>(); - row.put("group_name", rs.getString("group_name")); - row.put("added_by", rs.getString("added_by")); - long expiry = rs.getLong("expiry_time"); - row.put("expiry_time", expiry); - row.put("expired", expiry > 0 && expiry <= now); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public UserGroupConfig getHighestGroup(String player) { - List> groups = getPlayerGroups(player); - long now = System.currentTimeMillis(); - UserGroupConfig best = null; - for (Map g : groups) { - long expiry = (long) g.get("expiry_time"); - if (expiry > 0 && expiry <= now) continue; - String name = (String) g.get("group_name"); - UserGroupConfig cfg = groupConfigs.get(name); - if (cfg != null && (best == null || cfg.priority > best.priority)) { - best = cfg; - } - } - return best; - } - - public boolean isPlayerInGroup(String player, String groupName) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM user_group_member WHERE player_name=? AND group_name=?"); - ps.setString(1, player); - ps.setString(2, groupName); - boolean exists = ps.executeQuery().next(); - ps.close(); - return exists; - } catch (SQLException e) { - return false; - } - } - - // ===== 过期自动移除 ===== - - private void startExpiryChecker() { - new BukkitRunnable() { - @Override - public void run() { - checkExpiry(); - } - }.runTaskTimerAsynchronously(plugin, 1200L, 1200L); // 每分钟检查 - } - - private void checkExpiry() { - try { - long now = System.currentTimeMillis(); - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, group_name FROM user_group_member " - + "WHERE expiry_time > 0 AND expiry_time <= ?"); - ps.setLong(1, now); - ResultSet rs = ps.executeQuery(); - List expired = new ArrayList<>(); - while (rs.next()) { - expired.add(new String[]{ - rs.getString("player_name"), - rs.getString("group_name")}); - } - rs.close(); ps.close(); - - for (String[] e : expired) { - PreparedStatement del = db.prepareStatement( - "DELETE FROM user_group_member WHERE player_name=? AND group_name=?"); - del.setString(1, e[0]); - del.setString(2, e[1]); - del.executeUpdate(); - del.close(); - plugin.getLogger().info("[UserGroup] 过期移除: " + e[0] + " -> " + e[1]); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ===== 资源包分发 ===== - - public void sendResourcePack(Player p) { - UserGroupConfig cfg = getHighestGroup(p.getName()); - if (cfg == null || cfg.packUrl == null || cfg.packUrl.isEmpty()) return; - - String url = cfg.packUrl; - String prompt = cfg.packPrompt != null - ? cfg.packPrompt : "Custom Resource Pack"; - - plugin.getServer().getScheduler().runTaskLater(plugin, () -> { - try { - p.setResourcePack(url, new byte[0], prompt, false); - plugin.getLogger().info("[UserGroup] 资源包已发送: " - + p.getName() + " -> " + cfg.displayName); - } catch (Exception e) { - plugin.getLogger().warning( - "[UserGroup] 资源包发送失败: " + e.getMessage()); - } - }, 40L); - } - - public Map getGroupConfigs() { - return groupConfigs; - } - - // ===== 配置类 ===== - - public static class UserGroupConfig { - public String name; - public String displayName = ""; - public String displayColor = "§f"; - public String displayEmoji = ""; - public int priority = 0; - public boolean permanent = true; - public int durationMinutes = 0; - public String packUrl = ""; - public String packHash = ""; - public String packPrompt = "Custom Resource Pack"; - public int packSize = 0; - public String localFile = ""; - } -} diff --git a/backup/src/main/java/Sdf1_login/VerificationManager.java b/backup/src/main/java/Sdf1_login/VerificationManager.java deleted file mode 100644 index 3fc4ceb..0000000 --- a/backup/src/main/java/Sdf1_login/VerificationManager.java +++ /dev/null @@ -1,158 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.UUID; -import java.util.function.Consumer; - -public class VerificationManager { - - private final Main plugin; - - public VerificationManager(Main plugin) { - this.plugin = plugin; - } - - public boolean isBedrockPlayer(Player p) { - String uuid = p.getUniqueId().toString(); - return uuid.contains("f851ba4") - || uuid.startsWith("00000000-0000-0000"); - } - - public boolean isOnlineMode() { - return Bukkit.getServer().getOnlineMode(); - } - - public void verifyPremiumAsync(Player player, - Consumer callback) { - Thread t = new Thread(() -> doVerify(player, callback), - "Sdf1_login-PremiumVerify"); - t.setDaemon(true); - t.start(); - } - - private void doVerify(Player player, - Consumer callback) { - String name = player.getName(); - String uuid = player.getUniqueId().toString(); - - plugin.getLogger().info( - "[Sdf1_login] ====== 验证开始 ======"); - plugin.getLogger().info( - "[Sdf1_login] 玩家: " + name); - plugin.getLogger().info( - "[Sdf1_login] 当前UUID: " + uuid); - - try { - UUID offlineUuid = UUID.nameUUIDFromBytes( - ("OfflinePlayer:" + name) - .getBytes(StandardCharsets.UTF_8)); - String offlineStr = offlineUuid.toString(); - - plugin.getLogger().info( - "[Sdf1_login] 离线UUID: " + offlineStr); - - boolean isOfflineUuid = - player.getUniqueId().equals(offlineUuid); - - plugin.getLogger().info( - "[Sdf1_login] UUID匹配离线格式: " - + isOfflineUuid); - - if (!isOfflineUuid) { - plugin.getLogger().info( - "[Sdf1_login] 结论: 正版玩家"); - plugin.getLogger().info( - "[Sdf1_login] ====== 验证结束 ======"); - callback.accept(true); - return; - } - - plugin.getLogger().info( - "[Sdf1_login] UUID为离线格式," - + "查询Mojang API..."); - UUID mojangUuid = fetchMojangUuid(name); - - if (mojangUuid != null) { - plugin.getLogger().info( - "[Sdf1_login] Mojang返回UUID: " - + mojangUuid); - plugin.getLogger().info( - "[Sdf1_login] 结论: 盗版客户端" - + "使用了正版名字"); - } else { - plugin.getLogger().info( - "[Sdf1_login] Mojang无此名字"); - plugin.getLogger().info( - "[Sdf1_login] 结论: 纯离线玩家"); - } - - plugin.getLogger().info( - "[Sdf1_login] ====== 验证结束 ======"); - callback.accept(false); - - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 验证异常: " - + e.getMessage()); - callback.accept(false); - } - } - - private UUID fetchMojangUuid(String name) { - HttpURLConnection conn = null; - try { - URL url = new URL( - "https://api.mojang.com/users/profiles/minecraft/" - + name); - conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - conn.setRequestMethod("GET"); - - int code = conn.getResponseCode(); - plugin.getLogger().info( - "[Sdf1_login] Mojang API响应: " + code); - - if (code == 200) { - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - conn.getInputStream(), - StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - reader.close(); - - String json = sb.toString(); - String uuidStr = json.replaceAll( - ".*\"id\"\\s*:\\s*\"([^\"]+)\".*", - "$1"); - plugin.getLogger().info( - "[Sdf1_login] Mojang原始UUID: " - + uuidStr); - - String formatted = uuidStr.replaceFirst( - "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", - "$1-$2-$3-$4-$5"); - return UUID.fromString(formatted); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] Mojang查询异常: " - + e.getMessage()); - } finally { - if (conn != null) conn.disconnect(); - } - return null; - } -} diff --git a/backup/src/main/java/Sdf1_login/WelcomeManager.java b/backup/src/main/java/Sdf1_login/WelcomeManager.java deleted file mode 100644 index 68bd3ef..0000000 --- a/backup/src/main/java/Sdf1_login/WelcomeManager.java +++ /dev/null @@ -1,163 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -public class WelcomeManager { - - private final Main plugin; - private final Random random = new Random(); - private final List registerMsgs = - new ArrayList<>(); - private final List loginMsgs = - new ArrayList<>(); - - public WelcomeManager(Main plugin) { - this.plugin = plugin; - loadMessages(); - } - - public void loadMessages() { - registerMsgs.clear(); - loginMsgs.clear(); - File f = new File( - plugin.getDataFolder(), - "欢迎仪式.txt"); - if (!f.exists()) { - writeDefault(f); - } - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets - .UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("注册") - || t.equals("注册:")) { - section = "register"; - continue; - } - if (t.equals("登录") - || t.equals("登录:")) { - section = "login"; - continue; - } - // 支持 = 和 : 两种分隔 - if (section.isEmpty()) { - if (t.startsWith("注册") - && (t.contains("=") - || t.contains(":"))) { - int idx = t.indexOf('='); - int idx2 = t.indexOf(':'); - if (idx < 0) idx = idx2; - else if (idx2 >= 0) - idx = Math.min(idx, idx2); - String v = t.substring(idx + 1) - .trim(); - if (!v.isEmpty()) - registerMsgs.add( - colorize(v)); - continue; - } - if (t.startsWith("登录") - && (t.contains("=") - || t.contains(":"))) { - int idx = t.indexOf('='); - int idx2 = t.indexOf(':'); - if (idx < 0) idx = idx2; - else if (idx2 >= 0) - idx = Math.min(idx, idx2); - String v = t.substring(idx + 1) - .trim(); - if (!v.isEmpty()) - loginMsgs.add( - colorize(v)); - continue; - } - continue; - } - if (!t.isEmpty()) { - if (section.equals("register")) - registerMsgs.add( - colorize(t)); - else - loginMsgs.add( - colorize(t)); - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[欢迎] 加载失败: " - + e.getMessage()); - } - plugin.getLogger().info( - "[欢迎] 加载完成: 注册=" - + registerMsgs.size() - + " 登录=" + loginMsgs.size()); - } - - public void onRegister(Player p) { - sendRandom(p, registerMsgs, "注册"); - } - - public void onLogin(Player p) { - sendRandom(p, loginMsgs, "登录"); - } - - private void sendRandom(Player p, - List msgs, String tag) { - if (msgs.isEmpty()) return; - String msg = msgs.get( - random.nextInt(msgs.size())); - msg = msg.replace("{player}", - p.getName()); - final String finalMsg = msg; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> Bukkit - .broadcastMessage( - finalMsg), 10L); - } - - private String colorize(String msg) { - return msg.replace("&", "\u00a7"); - } - - private void writeDefault(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets - .UTF_8))) { - pw.println("# 欢迎仪式配置"); - pw.println("# &为颜色代码"); - pw.println("# {player} = 玩家名"); - pw.println(); - pw.println("注册:"); - pw.println("&a&l欢迎 &f{player} &a加入服务器!&7[注册]"); - pw.println("&6&l{player} &e已完成注册,欢迎入驻!&7[注册]"); - pw.println("&b&l新成员 &f{player} &b已就位!&7[注册]"); - pw.println(); - pw.println("登录:"); - pw.println("&a&l欢迎回来 &f{player}&a!&7[登录]"); - pw.println("&e&l{player} &e已登录,继续冒险吧!&7[登录]"); - pw.println("&b&l{player} &b回来了!&7[登录]"); - } catch (IOException ignored) { - } - } -} diff --git a/backup/src/main/resources/plugin.yml b/backup/src/main/resources/plugin.yml deleted file mode 100644 index f6868d9..0000000 --- a/backup/src/main/resources/plugin.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Sdf1_login -version: 1.0 -main: Sdf1_login.Main -api-version: '1.21' -commands: - sdf1_login: - description: Sdf1_login主命令 - usage: /sdf1_login - reg: - description: 注册 - usage: /reg <密码> - login: - description: 登录 - usage: /login <密码> - l: - description: 登录快捷版 - usage: /l <密码> - 注册: - description: 注册(中文) - usage: /注册 <密码> - 登录: - description: 登录(中文) - usage: /登录 <密码> - 玩家信息: - description: 打开面板(中文) - usage: /玩家信息 - 改密码: - description: 修改密码(中文) - usage: /改密码 <旧密码> <新密码> <确认新密码> - 设置邮箱: - description: 设置邮箱(中文) - usage: /设置邮箱 <邮箱地址> - 撤销: - description: 撤销操作(中文) - usage: /撤销 - 删除: - description: 删除玩家(中文) - usage: /删除 <玩家> <密码> - 找回密码: - description: 找回密码(中文) - usage: /找回密码 - 签到: - description: 每日签到(中文) - usage: /签到 - #oa: - # description: 申请删除账号 - # usage: /oa [玩家名] - 删除账号: - description: 申请删除账号(中文) - usage: /删除账号 [玩家名] - 通过: - description: 批准OA请求(中文) - usage: /通过 <编号> - 拒绝: - description: 拒绝OA请求(中文) - usage: /拒绝 <编号> [理由] - accept: - description: 批准OA请求 - usage: /accept <编号> - deny: - description: 拒绝OA请求 - usage: /deny <编号> [理由] - email: - description: 绑定邮箱 - usage: /email <邮箱> - 绑定: - description: 绑定邮箱(中文) - usage: /绑定 <邮箱> - 绑定邮箱: - description: 绑定邮箱 - usage: /绑定邮箱 - chat: - description: 聊天过滤管理 - usage: /chat - recycle: - description: 打开垃圾回收站 - usage: /recycle - 垃圾清理: - description: 手动清理掉落物 - usage: /垃圾清理 - menu: - description: 雪球菜单开关控制 - usage: /menu <开/关> - pvp : - description: PVP榜单 - usage: /pvp - cypay : - description: 草原探险债券 - usage: /cypay - printer : - description: 打印债券流水 - usage: /printer - shop : - description: 游戏商人 - usage: /shop - 商店: - description: 打开商店 - usage: /商店 - #testorder: - # description: 测试订单中心 - # usage: /testorder - protect: - description: 区域保护 - usage: /protect \ No newline at end of file diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/AFKManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/AFKManager.java deleted file mode 100644 index f714c1b..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/AFKManager.java +++ /dev/null @@ -1,197 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; - -public class AFKManager { - - // 玩家最后活动时间(空闲才开始计时) - private final Map lastAction = - new HashMap<>(); - // 每个玩家当前的警告阶段 - private final Map lastStage = - new HashMap<>(); - // 是否已进入挂机检测(false=还在活动,不计时) - private final Map afkTracking = - new HashMap<>(); - - private final ConfigManager config; - private final JavaPlugin plugin; - - public AFKManager(ConfigManager config, - JavaPlugin plugin) { - this.config = config; - this.plugin = plugin; - } - - public void startCheck() { - new BukkitRunnable() { - public void run() { - tick(); - } - }.runTaskTimer(plugin, 20L, 20L); - } - - private void tick() { - if (!config.afkEnabled) return; - long now = System.currentTimeMillis(); - - for (Player p : Bukkit.getOnlinePlayers()) { - String name = p.getName(); - UUID uuid = p.getUniqueId(); - - if (!isLoggedIn(name)) continue; - if (config.getAfkWhitelist().contains(name)) - continue; - - // 确保有记录 - if (!lastAction.containsKey(uuid)) { - lastAction.put(uuid, now); - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - continue; - } - - long last = lastAction.get(uuid); - long idle = (now - last) / 1000; - int timeout = config.afkTimeout; - - // 还在活动范围内:不进入挂机检测 - if (idle < 60) { - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - continue; - } - - // 超过60秒无活动:开始挂机检测 - afkTracking.put(uuid, true); - long remain = timeout - idle; - - // 已超时:踢出 - if (remain <= 0) { - p.kickPlayer("§c§l你已挂机 " - + fmtSec(idle) + ",被踢出"); - lastAction.remove(uuid); - lastStage.remove(uuid); - afkTracking.remove(uuid); - continue; - } - - int stage = lastStage.getOrDefault(uuid, 0); - - // 阶段3:最后10秒 - if (remain <= 10) { - if (stage < 3) { - lastStage.put(uuid, 3); - } - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + remain + " 秒踢出", - 0, 22, 3); - playSound(p); - continue; - } - - // 阶段2:1分钟~10秒 - if (remain <= 60) { - if (stage < 2) { - lastStage.put(uuid, 2); - p.sendMessage( - "§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" - + fmtSec(idle) - + "§f,还剩 §c" - + fmtSec(remain) - + "§f踢出"); - } - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§c还剩 " + fmtSec(remain) + " 踢出", - 5, 25, 5); - playSound(p); - continue; - } - - // 阶段1:3分钟提醒 - if (remain <= 180 && stage < 1) { - lastStage.put(uuid, 1); - p.sendMessage( - "§e§l⚠ 挂机警告 ⚠ §f你已挂机 §c" - + fmtSec(idle) - + "§f,还剩 §c" - + fmtSec(remain) - + "§f踢出"); - p.sendTitle( - "§e§l挂机了 " + fmtSec(idle), - "§f还剩 " + fmtSec(remain) + " 踢出", - 10, 60, 20); - for (int i = 0; i < 3; i++) { - final int delay = i * 10; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - if (p.isOnline()) playSound(p); - }, delay); - } - } - - if (remain > 180) { - lastStage.remove(uuid); - } - } - } - - /** - * 玩家有活动时调用(移动/交互/放置/挖掘等) - * 重置计时,退出挂机检测 - */ - public void recordAction(UUID uuid) { - lastAction.put(uuid, System.currentTimeMillis()); - afkTracking.put(uuid, false); - lastStage.put(uuid, 0); - } - - /** - * 玩家下线时调用 - * 彻底停止计时,清理所有数据 - */ - public void remove(UUID uuid) { - lastAction.remove(uuid); - lastStage.remove(uuid); - afkTracking.remove(uuid); - } - - /** - * 获取玩家当前是否在挂机检测中 - */ - public boolean isAfkTracking(UUID uuid) { - return afkTracking.getOrDefault(uuid, false); - } - - private boolean isLoggedIn(String name) { - return ((Main) plugin).getLoggedIn() - .contains(name); - } - - private void playSound(Player p) { - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_CHIME, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_BASS, - 1.0f, 0.79f); - p.playSound(p.getLocation(), - Sound.BLOCK_NOTE_BLOCK_PLING, - 1.0f, 0.79f); - } - - private String fmtSec(long sec) { - if (sec < 60) return sec + "秒"; - long m = sec / 60; - long s = sec % 60; - return m + "分" + (s > 0 ? s + "秒" : ""); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/AccountRequestManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/AccountRequestManager.java deleted file mode 100644 index ae5eaad..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/AccountRequestManager.java +++ /dev/null @@ -1,146 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class AccountRequestManager { - - public enum ApprovalMode { AUTO, MANUAL, HYBRID } - - public static class Request { - public final int id; - public final String requester; - public final String target; - public final String ip; - public final long time; - public String status; - public String denyReason; - - public Request(int id, String requester, String target, String ip) { - this.id = id; - this.requester = requester; - this.target = target; - this.ip = ip; - this.time = System.currentTimeMillis(); - this.status = "PENDING"; - this.denyReason = ""; - } - } - - private final Main plugin; - private final Map requests = new ConcurrentHashMap<>(); - private final Map pendingByPlayer = new ConcurrentHashMap<>(); - private int nextId = 1; - private ApprovalMode mode = ApprovalMode.MANUAL; - private int autoDelayMinutes = 60; - - public AccountRequestManager(Main plugin) { - this.plugin = plugin; - } - - public ApprovalMode getMode() { return mode; } - - public void setMode(String m) { - try { this.mode = ApprovalMode.valueOf(m.toUpperCase()); } - catch (Exception e) { this.mode = ApprovalMode.MANUAL; } - } - - public void setAutoDelayMinutes(int min) { - this.autoDelayMinutes = min; - } - - public Request createRequest(String requester, String target, String ip) { - int id = nextId++; - Request req = new Request(id, requester, target, ip); - requests.put(id, req); - pendingByPlayer.put(requester, id); - - if (mode == ApprovalMode.AUTO) { - scheduleAutoApprove(id); - } else if (mode == ApprovalMode.HYBRID) { - String targetIp = (String) plugin.getDb().getField(target, "ip_address"); - if (ip != null && ip.equals(targetIp)) { - scheduleAutoApprove(id); - } - } - return req; - } - - private void scheduleAutoApprove(int id) { - long delayTicks = (long) autoDelayMinutes * 60L * 20L; - Request req = requests.get(id); - if (req == null) return; - String requesterName = req.requester; - String targetName = req.target; - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 将在 " + autoDelayMinutes + " 分钟后自动通过"); - - Bukkit.getScheduler().runTaskLater(plugin, () -> { - Request r = requests.get(id); - if (r != null && "PENDING".equals(r.status)) { - approveRequest(id); - plugin.getLogger().info("[Sdf1_login] OA#" + id + " 自动通过"); - Player requester = Bukkit.getPlayer(requesterName); - if (requester != null && requester.isOnline()) { - requester.sendMessage("§a[Sdf1_login] §f您的删除请求 #" + id + "(§e" + targetName + "§a)已自动通过!"); - } - } - }, delayTicks); - } - - public boolean approveRequest(int id) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "APPROVED"; - plugin.getDb().deleteUser(req.target); - pendingByPlayer.remove(req.requester); - // 清背包+传送出生点+踢出 - Player target = Bukkit.getPlayer(req.target); - if (target != null && target.isOnline()) { - target.getInventory().clear(); - target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]); - target.getInventory().setExtraContents(new org.bukkit.inventory.ItemStack[1]); - target.teleport(target.getWorld().getSpawnLocation()); - target.kickPlayer("§c§l你的账号已被管理员删除,请重新注册"); - } - return true; - } - - - public boolean denyRequest(int id, String reason) { - Request req = requests.get(id); - if (req == null || !"PENDING".equals(req.status)) return false; - req.status = "DENIED"; - req.denyReason = reason; - pendingByPlayer.remove(req.requester); - return true; - } - - public List getPendingRequests() { - List list = new ArrayList<>(); - for (Request r : requests.values()) { - if ("PENDING".equals(r.status)) list.add(r); - } - return list; - } - - public Request getRequest(int id) { - return requests.get(id); - } - - public boolean hasPending(String player) { - return pendingByPlayer.containsKey(player); - } - - public void cancelPending(String player) { - Integer id = pendingByPlayer.remove(player); - if (id != null) { - Request r = requests.get(id); - if (r != null) r.status = "CANCELLED"; - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/AreaProtection.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/AreaProtection.java deleted file mode 100644 index bc53d53..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/AreaProtection.java +++ /dev/null @@ -1,4907 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.event.block.BlockBurnEvent; -import org.bukkit.event.block.BlockIgniteEvent; -import org.bukkit.event.block.BlockSpreadEvent; -import org.bukkit.entity.Entity; -import org.bukkit.event.player.PlayerInteractAtEntityEvent; -import java.sql.*; -import org.bukkit.event.entity.*; -import org.bukkit.event.player.*; -import org.bukkit.GameMode; -import org.bukkit.event.EventPriority; - - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; -import java.util.Iterator; -import java.util.Set; -import java.util.HashSet; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.event.entity.EntitySpawnEvent; -import org.bukkit.entity.Item; - - - - -public class AreaProtection implements Listener { - - private final Main plugin; - public List giveEffects = new ArrayList<>(); - private final Map> playerAreas - = new HashMap<>(); - private final Map areas = - new ConcurrentHashMap<>(); - private Connection dbConnection; - - // 全局白名单(玩家) - private final Set globalPlayerWhitelist = - ConcurrentHashMap.newKeySet(); - // 区域白名单 key=区域名 value=玩家名集合 - private final Map> areaPlayerWhitelist = - new ConcurrentHashMap<>(); - // 全局黑名单(物品ID) - private final Set globalItemBlacklist = - ConcurrentHashMap.newKeySet(); - // 区域黑名单 - private final Map> areaItemBlacklist = - new ConcurrentHashMap<>(); - - // 选地 - private static final Material WAND = Material.BREEZE_ROD; - - private final Map pos1 = - new ConcurrentHashMap<>(); - private final Map pos2 = - new ConcurrentHashMap<>(); - private final Set selecting = - ConcurrentHashMap.newKeySet(); - public boolean denyRaid = false; - // ===== 统一文件夹 ===== - private final File rootDir; - private final File whitelistDir; - private final File globalWhitelistFile; - private final File oldDbFile; - private final File newDbFile; - - // 边框显示 - private final Map displayTaskIds = - new ConcurrentHashMap<>(); - private final Map> globalWhitelist - = new HashMap<>(); - private final Map> areaWhitelist - = new HashMap<>(); - private final Map protectedEntities - = new ConcurrentHashMap<>(); - // 区域给予的效果记录(贴标) - private final Map> playerAppliedEffects = new HashMap<>(); - // 延时清理任务 - private static final long MOVE_DEBOUNCE_MS = 100; - // 新:全部改为线程安全 - private final Map playerCurrentArea - = new ConcurrentHashMap<>(); - private final Map> playerMarkedEffects - = new ConcurrentHashMap<>(); - private final Map pendingClearTask - = new ConcurrentHashMap<>(); - private final Map pendingClearArea - = new ConcurrentHashMap<>(); - private final Map lastMoveProcess - = new ConcurrentHashMap<>(); - // 防止传送时重复处理 - private final Set teleporting - = ConcurrentHashMap.newKeySet(); - // 待清理的效果列表(与延时任务配合) - private final Map> pendingClearEffects - = new ConcurrentHashMap<>(); - - - -// ==================== 强制游戏模式 ==================== - - /** - * 监听玩家尝试切换游戏模式 - * 如果在强制游戏模式区域且不在豁免名单中,则取消切换 - */ - // 防止递归的标记 - private final Set modeSwitching - = ConcurrentHashMap.newKeySet(); - @EventHandler(priority = EventPriority.HIGHEST) - public void onGameModeChange( - PlayerGameModeChangeEvent event) { - if (event.isCancelled()) return; - - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - if (modeSwitching.contains(uid)) return; - - String areaName = playerCurrentArea.get(uid); - if (areaName == null) return; - - AreaConfig ac = areas.get(areaName); - if (ac == null) return; - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) return; - if (isExemptFromGameMode(p, areaName)) return; - - event.setCancelled(true); - - modeSwitching.add(uid); - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } finally { - modeSwitching.remove(uid); - } - - // ★ 切换后发一次中文提示 - p.sendMessage(formatAreaMsg( - "&c该区域强制 " + ac.enforceGameMode - + " 模式,无法切换")); - } - - public Set getAlreadyForced() { - return alreadyForced; - } - - - // ==================== 定时强制游戏模式 ==================== - - private BukkitTask enforceTask; - - /** - * 启动定时强制任务 - * 每秒检查一次所有在线玩家 - */ - private BukkitTask enforceTask1; - - // 记录上次强制模式的玩家(避免重复发消息) - private final Set alreadyForced - = ConcurrentHashMap.newKeySet(); - - public void startEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - } - enforceTask = new BukkitRunnable() { - @Override - public void run() { - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String areaName = - playerCurrentArea.get(uid); - if (areaName == null) { - // 不在任何区域,清除标记 - alreadyForced.remove(uid); - continue; - } - - AreaConfig ac = areas.get(areaName); - if (ac == null) continue; - if (ac.enforceGameMode == null - || ac.enforceGameMode - .isEmpty()) continue; - if (isExemptFromGameMode( - p, areaName)) { - alreadyForced.remove(uid); - continue; - } - - GameMode target = - GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - modeSwitching.add(uid); - try { - p.setGameMode(target); - } finally { - modeSwitching.remove(uid); - } - - // ★ 只在玩家已被强制过时才发消息 - // 首次不发,后续才提醒 - if (alreadyForced.contains(uid)) { - p.sendMessage(formatAreaMsg( - "&c该区域强制 " - + ac.enforceGameMode - + " 模式,无法切换")); - } - alreadyForced.add(uid); - } else { - // 模式一致,清除标记 - alreadyForced.remove(uid); - } - } - } - }.runTaskTimer(plugin, 20L, 20L); - } - - public void stopEnforceTask() { - if (enforceTask != null) { - enforceTask.cancel(); - enforceTask = null; - } - } - - - - /** - * 检查玩家是否豁免于游戏模式强制 - * 豁免条件:全局白名单 / 区域白名单 / 模式排除名单 - */ - private boolean isExemptFromGameMode( - Player p, String areaName) { - String name = p.getName(); - - /* if (p.isOp()) { - plugin.getLogger().info( - "[防护-调试] 豁免原因: OP, 玩家=" + name); - return true; - }*/ - if (globalPlayerWhitelist.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 全局白名单, 玩家=" - + name);*/ - return true; - } - Set aw = - areaPlayerWhitelist.get(areaName); - if (aw != null && aw.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 区域白名单, 玩家=" - + name + " 区域=" + areaName);*/ - return true; - } - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) { - /* plugin.getLogger().info( - "[防护-调试] 豁免原因: 模式排除名单, 玩家=" - + name);*/ - return true; - } - /* plugin.getLogger().info( - "[防护-调试] 无豁免, 玩家=" + name);*/ - return false; - } - - /** - * 玩家进入区域时,如果该区域强制游戏模式, - * 则立即设置(排除豁免玩家) - */ - private void applyEnforcedGameMode(Player p, - String areaName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: ac=null, area=" - + areaName);*/ - return; - } - if (ac.enforceGameMode == null - || ac.enforceGameMode.isEmpty()) { - /* plugin.getLogger().info( - "[防护-调试] applyMode: 未配置强制模式, area=" - + areaName);*/ - return; - } - - boolean exempt = isExemptFromGameMode( - p, areaName); - /* plugin.getLogger().info( - "[防护-调试] applyMode: 玩家=" + p.getName() - + " 区域=" + areaName - + " 强制=" + ac.enforceGameMode - + " 当前=" + p.getGameMode() - + " 豁免=" + exempt - + " OP=" + p.isOp() - + " 全白=" + globalPlayerWhitelist - .contains(p.getName()));*/ - - if (exempt) return; - - try { - GameMode target = GameMode.valueOf( - ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - /* plugin.getLogger().info( - "[防护] 强制切换 " + p.getName() - + " → " + ac.enforceGameMode);*/ - } - } catch (Exception e) { - /* plugin.getLogger().warning( - "[防护] 强制模式失败: " - + e.getMessage());*/ - } - } - - public AreaProtection(Main plugin) { - this.plugin = plugin; - - // 统一根目录 - rootDir = new File(plugin.getDataFolder(), "区域防护"); - if (!rootDir.exists()) rootDir.mkdirs(); - - // 白名单子目录 - whitelistDir = new File(rootDir, "whitelists"); - if (!whitelistDir.exists()) whitelistDir.mkdirs(); - globalWhitelistFile = new File( - whitelistDir, "全局白名单.txt"); - - // 数据库迁移 - oldDbFile = new File( - plugin.getDataFolder(), "area_effects.db"); - newDbFile = new File(rootDir, "area_effects.db"); - migrateDatabase(); - - // 初始化 - initDatabase(); - writeDefaultConfig(); - loadAllAreas(); - loadWhitelists(); - recoverPendingEffects(); - } - public void recoverPendingEffects() { - try { - // 1. 遍历所有在线玩家(而不是遍历DB记录) - for (Player p : Bukkit.getOnlinePlayers()) { - UUID uid = p.getUniqueId(); - String playerName = p.getName(); - - // 2. 获取玩家当前所在区域 - AreaConfig currentAc = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - - // 3. 获取该玩家在DB中的所有历史记录 - List recordedAreas = - getPlayerAreas(uid); - - // 4. 清理不在当前区域的旧记录 - for (String recordedArea : recordedAreas) { - if (currentAc == null - || !currentAc.name - .equals(recordedArea)) { - // 玩家不在这个区域了 → 清DB - removePlayerEffects( - uid, recordedArea); - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在" + recordedArea - + ", 清理旧记录"); - } - } - - // 5. 如果在区域中 → 恢复/给予效果 - if (currentAc != null) { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 在" + currentAc.name - + ", 恢复效果"); - applyRegionEffects(p, uid, - currentAc.name, currentAc); - } else { - plugin.getLogger().info( - "[防护] 重连: " + playerName - + " 不在任何区域, 跳过"); - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 恢复效果失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - - // 取消延时清理任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 清标记 - playerMarkedEffects.remove(uid); - playerCurrentArea.remove(uid); - } - - -// ==================== 数据库迁移 ==================== - - /** - * 如果旧位置有 db 文件,移到新位置 - * 如果新位置已有,不动 - * 如果旧位置也没有,在新位置创建 - */ - - private void migrateDatabase() { - if (newDbFile.exists()) { - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - oldDbFile.delete(); - } - return; - } - if (oldDbFile.exists() - && !oldDbFile.equals(newDbFile)) { - try { - java.nio.file.Files.move( - oldDbFile.toPath(), - newDbFile.toPath(), - java.nio.file.StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 迁移数据库失败: " - + e.getMessage()); - } - } - } - - - // 保存玩家进入区域时给予的效果 - private void savePlayerEffects(UUID uid, String playerName, - String areaName, List effects) { - try { - // 先删除该区域旧记录,避免重复 - PreparedStatement del = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - del.setString(1, uid.toString()); - del.setString(2, areaName); - del.executeUpdate(); - del.close(); - - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)"); - long now = System.currentTimeMillis(); - for (PotionEffectType type : effects) { - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, type.getName()); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, now); - ps.addBatch(); - } - ps.executeBatch(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 保存效果失败: " + e.getMessage()); - } - } - - // 查询玩家在某个区域获得的效果 - private List getPlayerEffects( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - PotionEffectType type = PotionEffectType - .getByName(rs.getString("effect_type")); - if (type != null) list.add(type); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - // 删除玩家在某个区域的效果记录 - private void removePlayerEffects(UUID uid, String areaName) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ? AND area_name = ?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除效果失败: " + e.getMessage()); - } - } - - // 删除玩家所有效果记录 - private void removeAllPlayerEffects(UUID uid) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "DELETE FROM player_effects " - + "WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 删除全部效果失败: " + e.getMessage()); - } - } - - // 获取玩家所有记录的区域 - private List getPlayerAreas(UUID uid) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT DISTINCT area_name " - + "FROM player_effects WHERE uuid = ?"); - ps.setString(1, uid.toString()); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("area_name")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询区域失败: " + e.getMessage()); - } - return list; - } - - // 查询所有待处理的效果(跨服重启也能恢复) - private List getAllPendingEffects() { - List list = new ArrayList<>(); - try { - Statement stmt = dbConnection.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT uuid, player_name, area_name, " - + "effect_type FROM player_effects"); - while (rs.next()) { - list.add(new String[]{ - rs.getString("uuid"), - rs.getString("player_name"), - rs.getString("area_name"), - rs.getString("effect_type")}); - } - rs.close(); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询全部效果失败: " + e.getMessage()); - } - return list; - } - - - private void writeDefaultConfig() { - File f = new File(rootDir, "末地保护区.txt"); - if (f.exists()) return; - - String endWorld = "world_the_end"; - for (org.bukkit.World w : Bukkit.getWorlds()) { - if (w.getEnvironment() - == org.bukkit.World.Environment.THE_END) { - endWorld = w.getName(); - break; - } - } - - final String ew = endWorld; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 末地保护区"); - pw.println("起点: " + ew + ",-30000000,-30000000"); - pw.println("终点: " + ew + ",30000000,30000000"); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("没收物品: BROWN_MUSHROOM,RED_MUSHROOM,MUSHROOM_STEW"); - pw.println("没收提示: 你携带了违禁品「蘑菇」,已被全部没收!"); - pw.println("通报批评: {player} 在末地违规携带蘑菇!"); - pw.println(); - pw.println("禁止放置方块"); - pw.println("禁止破坏方块"); - pw.println("禁止PVP"); - pw.println("禁止袭击"); - pw.println(); - pw.println("效果: 夜视 1 999"); - pw.println("进入提示: 末地保护区 - 请遵守规则"); - pw.println("离开提示: 已离开末地保护区"); - pw.close(); - plugin.getLogger().info("[防护] 生成末地配置, 世界: " + ew); - } catch (IOException ignored) { - } - } - - public void reload() { - loadAllAreas(); - loadWhitelists(); - // 强制输出醒目的中文日志 - plugin.getLogger().info("========================================"); - plugin.getLogger().info("[防护] 已重载 " + areas.size() + " 个区域"); - plugin.getLogger().info("[防护] 已重载白名单"); - plugin.getLogger().info("========================================"); - } - -// ==================== 消息格式化 ==================== - - /** - * 格式化区域消息 - * 1. & → §(颜色符号转换) - * 2.
和 \n → 换行 - * 3. 内容 → §l内容§r(加粗) - * 4. 自动添加 [区域防护] 前缀 - */ - private String formatAreaMsg(String raw) { - if (raw == null || raw.isEmpty()) return ""; - - String msg = raw; - - // & → § - for (int i = 0; i <= 9; i++) { - msg = msg.replace("&" + i, "§" + i); - } - for (char c : "abcdefklnor".toCharArray()) { - msg = msg.replace("&" + c, "§" + c); - } - - // 换行 - msg = msg.replace("
", "\n"); - msg = msg.replace("\\n", "\n"); - - // HTML → Minecraft格式(栈式解析,支持嵌套) - msg = convertHtmlTags(msg); - - // 前缀 - msg = "§c§l【区域防护】§r " + msg; - - return msg; - } - - /** - * 栈式HTML标签解析 - * 处理 text rest 这样的嵌套标签 - * 内层关闭时重新应用外层格式 - */ - private String convertHtmlTags(String msg) { - StringBuilder sb = new StringBuilder(); - java.util.Deque stack = - new ArrayDeque<>(); - int i = 0; - - while (i < msg.length()) { - if (msg.charAt(i) == '<' - && i + 1 < msg.length()) { - if (msg.charAt(i + 1) == '/') { - // 关闭标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 2, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0 && !stack.isEmpty() - && stack.peek() == code) { - stack.pop(); - if (!stack.isEmpty()) { - // 重置后重新应用外层格式 - sb.append("§r"); - for (char c : stack) { - sb.append("§").append(c); - } - } else { - sb.append("§r"); - } - } - i = end + 1; - } else { - // 开放标签 - int end = msg.indexOf('>', i); - if (end < 0) { - sb.append(msg.charAt(i)); - i++; - continue; - } - String tag = msg.substring(i + 1, end) - .trim().toLowerCase(); - char code = htmlToMc(tag); - if (code != 0) { - stack.push(code); - sb.append("§").append(code); - } - i = end + 1; - } - } else { - sb.append(msg.charAt(i)); - i++; - } - } - return sb.toString(); - } - - private char htmlToMc(String tag) { - switch (tag) { - case "b": return 'l'; - case "i": return 'o'; - case "u": return 'n'; - case "s": return 'm'; - default: return 0; - } - } - -// =============== 加载 ==================== - - public void loadAllAreas() { - areas.clear(); - File[] files = rootDir.listFiles( - (File d, String n) -> n.endsWith(".txt")); - if (files == null) return; - for (File f : files) { - try { - String name = f.getName() - .replace(".txt", ""); - AreaConfig ac = parseArea(name, f); - areas.put(name, ac); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载失败: " + f.getName()); - } - } - } - - - private void initDatabase() { - try { - dbConnection = DriverManager.getConnection( - "jdbc:sqlite:" + newDbFile.getAbsolutePath()); - Statement stmt = dbConnection.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS player_effects (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "uuid TEXT NOT NULL," - + "player_name TEXT," - + "area_name TEXT NOT NULL," - + "effect_type TEXT NOT NULL," - + "effect_level INTEGER DEFAULT 0," - + "effect_duration INTEGER DEFAULT 999," - + "enter_time INTEGER NOT NULL)"); - stmt.executeUpdate( - "CREATE INDEX IF NOT EXISTS idx_uuid " - + "ON player_effects(uuid)"); - stmt.close(); - } catch (SQLException e) { - plugin.getLogger().severe( - "[防护] 数据库初始化失败: " - + e.getMessage()); - } - } - - -// ==================== 效果名称中英文映射 ==================== - - /** - * 将中文效果名转为 Bukkit 能识别的英文名 - */ - private String resolveEffectName(String name) { - if (name == null) return null; - String n = name.trim(); - // 如果已经是英文或命名空间格式,直接用 - if (n.contains(":") - || n.equals(n.toUpperCase())) { - return n; - } - // 中文 → 英文映射 - switch (n) { - case "伤害吸收": return "ABSORPTION"; - case "生命提升": return "HEALTH_BOOST"; - case "抗性提升": return "DAMAGE_RESISTANCE"; - case "抗火": return "FIRE_RESISTANCE"; - case "夜视": return "NIGHT_VISION"; - case "力量": return "INCREASE_DAMAGE"; - case "速度": return "SPEED"; - case "急迫": return "FAST_DIGGING"; - case "跳跃提升": return "JUMP"; - case "再生": return "REGENERATION"; - case "生命恢复": return "REGENERATION"; - case "治疗": return "HEAL"; - case "瞬间治疗": return "HEAL"; - case "伤害药水": return "HARM"; - case "瞬间伤害": return "HARM"; - case "虚弱": return "WEAKNESS"; - case "中毒": return "POISON"; - case "缓慢": return "SLOW"; - case "挖掘疲劳": return "SLOW_DIGGING"; - case "失明": return "BLINDNESS"; - case "隐身": return "INVISIBILITY"; - case "发光": return "GLOWING"; - // 村庄英雄 - case "村庄英雄": return "HERO_OF_THE_VILLAGE"; - case "幸运": return "LUCK"; - case "不幸": return "UNLUCK"; - case "水下呼吸": return "WATER_BREATHING"; - case "水下速掘": return "DOLPHINS_GRACE"; - case "缓降": return "LEVITATION"; - case "漂浮": return "LEVITATION"; - case "饱和": return "SATURATION"; - // 无效效果 - case "伤害": return "HARM"; - case "治疗药水": return "HEAL"; - default: - // 尝试直接用英文匹配 - return n; - } - } - - /** - * 全量容错:把全角字符统一转半角, - * 去掉零宽字符/不可见字符, - * 让用户怎么写都能正确解析。 - */ - private String normalizeLine(String input) { - if (input == null || input.isEmpty()) return ""; - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - - // 跳过零宽字符和不可见字符 - if (c == '\u200B' // 零宽空格 - || c == '\u200C' // 零宽非连接 - || c == '\u200D' // 零宽连接 - || c == '\uFEFF' // BOM - || c == '\u00A0' // 不间断空格 - || c == '\u2028' // 行分隔符 - || c == '\u2029' // 段分隔符 - || c == '\u200E' // LTR标记 - || c == '\u200F' // RTL标记 - || c == '\u2060' // 字连接符 - || (c >= '\u2000' && c <= '\u200A')) { // 各种空格 - continue; - } - - // 全角→半角映射 - if (c >= '\uFF01' && c <= '\uFF5E') { - c = (char) (c - 0xFEE0); - } - - // 全角空格→半角空格 - if (c == '\u3000') { - c = ' '; - } - - sb.append(c); - } - - return sb.toString(); - } - - - private AreaConfig parseArea(String name, File f) - throws IOException { - AreaConfig ac = new AreaConfig(); - ac.name = name; - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean inBlockComment = false; - boolean inHtmlComment = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty()) continue; -// ===== 全量容错预处理:全角→半角,去零宽字符 ===== - line = normalizeLine(line); - if (line.isEmpty()) continue; -// ===== 暴力调试:输出所有包含关键词的行 ===== - if (line.indexOf("游戏") >= 0 - || line.indexOf("模式") >= 0 - || line.indexOf("生存") >= 0 - || line.indexOf("创造") >= 0 - || line.indexOf("排除") >= 0 - || line.indexOf("豁免") >= 0 - || line.indexOf("强制") >= 0 - || line.indexOf("GameMode") >= 0 - || line.indexOf("SURVIVAL") >= 0 - || line.indexOf("CREATIVE") >= 0) { - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < line.length(); i++) { - hex.append(String.format("U+%04X ", (int) line.charAt(i))); - } - /* plugin.getLogger().info("[暴力调试] 行=[" + line - + "] 长度=" + line.length() - + " 字符码=" + hex.toString());*/ - } -// ===== 兜底扫描:确保和平白名单一定能解析 ===== - if (line.indexOf("白名单") >= 0 - || line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - int ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf(':'); - if (ci < 0) ci = line.indexOf('='); - if (ci < 0) ci = line.indexOf(' '); - - // 和平白名单 - if (line.indexOf("和平") >= 0 - && line.indexOf("白名单") >= 0 - && ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.peaceWhitelist.add(nm); - } - plugin.getLogger().info("[解析兜底] 和平白名单=" - + ac.peaceWhitelist); - } - - // 排除名单 - if (line.indexOf("排除名单") >= 0 - || line.indexOf("模式排除") >= 0 - || line.indexOf("模式豁免") >= 0) { - if (ci >= 0) { - String rest = line.substring(ci + 1).trim(); - rest = rest.replaceAll("[,,;;|、/]", ","); - for (String s : rest.split(",")) { - String nm = s.trim(); - if (!nm.isEmpty()) ac.modeExempt.add(nm); - } - plugin.getLogger().info("[解析兜底] 排除名单=" - + ac.modeExempt); - } - } - } -// ===== 兜底结束 ===== - - if (inBlockComment) { - int endIdx = line.indexOf("*/"); - if (endIdx >= 0) { - inBlockComment = false; - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("/*")) { - int endIdx = line.indexOf("*/", 2); - if (endIdx >= 0) { - line = line.substring(endIdx + 2).trim(); - if (line.isEmpty()) continue; - } else { - inBlockComment = true; - continue; - } - } - if (inHtmlComment) { - int endIdx = line.indexOf("-->"); - if (endIdx >= 0) { - inHtmlComment = false; - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - continue; - } - } - if (line.startsWith("", 4); - if (endIdx >= 0) { - line = line.substring(endIdx + 3).trim(); - if (line.isEmpty()) continue; - } else { - inHtmlComment = true; - continue; - } - } - if (line.startsWith("#") || line.startsWith("//")) { - continue; - } - int hashIdx = line.indexOf("#"); - if (hashIdx > 0) { - line = line.substring(0, hashIdx).trim(); - } - int slashIdx = line.indexOf("//"); - if (slashIdx > 0) { - line = line.substring(0, slashIdx).trim(); - } - if (line.isEmpty()) continue; - - if (line.startsWith("起点:")) { - String[] pp = line.substring("起点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x1 = Integer.parseInt(pp[1].trim()); - ac.z1 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("终点:")) { - String[] pp = line.substring("终点:".length()) - .trim().split(","); - if (pp.length == 3) { - ac.world = pp[0].trim(); - ac.x2 = Integer.parseInt(pp[1].trim()); - ac.z2 = Integer.parseInt(pp[2].trim()); - } - } else if (line.startsWith("高度范围:")) { - String[] pp = line.substring("高度范围:".length()) - .trim().split("-"); - if (pp.length == 2) { - ac.yMin = Integer.parseInt(pp[0].trim()); - ac.yMax = Integer.parseInt(pp[1].trim()); - } - } else if (line.startsWith("没收物品:")) { - for (String s : line.substring("没收物品:".length()) - .trim().split("[,,]")) - ac.confiscateItems.add( - s.trim().toUpperCase()); - } else if (line.startsWith("禁止使用物品:")) { - for (String s : line.substring("禁止使用物品:".length()) - .trim().split("[,,]")) - ac.denyUseItems.add( - s.trim().toUpperCase()); - } else if (line.equals("禁止放置方块")) { - ac.denyBlockPlace = true; - } else if (line.equals("禁止破坏方块")) { - ac.denyBlockBreak = true; - } else if (line.equals("禁止PVP")) { - ac.denyPVP = true; - } else if (line.equals("禁止摔伤")) { - ac.denyFallDamage = true; - } else if (line.equals("禁止饥饿")) { - ac.denyHunger = true; - } else if (line.equals("禁止一切伤害")) { - ac.denyAllDamage = true; - } else if (line.equals("禁止丢弃物品")) { - ac.denyDrop = true; - } else if (line.equals("禁止末影珍珠")) { - ac.denyEnderPearl = true; - } else if (line.equals("禁止使用弓箭")) { - ac.denyBow = true; - } else if (line.equals("禁止骑乘")) { - ac.denyMount = true; - } else if (line.equals("禁止袭击")) { - ac.denyRaid = true; - } else if (line.equals("禁止药水")) { - ac.denyPotion = true; - } else if (line.equals("禁止爆炸")) { - ac.denyExplosion = true; - } else if (line.equals("禁止燃烧") - || line.equals("禁止火焰蔓延")) { - ac.denyFireSpread = true; - } else if (line.equals("清除所有效果") - || line.equals("清理所有效果")) { - ac.clearAllBadEffects = true; - } else if (line.startsWith("清除") - || line.startsWith("清理")) { - String effName = line.substring(2).trim(); - if (effName.endsWith("效果")) - effName = effName.substring(0, - effName.length() - 2); - if (!effName.isEmpty()) - ac.clearEffects.add(effName); - } else if (line.contains("和平模式") - && !line.contains("白名单") - && !line.contains("定时") - && !line.contains("开关")) { - ac.peaceMode = true; - plugin.getLogger().info("[解析] 和平模式已识别"); - - } else if (line.startsWith("效果")) { - String rest = line.substring(2).trim(); - // 去掉前导冒号、空格 - while (rest.length() > 0 - && (rest.charAt(0) == ':' - || rest.charAt(0) == ':' - || rest.charAt(0) == ' ' - || rest.charAt(0) == '\t')) { - rest = rest.substring(1); - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - // 第一个是效果名(可能是中文) - String effName = parts[0]; - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) {} - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) {} - } - - // ★ 用映射方法转为英文名 - String resolved = resolveEffectName(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → " + resolved - + " 等级=" + effLv - + " 时长=" + effDur); - - ac.giveEffects.add(new String[]{ - resolved, - String.valueOf(effLv), - String.valueOf(effDur)}); - // ★ 加日志确认解析结果 - PotionEffectType checkType = - resolveEffectType(effName); - plugin.getLogger().info("[防护] 解析效果: " - + effName + " → type=" - + (checkType != null - ? checkType.getName() - : "NULL(失败)") - + " 等级=" + effLv - + " 时长=" + effDur); - - - } - if (!rest.isEmpty()) { - String[] parts = rest.split("\\s+"); - int effLv = 1; - int effDur = 999; - if (parts.length >= 2) { - try { - effLv = Integer.parseInt(parts[1]); - } catch (NumberFormatException ignored) { - } - } - if (parts.length >= 3) { - try { - effDur = Integer.parseInt(parts[2]); - } catch (NumberFormatException ignored) { - } - } - ac.giveEffects.add(new String[]{ - parts[0], - String.valueOf(effLv), - String.valueOf(effDur)}); - } - } else if (line.startsWith("通报批评:")) { - ac.announceTemplate = line.substring( - "通报批评:".length()).trim(); - ac.enableAnnounce = true; - } else if (line.startsWith("没收提示:")) { - ac.confiscateMsg = line.substring( - "没收提示:".length()).trim(); - } else if (line.startsWith("进入提示:")) { - String raw = line.substring("进入提示:".length()).trim(); - ac.enterMsg = raw; - - // ★ 调试:打印原文和Unicode - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★进入提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("离开提示:")) { - String raw = line.substring("离开提示:".length()).trim(); - ac.leaveMsg = raw; - - StringBuilder hex = new StringBuilder(); - for (int i = 0; i < raw.length(); i++) { - hex.append(String.format("U+%04X ", - (int) raw.charAt(i))); - } - plugin.getLogger().info( - "[防护-解析] ★★★离开提示原文=[" + raw - + "] 长度=" + raw.length() - + " Unicode=" + hex.toString()); - } else if (line.startsWith("惩罚命令:")) { - ac.punishCommands.add(line.substring( - "惩罚命令:".length()).trim()); - } else if (line.startsWith("强制游戏模式:") - || line.startsWith("游戏模式:")) { - String prefix = line.startsWith("强制游戏模式:") - ? "强制游戏模式:" : "游戏模式:"; - String modeStr = line.substring(prefix.length()) - .trim().toUpperCase(); - // 容错:支持多种写法 - if (modeStr.equals("CREATIVE") || modeStr.equals("创造") - || modeStr.equals("1")) { - ac.enforceGameMode = "CREATIVE"; - } else if (modeStr.equals("SURVIVAL") || modeStr.equals("生存") - || modeStr.equals("0")) { - ac.enforceGameMode = "SURVIVAL"; - } else if (modeStr.equals("ADVENTURE") || modeStr.equals("冒险") - || modeStr.equals("2")) { - ac.enforceGameMode = "ADVENTURE"; - } else if (modeStr.equals("SPECTATOR") || modeStr.equals("旁观") - || modeStr.equals("spectator") - || modeStr.equals("3")) { - ac.enforceGameMode = "SPECTATOR"; - } - } else if (line.contains("排除名单") - || line.contains("模式排除") - || line.contains("模式豁免")) { - int colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf(':'); - if (colonIdx < 0) colonIdx = line.indexOf('='); - if (colonIdx >= 0) { - String rest = line.substring(colonIdx + 1).trim(); - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playerName = s.trim(); - if (!playerName.isEmpty()) { - ac.modeExempt.add(playerName); - } - } - } - - String rest = line.substring(/*idx*/ + 4).trim(); - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String playername = s.trim(); - if (!name.isEmpty()) ac.modeExempt.add(name); - } - } - - while (rest.length() > 0) { - char c = rest.charAt(0); - if (c == ':' || c == ':' || c == '=' - || c == ' ' || c == '\t') { - rest = rest.substring(1); - } else { - break; - } - - } - if (!rest.isEmpty()) { - String cleaned = rest.replaceAll("[,,;;|、/]", ","); - for (String s : cleaned.split(",")) { - String wlName = s.trim(); - if (!wlName.isEmpty()) { - ac.peaceWhitelist.add(wlName); - } - } - } - - - - } else if (line.equals("禁止交互展示框") - || line.equals("禁止展示框")) { - ac.denyItemFrame = true; - - } - } - br.close(); - return ac; - } -// ===== 和平白名单操作 ===== - - // 添加生物名字到区域和平白名单 - public boolean addPeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.add(creatureName); - savePeaceWhitelist(ac); - return true; - } - - // 从区域和平白名单移除生物名字 - public boolean removePeaceName(String areaName, String creatureName) { - AreaConfig ac = areas.get(areaName); - if (ac == null) return false; - ac.peaceWhitelist.remove(creatureName); - savePeaceWhitelist(ac); - return true; - } - -// ===== 模式排除名单操作 ===== - - - @EventHandler - public void onCreatureSpawn(CreatureSpawnEvent e) { - Entity entity = e.getEntity(); - if (!isHostile(entity.getType().name())) return; - - Location loc = entity.getLocation(); - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - - if (ac == null || !ac.peaceMode) return; - - protectedEntities.put( - entity.getUniqueId(), - System.currentTimeMillis() + 5000); - - /* plugin.getLogger().info("[和平] " - + entity.getType().name() - + " 获得5秒保护期" - + " 区域=" + ac.name - + " 白名单=" + ac.peaceWhitelist - + " 白名单数量=" + ac.peaceWhitelist.size());*/ - } - - - public void cleanupProtectedEntities() { - long now = System.currentTimeMillis(); - int cleaned = 0; - Iterator> it = - protectedEntities.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (now > entry.getValue()) { - it.remove(); - cleaned++; - } - } - if (cleaned > 0) { - /* plugin.getLogger().info("[和平模式] 清理过期保护: " - + cleaned + "个 剩余:" - + protectedEntities.size() + "个");*/ - } - } - - - public void banHostilesWithWhitelist(Player p, AreaConfig ac) { - long now = System.currentTimeMillis(); - // 看看附近到底有什么实体 - List nearby = p.getNearbyEntities(48, 48, 48); - /* plugin.getLogger().info("[和平] " + p.getName() - + " 附近实体总数=" + nearby.size());*/ - for (Entity ent : nearby) { - String typeName = ent.getType().name(); - /* plugin.getLogger().info("[和平] 实体: " - + typeName + " isHostile=" + isHostile(typeName));*/ - } - int scanned = 0; - int waiting = 0; - int saved = 0; - int banished = 0; - - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (!isHostile(ent.getType().name())) continue; - scanned++; - - UUID entUid = ent.getUniqueId(); - Long protectUntil = protectedEntities.get(entUid); - String customName = ent.getCustomName(); - - // 有保护期记录 - if (protectUntil != null) { - long remain = protectUntil - now; - - if (remain > 0) { - // 保护期内,精确剩余秒数 - int secondsLeft = (int) Math.ceil(remain / 1000.0); - waiting++; - - // 保护期内也检查名字(玩家可能刚贴上) - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - protectedEntities.remove(entUid); - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 白名单匹配,永久保留");*/ - continue; - } - - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 保护期剩余约" + secondsLeft + "秒");*/ - continue; - } - - // 保护期到了,最终检查 - protectedEntities.remove(entUid); - /* plugin.getLogger().info("[和平] " - + ent.getType().name() + " 5秒到期");*/ - - // 到期时再查一次白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 到期但白名单匹配,保留");*/ - continue; - } - - // 没名字或不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名=[" + customName + "] 传送虚空");*/ - continue; - } - - // 没有保护期记录(非刷怪蛋生成的或记录被清了) - // 也检查一下白名单 - if (customName != null - && !customName.isEmpty() - && ac.peaceWhitelist.contains(customName)) { - saved++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 命名[" + customName - + "] 无保护期但白名单匹配,保留");*/ - continue; - } - - // 没有保护期且不在白名单,传送虚空 - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - banished++; - /* plugin.getLogger().info("[和平] " - + ent.getType().name() - + " 无保护期,命名=[" + customName + "] 传送虚空");*/ - } - - /* plugin.getLogger().info("[和平] " - + " 扫描=" + scanned - + " 等待=" + waiting - + " 保留=" + saved - + " 传送=" + banished - + " 白名单=" + ac.peaceWhitelist);*/ - } - - private static final Set HOSTILE_TYPES = new HashSet<>(Arrays.asList( - "ZOMBIE", "ZOMBIE_VILLAGER", "HUSK", "DROWNED", - "SKELETON", "STRAY", "BOGGED", "WITHER_SKELETON", - "CREEPER", "SPIDER", "CAVE_SPIDER", "ENDERMITE", - "SILVERFISH", "BLAZE", "GHAST", "BIG_GHAST", - "MAGMA_CUBE", "SLIME", "WITCH", - "GUARDIAN", "ELDER_GUARDIAN", - "PILLAGER", "VINDICATOR", "EVOKER", "RAVAGER", - "ILLUSIONER", "VEX", - "PHANTOM", "SHULKER", "WARDEN", "BREEZE", - "PIGLIN", "PIGLIN_BRUTE", "ZOGLIN", - "ZOMBIFIED_PIGLIN", "ENDER_DRAGON" - )); - - private boolean isHostile(String typeName) { - return HOSTILE_TYPES.contains(typeName); - } - - // ==================== 白名单持久化 ==================== - - /** - * 保存白名单到文件 - * 路径:区域防护/whitelists/全局白名单.txt - * 路径:区域防护/whitelists/区域名.txt - */ - public void saveWhitelists() { - try { - // 1. 保存全局白名单 - PrintWriter pwGlobal = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(globalWhitelistFile), - StandardCharsets.UTF_8)); - for (String name : globalPlayerWhitelist) { - pwGlobal.println(name); - } - pwGlobal.close(); - - // 2. 删除旧的区域白名单文件(防止残留) - File[] oldFiles = whitelistDir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (oldFiles != null) { - for (File f : oldFiles) f.delete(); - } - - // 3. 保存各区域白名单 - for (Map.Entry> entry - : areaPlayerWhitelist.entrySet()) { - String name = entry.getKey(); - File areaFile = new File( - whitelistDir, name + ".txt"); - PrintWriter pwArea = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(areaFile), - StandardCharsets.UTF_8)); - for (String playerName : entry.getValue()) { - pwArea.println(playerName); - } - pwArea.close(); - } - - plugin.getLogger().info("[防护] 白名单已保存"); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 保存白名单失败: " - + e.getMessage()); - } - } - - - - // ==================== 区域检测 ==================== - - public AreaConfig getArea(String worldName, - int x, int y, int z) { - for (AreaConfig ac : areas.values()) { - if (ac.world.equalsIgnoreCase(worldName) - && inRect(x, z, ac) - && y >= ac.yMin - && y <= ac.yMax) { - return ac; - } - } - return null; - } - - private boolean inRect(int x, int z, AreaConfig ac) { - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - return x >= minX && x <= maxX - && z >= minZ && z <= maxZ; - } - - // ==================== 白名单检查 ==================== - - private boolean isPlayerWhitelisted(String player, - AreaConfig ac) { - if (globalPlayerWhitelist.contains( - player.toLowerCase())) - return true; - Set areaList = - areaPlayerWhitelist.get(ac.name); - return areaList != null - && areaList.contains(player.toLowerCase()); - } - - private boolean isItemBlacklisted(String itemId, - AreaConfig ac) { - if (globalItemBlacklist.contains( - itemId.toUpperCase())) - return true; - Set areaList = - areaItemBlacklist.get(ac.name); - return areaList != null - && areaList.contains(itemId.toUpperCase()); - } - - // ==================== 没收 ==================== - - private int confiscateItem(Player p, String itemTypeId) { - Material mat = Material.matchMaterial(itemTypeId); - if (mat == null) return 0; - ItemStack[] contents = p.getInventory().getContents(); - int removed = 0; - for (int i = 0; i < contents.length; i++) { - if (contents[i] != null - && contents[i].getType() == mat) { - removed += contents[i].getAmount(); - contents[i] = null; - } - } - p.getInventory().setContents(contents); - return removed; - } - - // ==================== 边框显示 ==================== - private void showBorder(Player p, AreaConfig ac, boolean temp) { - cancelBorder(p); - int taskId = Bukkit.getScheduler() - .runTaskTimer(plugin, new Runnable() { - @Override - public void run() { - if (!p.isOnline()) return; - Location loc = p.getLocation(); - int px = loc.getBlockX(); - int py = loc.getBlockY(); - int pz = loc.getBlockZ(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - for (int dy = 5; dy <= 30; dy += 5) { - for (int x = minX; x <= maxX; x += 4) { - for (int z = minZ; z <= maxZ; z += 4) { - if (x == minX || x == maxX - || z == minZ || z == maxZ) { - p.spawnParticle(Particle.END_ROD, - x + 0.5, py + dy, z + 0.5, - 1, 0, 0, 0, 0); - } - } - } - } - } - }, 0L, 20L).getTaskId(); - displayTaskIds.put(p.getUniqueId(), taskId); // taskId 是 int - if (temp) { - Bukkit.getScheduler().runTaskLater(plugin, - () -> cancelBorder(p), 300L); - } - } - - - private void cancelBorder(Player p) { - Integer taskId = displayTaskIds.remove(p.getUniqueId()); - if (taskId != null) { - Bukkit.getScheduler().cancelTask(taskId); - } - } - - public void onPlayerQuit(String playerName) { - playerCurrentArea.remove(playerName); - } - - public void clearAllPlayerAreas() { - playerCurrentArea.clear(); - } - private void saveModeExempt(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("排除名单") - || trimmed.contains("模式排除") - || trimmed.contains("模式豁免")) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.modeExempt.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("排除名单:"); - boolean first = true; - for (String name : ac.modeExempt) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存模式排除名单失败: " + e.getMessage()); - } - } - - // ==================== 事件监听 ==================== - @EventHandler - public void onPlayerMove(PlayerMoveEvent event) { - Player p = event.getPlayer(); - UUID uid = p.getUniqueId(); - Location to = event.getTo(); - if (to == null) return; - - Location from = event.getFrom(); - if (from.getBlockX() == to.getBlockX() - && from.getBlockZ() == to.getBlockZ()) { - return; - } - - // 防抖 - Long lastTime = lastMoveProcess.get(uid); - long now = System.currentTimeMillis(); - if (lastTime != null - && now - lastTime < MOVE_DEBOUNCE_MS) { - return; - } - lastMoveProcess.put(uid, now); - - // 检测区域 - String oldArea = playerCurrentArea.get(uid); - String newArea = null; - String forcedMode = null; - - for (Map.Entry entry - : areas.entrySet()) { - AreaConfig ac = entry.getValue(); - if (!ac.world.equals(to.getWorld().getName())) - continue; - int px = to.getBlockX(); - int pz = to.getBlockZ(); - int py = to.getBlockY(); - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - if (px >= minX && px <= maxX - && pz >= minZ && pz <= maxZ - && py >= ac.yMin && py <= ac.yMax) { - newArea = entry.getKey(); - forcedMode = ac.enforceGameMode; - break; - } - } - - /* plugin.getLogger().info( - "[防护-移动] " + p.getName() - + " 旧=[" + oldArea - + "] 新=[" + newArea + "]" - + " areas.size=" + areas.size() - + " world=" + to.getWorld().getName());*/ - - // ===== 2. 区域没变 ===== - if (Objects.equals(oldArea, newArea)) { - if (newArea != null) { - AreaConfig ac = areas.get(newArea); - if (ac != null) { - // ★ 每次移动都清除指定效果 - clearBadEffects(p, ac); - - // ★ 每次移动都强制游戏模式 - if (ac.enforceGameMode != null - && !ac.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - } - return; - } - - // 离开旧区域 - if (oldArea != null) { - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★开始离开处理: - + oldArea);*/ - - AreaConfig oldAc = areas.get(oldArea); - - // 离开提示 - if (oldAc != null && oldAc.leaveMsg != null - && !oldAc.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(oldAc.leaveMsg)); - } - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★收标: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - plugin.getLogger().info( - "[防护-移动-离开] 标记项: " + t.getName()); - } - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - plugin.getLogger().info( - "[防护-移动-离开] 取消旧延时"); - } - - // 创建延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = oldArea; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - /* plugin.getLogger().info( - "[防护-移动-离开] ★★★创建延时任务: " - + toClear.size() + "个");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-移动-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); - - plugin.getLogger().info( - "[防护-移动-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size()); - - if (online == null || !online.isOnline()) { - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-移动-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-移动-清理] + cleared" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - } - } - - - // 进入新区域 - if (newArea != null) { - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - if (oldP != null) { - diffEffects(p, uid, oldP, newArea); - } - } - - AreaConfig newAc = areas.get(newArea); - if (newAc != null) { - if (newAc.enterMsg != null - && !newAc.enterMsg.isEmpty()) { - p.sendMessage( - formatAreaMsg(newAc.enterMsg)); - } - applyRegionEffects(p, uid, newArea, newAc); - // 清除效果 - clearBadEffects(p, newAc); - - } -// ===== 清除指定效果 ===== - if (newAc.clearEffects != null - && !newAc.clearEffects.isEmpty()) { - for (String effName : newAc.clearEffects) { - PotionEffectType type = - resolveEffectType(effName); - if (type != null && p.getPotionEffect(type) != null) { - p.removePotionEffect(type); - plugin.getLogger().info( - "[防护] ★清除效果: " + effName - + " 玩家=" + p.getName()); - } - } - } - -// ===== 清除所有效果 ===== - if (newAc.clearAllBadEffects) { - for (PotionEffect eff : p.getActivePotionEffects()) { - p.removePotionEffect(eff.getType()); - } - plugin.getLogger().info( - "[防护] ★清除所有效果, 玩家=" + p.getName()); - } - - if (forcedMode != null - && !isExemptFromGameMode(p, newArea)) { - GameMode target = - GameMode.valueOf(forcedMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - if (newArea != null) { - playerCurrentArea.put(uid, newArea); - } else { - playerCurrentArea.remove(uid); - } - } -// ===== 清理袭击生物 ===== - - // 需要清理的袭击生物类型 - private static final Set RAID_MOBS = new HashSet<>(Arrays.asList( - "VEX", - "EVOKER", - "PILLAGER", - "VINDICATOR", - "WITCH", - "RAVAGER", - "PILLAGER" - )); - - @EventHandler - public void onEntitySpawn(EntitySpawnEvent event) { - Entity entity = event.getEntity(); - Location loc = entity.getLocation(); - - // 检查是否在防护区域内 - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), loc.getBlockY(), - loc.getBlockZ()); - if (ac == null) return; - - // 禁止袭击:取消袭击事件 - if (ac.denyRaid) { - // 清理袭击生物 - String typeName = entity.getType().name(); - if (RAID_MOBS.contains(typeName)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] ★清理袭击生物: " + typeName - + " 区域=" + ac.name); - } - } - - // 没收物品检测 - if (ac.confiscateItems != null - && !ac.confiscateItems.isEmpty()) { - if (entity instanceof Item) { - Item item = (Item) entity; - String itemType = item.getItemStack() - .getType().name(); - if (ac.confiscateItems.contains(itemType)) { - event.setCancelled(true); - plugin.getLogger().info( - "[防护] 没收违禁掉落物: " - + itemType); - } - } - } - } - - - /** - * 进入区域时发放效果 - * 流程:查DB → 有记录跳过写入 → 无记录写入DB - * → 给效果 → 贴标 - */ - private void applyRegionEffects(Player p, UUID uid, - String areaName, AreaConfig ac) { - /* plugin.getLogger().info( - "[防护-贴标] ★★★方法被调用! 玩家=" - + p.getName() + " 区域=" + areaName - + " ac=" + (ac != null) - + " 效果配置数=" - + (ac != null ? ac.giveEffects.size() : "null"));*/ - - if (ac == null || ac.giveEffects.isEmpty()) return; - if (ac.giveEffects.isEmpty()) return; - // 查DB:该玩家在此区域是否已有效果记录 - List dbEffects = - getPlayerEffectNames(uid, areaName); - - if (dbEffects.isEmpty()) { - // 无记录 → 写入DB - for (String[] eff : ac.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t == null) continue; - saveSingleEffect(uid, p.getName(), - areaName, t.getName()); - } - plugin.getLogger().info( - "[防护] DB写入, 玩家=" + p.getName() - + " 区域=" + areaName); - } else { - plugin.getLogger().info( - "[防护] DB已有记录, 跳过写入, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - - // 给效果 + 贴标 - List applied = new ArrayList<>(); - for (String[] eff : ac.giveEffects) { - try { - PotionEffectType type = - resolveEffectType(eff[0]); - if (type == null) continue; - int lv = Integer.parseInt(eff[1]) - 1; - int dur = Integer.parseInt(eff[2]) * 20; - - PotionEffect existing = - p.getPotionEffect(type); - if (existing != null - && existing.getAmplifier() >= lv) { - continue; - } - p.addPotionEffect( - new PotionEffect(type, dur, lv)); - applied.add(type); - } catch (Exception ignored) { - } - } - - // 贴标 - // 新代码(合并,不覆盖): - List existingMarks = - playerMarkedEffects.get(uid); - if (existingMarks != null && !existingMarks.isEmpty()) { - // 已有标记,合并新增的 - for (PotionEffectType t : applied) { - if (!existingMarks.contains(t)) { - existingMarks.add(t); - } - } - /* plugin.getLogger().info( - "[防护-贴标] 合并标记: " - + existingMarks.size() - + " 个, 玩家=" + p.getName());*/ - } else { - // 无标记,新建 - playerMarkedEffects.put(uid, applied); - /* plugin.getLogger().info( - "[防护-贴标] 新建标记: " - + applied.size() - + " 个, 玩家=" + p.getName());*/ - } - } - - /** - * 离开区域时,延时15秒清理效果 - * 流程:收标 → 15秒后清效果 → 确认清空 → 清DB - */ - private void scheduleEffectClear(Player p, UUID uid, - String areaName) { - // 收标 - List marked = - playerAppliedEffects.remove(uid); - - plugin.getLogger().info( - "[防护] 收标 " + (marked != null ? marked.size() : 0) - + " 个, 玩家=" + p.getName() - + " 区域=" + areaName); - - // 启动15秒延时 - pendingClearArea.put(uid, areaName); - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - - // 清除效果 - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - } - - // 确认清空后清DB - removePlayerEffects(uid, areaName); - plugin.getLogger().info( - "[防护] 15秒已到, 清除 " - + (marked != null ? marked.size() : 0) - + " 个效果 + DB, 玩家=" - + p.getName() - + " 区域=" + areaName); - } - }.runTaskLater(plugin, 300L); // 15秒 = 300 tick - - pendingClearTask.put(uid, task); - } - public Set getAllAreaNames() { - return areas.keySet(); - } - - /** - * 15秒内进了新区域: - * 清除旧区域有、新区域没有的效果 - */ - private void diffEffects(Player p, UUID uid, - String oldAreaName, String newAreaName) { - AreaConfig oldAc = areas.get(oldAreaName); - AreaConfig newAc = areas.get(newAreaName); - if (oldAc == null || newAc == null) return; - - // ★ 从 pendingClearEffects 获取旧标记 - List oldMarked = - pendingClearEffects.remove(uid); - - if (oldMarked == null || oldMarked.isEmpty()) { - plugin.getLogger().info( - "[防护-跨区域] 无待清理效果"); - return; - } - - // 新区域的效果集合 - Set newTypes = new HashSet<>(); - if (newAc != null) { - for (String[] eff : newAc.giveEffects) { - PotionEffectType t = - resolveEffectType(eff[0]); - if (t != null) newTypes.add(t); - } - } - - int cleared = 0; - for (PotionEffectType type : oldMarked) { - if (!newTypes.contains(type)) { - p.removePotionEffect(type); - cleared++; - plugin.getLogger().info( - "[防护-跨区域] 清除: " - + type.getName()); - } else { - plugin.getLogger().info( - "[防护-跨区域] 保留: " - + type.getName() - + " (新区域也有)"); - } - } - - removePlayerEffects(uid, oldAreaName); - plugin.getLogger().info( - "[防护-跨区域] 完成: 清除" - + cleared + "/" + oldMarked.size() - + " 个, " + oldAreaName - + " → " + newAreaName); - } - -// ===== DB 辅助方法(简化版)===== - - /** - * 查询玩家在某个区域的效果记录(返回英文名列表) - */ - private List getPlayerEffectNames( - UUID uid, String areaName) { - List list = new ArrayList<>(); - try { - PreparedStatement ps = dbConnection.prepareStatement( - "SELECT effect_type FROM player_effects " - + "WHERE uuid=? AND area_name=?"); - ps.setString(1, uid.toString()); - ps.setString(2, areaName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - list.add(rs.getString("effect_type")); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 查询效果失败: " + e.getMessage()); - } - return list; - } - - /** - * 写入单条效果记录 - */ - private void saveSingleEffect(UUID uid, - String playerName, String areaName, - String effectType) { - try { - PreparedStatement ps = dbConnection.prepareStatement( - "INSERT INTO player_effects " - + "(uuid, player_name, area_name, " - + "effect_type, effect_level, " - + "effect_duration, enter_time) " - + "VALUES (?,?,?,?,?,?,?)"); - ps.setString(1, uid.toString()); - ps.setString(2, playerName); - ps.setString(3, areaName); - ps.setString(4, effectType); - ps.setInt(5, 0); - ps.setInt(6, 999); - ps.setLong(7, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - plugin.getLogger().warning( - "[防护] 写入效果失败: " + e.getMessage()); - } - } - - // ==================== 违禁品没收 ==================== - - /** - * 检查玩家是否豁免于没收 - * 全局白名单 / 区域白名单 / modeExempt / OP 都跳过 - */ - private boolean isExemptFromConfiscation( - Player p, String areaName) { - String name = p.getName(); - - if (globalPlayerWhitelist.contains(name)) - return true; - - Set areaWl = - areaPlayerWhitelist.get(areaName); - if (areaWl != null && areaWl.contains(name)) - return true; - - AreaConfig ac = areas.get(areaName); - if (ac != null && ac.modeExempt.contains(name)) - return true; - - return false; - } - - /** - * 没收玩家背包中的违禁品 - */ - private void confiscateItems(Player p, - AreaConfig ac) { - boolean confiscated = false; - - for (int i = 0; - i < p.getInventory().getSize(); i++) { - ItemStack item = p.getInventory().getItem(i); - if (item == null) continue; - - String typeName = item.getType().name(); - if (ac.confiscateItems - .contains(typeName)) { - p.getInventory().setItem(i, null); - confiscated = true; - } - } - - if (confiscated) { - String msg = ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty() - ? ac.confiscateMsg - : "§c你的违禁品已被没收"; - p.sendMessage(msg); - - // 通报批评 - if (ac.enableAnnounce - && ac.announceTemplate != null) { - String text = ac.announceTemplate - .replace("{player}", - p.getName()); - Bukkit.broadcastMessage(text); - } - } - } - - - - private String getChineseGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: return "生存模式"; - case CREATIVE: return "创造模式"; - case ADVENTURE: return "冒险模式"; - case SPECTATOR: return "旁观模式"; - default: return mode.name(); - } - } - - private void handleEnter(Player p, AreaConfig ac) { - UUID uid = p.getUniqueId(); - - // 清除负面效果 - clearBadEffects(p, ac); - - // 给予效果 - List given = new ArrayList<>(); - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - given.add(type); - } - } - - removePlayerEffects(uid, ac.name); - if (!given.isEmpty()) { - // savePlayerEffects(uid, p.getName(), ac.name, given); - } - - // 白名单检查(最先执行) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 游戏模式切换(只对非白名单玩家) - if (ac.enforceGameMode != null) { - boolean exempt = isPlayerExemptFromModeChange(p, ac); - if (!exempt) { - GameMode target = GameMode.valueOf(ac.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - p.sendMessage("§e§l[区域防护] §f游戏模式已切换为: " - + getChineseGameMode(target)); - } - } - } - - // 和平模式/袭击检查 - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - handleConfiscate(p, ac); - } - - private boolean isPlayerExemptFromModeChange( - Player p, AreaConfig ac) { - // if (p.isOp()) return true; - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return true; - } - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return true; - } - if (ac.modeExempt.contains(p.getName())) { - return true; - } - return false; - } - - private void banRangedHostiles(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(48, 48, 48)) { - if (isRangedHostile(ent.getType().name())) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(ent.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - /** - * 按配置清除效果 - * denyRaid → 清除袭击相关 - * denyAllEffects → 清除所有负面+中性 - * denyAllEffects > denyRaid(全清优先) - */ - private void clearBadEffects(Player p, AreaConfig ac) { - // 和平模式 > 清除所有效果 > 禁止单个 - if (ac.peaceMode || ac.clearAllBadEffects) { - PotionEffectType[] allBad = { - PotionEffectType.SLOWNESS, - PotionEffectType.MINING_FATIGUE, - PotionEffectType.INSTANT_DAMAGE, - PotionEffectType.NAUSEA, - PotionEffectType.BLINDNESS, - PotionEffectType.HUNGER, - PotionEffectType.WEAKNESS, - PotionEffectType.POISON, - PotionEffectType.WITHER, - PotionEffectType.LEVITATION, - PotionEffectType.UNLUCK, - PotionEffectType.DARKNESS, - PotionEffectType.WIND_CHARGED, - PotionEffectType.WEAVING, - PotionEffectType.OOZING, - PotionEffectType.INFESTED, - PotionEffectType.BAD_OMEN, - PotionEffectType.TRIAL_OMEN, - PotionEffectType.RAID_OMEN - }; - for (PotionEffectType type : allBad) { - p.removePotionEffect(type); - } - return; - } - - // 禁止袭击 → 自动关联清除 - if (ac.denyRaid) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - p.removePotionEffect(PotionEffectType.RAID_OMEN); - } - - // 配置文件单个清除 - for (String effName : ac.clearEffects) { - PotionEffectType type = resolveEffectType(effName); - if (type != null) { - p.removePotionEffect(type); - } - } - } - - - private PotionEffectType resolveEffectType(String name) { - if (name == null || name.isEmpty()) return null; - - // 去掉"效果"后缀 - String clean = name; - if (clean.endsWith("效果")) { - clean = clean.substring(0, clean.length() - 2); - } - - // 硬编码中文名→英文ID映射(不依赖任何外部Map) - switch (clean) { - // 负面 - case "缓慢": - return resolveEffectType("slowness"); - case "挖掘疲劳": - return resolveEffectType("mining_fatigue"); - case "瞬间伤害": - return resolveEffectType("instant_damage"); - case "反胃": - return resolveEffectType("nausea"); - case "失明": - return resolveEffectType("blindness"); - case "饥饿": - return resolveEffectType("hunger"); - case "虚弱": - return resolveEffectType("weakness"); - case "中毒": - return resolveEffectType("poison"); - case "凋零": - return resolveEffectType("wither"); - case "飘浮": - return resolveEffectType("levitation"); - case "霉运": - return resolveEffectType("unluck"); - case "黑暗": - return resolveEffectType("darkness"); - case "蓄风": - return resolveEffectType("wind_charged"); - case "盘丝": - return resolveEffectType("weaving"); - case "渗浆": - return resolveEffectType("oozing"); - case "寄生": - return resolveEffectType("infested"); - // 中性 - case "不祥之兆": - case "不祥征兆": - return resolveEffectType("bad_omen"); - case "袭击之兆": - case "袭击征兆": - return resolveEffectType("raid_omen"); - case "试炼之兆": - case "试炼征兆": - return resolveEffectType("trial_omen"); - // 正面 - case "迅捷": - return resolveEffectType("speed"); - case "急迫": - return resolveEffectType("haste"); - case "力量": - return resolveEffectType("strength"); - case "瞬间治疗": - return resolveEffectType("instant_health"); - case "跳跃提升": - return resolveEffectType("jump_boost"); - case "生命恢复": - return resolveEffectType("regeneration"); - case "抗性提升": - return resolveEffectType("resistance"); - case "抗火": - return resolveEffectType("fire_resistance"); - case "水下呼吸": - return resolveEffectType("water_breathing"); - case "隐身": - return resolveEffectType("invisibility"); - case "夜视": - return resolveEffectType("night_vision"); - case "发光": - return resolveEffectType("glowing"); - case "生命提升": - return resolveEffectType("health_boost"); - case "伤害吸收": - return resolveEffectType("absorption"); - case "饱和": - return resolveEffectType("saturation"); - case "幸运": - return resolveEffectType("luck"); - case "村庄英雄": - return resolveEffectType("hero_of_the_village"); - case "缓降": - return resolveEffectType("slow_falling"); - case "潮涌能量": - return resolveEffectType("conduit_power"); - case "海豚的恩惠": - return resolveEffectType("dolphins_grace"); - case "致命中毒": - return resolveEffectType("poison"); - default: - break; - } - - -// 兜底:直接用原版英文ID查(★ 注意是 PotionEffectType.getByName,不是 resolveEffectType) - PotionEffectType fallback = PotionEffectType.getByName(clean); - if (fallback != null) return fallback; - fallback = PotionEffectType.getByName(clean.toLowerCase()); - if (fallback != null) return fallback; - - plugin.getLogger().warning("[防护] 无法识别效果: " + name); - return null; - - } - - - private static final Map EFFECT_MAP - = new HashMap<>(); - - - private void clearRaidEffects(Player p) { - boolean cleared = false; - try { - if (p.hasPotionEffect(PotionEffectType.BAD_OMEN)) { - p.removePotionEffect(PotionEffectType.BAD_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - try { - if (p.hasPotionEffect(PotionEffectType.RAID_OMEN)) { - p.removePotionEffect(PotionEffectType.RAID_OMEN); - cleared = true; - } - } catch (Exception ignored) { - } - if (cleared) { - p.sendMessage("§c§l[区域防护] §f已清除袭击相关效果"); - } - } - - public Map> - getPendingClearEffects() { - return pendingClearEffects; - } - - public void onPlayerOffline(UUID uid, String playerName, - String areaName) { - // 清除该玩家在该区域的所有效果 - List given = getPlayerEffects(uid, areaName); - for (PotionEffectType type : given) { - // 只清除我们给予的效果 - Player p = Bukkit.getPlayer(uid); - if (p != null) { - p.removePotionEffect(type); - } - } - // 清除DB记录 - removePlayerEffects(uid, areaName); - plugin.getLogger().info("[防护] " + playerName - + " 下线,已清除" + given.size() - + "个效果并清理DB"); - } - - @EventHandler - public void onTeleport(PlayerTeleportEvent e) { - Location from = e.getFrom(); - Location to = e.getTo(); - if (to == null) return; - - if (from.getWorld() == to.getWorld() - && from.distance(to) < 5) return; - - Player p = e.getPlayer(); - UUID uid = p.getUniqueId(); - - // 标记传送中 - teleporting.add(uid); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - teleporting.remove(uid); - }, 5L); - - AreaConfig fromArea = getArea( - from.getWorld().getName(), - from.getBlockX(), from.getBlockY(), - from.getBlockZ()); - AreaConfig toArea = getArea( - to.getWorld().getName(), - to.getBlockX(), to.getBlockY(), - to.getBlockZ()); - - String fromName = fromArea != null - ? fromArea.name : null; - String toName = toArea != null - ? toArea.name : null; - - /* plugin.getLogger().info( - "[防护-传送] ★★★进入onTeleport: " - + p.getName() - + " [" + fromName + "] → [" + toName + "]" - + " teleporting=" + teleporting.contains(uid));*/ - - if (Objects.equals(fromName, toName)) { - /* plugin.getLogger().info( - "[防护-传送] 区域相同, 跳过");*/ - return; - } - - // ===== 离开旧区域 ===== - if (fromName != null) { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★开始离开处理: " - + fromName);*/ - - // 收标 - List marked = - playerMarkedEffects.remove(uid); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★收标结果: " - + (marked != null ? marked.size() : "null"));*/ - - if (marked != null) { - for (PotionEffectType t : marked) { - /* plugin.getLogger().info( - "[防护-传送-离开] 标记项: " - + t.getName());*/ - } - } else { - /* plugin.getLogger().info( - "[防护-传送-离开] ★★标记为null! " - + "尝试其他key查找...");*/ - // 遍历查找可能的标记 - for (Map.Entry> entry - : playerMarkedEffects.entrySet()) { - if (entry.getKey().equals(uid)) { - /* plugin.getLogger().info( - "[防护-传送-离开] 找到标记! 效果数=" - + entry.getValue().size());*/ - } - } - /* plugin.getLogger().info( - "[防护-传送-离开] playerMarkedEffects大小=" - + playerMarkedEffects.size());*/ - } - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - } - - // 创建15秒延时清理 - if (marked != null && !marked.isEmpty()) { - String clearArea = fromName; - UUID clearUid = uid; - List toClear = - new ArrayList<>(marked); - pendingClearArea.put(uid, clearArea); - pendingClearEffects.put(uid, toClear); - - /* plugin.getLogger().info( - "[防护-传送-离开] ★★★创建延时任务: " - + toClear.size() + "个效果");*/ - - BukkitTask task = new BukkitRunnable() { - @Override - public void run() { - /* plugin.getLogger().info( - "[防护-传送-清理] ★★★延时触发!");*/ - pendingClearTask.remove(clearUid); - pendingClearArea.remove(clearUid); - - Player online = - Bukkit.getPlayer(clearUid); -/* - plugin.getLogger().info( - "[防护-传送-清理] 在线=" - + (online != null && online.isOnline()) - + " 效果数=" + toClear.size() - + " 区域=" + clearArea);*/ - - if (online == null - || !online.isOnline()) { - plugin.getLogger().info( - "[防护-传送-清理] 玩家不在线, 清DB"); - removePlayerEffects( - clearUid, clearArea); - return; - } - - int cleared = 0; - for (PotionEffectType type : toClear) { - PotionEffect eff = - online.getPotionEffect(type); - /* plugin.getLogger().info( - "[防护-传送-清理] 检查: " - + type.getName() - + " 身上=" + (eff != null));*/ - if (eff != null) { - online.removePotionEffect(type); - cleared++; - } - } - removePlayerEffects(clearUid, clearArea); - plugin.getLogger().info( - "[防护-传送-清理] ★★★完成: 清除" - + cleared + "/" + toClear.size()); - } - }.runTaskLater(plugin, 300L); - pendingClearTask.put(uid, task); - - } - - // 离开提示 - if (fromArea != null && fromArea.leaveMsg != null - && !fromArea.leaveMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(fromArea.leaveMsg)); - } - } - - // ===== 进入新区域 ===== - if (toName != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] ★★★开始进入处理: " - + toName);*/ - - // 取消旧延时 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - String oldP = pendingClearArea.remove(uid); - plugin.getLogger().info( - "[防护-传送-进入] 取消旧延时, 旧区域=" - + oldP); - if (oldP != null) { - diffEffects(p, uid, oldP, toName); - } - } - - // 进入提示 - if (toArea != null && toArea.enterMsg != null - && !toArea.enterMsg.isEmpty()) { - p.sendMessage(formatAreaMsg(toArea.enterMsg)); - } - - // 发放效果 - if (toArea != null) { - /* plugin.getLogger().info( - "[防护-传送-进入] 调用applyRegionEffects");*/ - applyRegionEffects(p, uid, toName, toArea); -// 清除效果 - clearBadEffects(p, toArea); - - if (toArea.confiscateItems != null - && !toArea.confiscateItems.isEmpty() - && !isExemptFromConfiscation( - p, toName)) { - confiscateItems(p, toArea); - } - } - - // 强制模式 - if (toArea != null && toArea.enforceGameMode != null - && !toArea.enforceGameMode.isEmpty() - && !isExemptFromGameMode(p, toName)) { - GameMode target = GameMode.valueOf( - toArea.enforceGameMode); - if (p.getGameMode() != target) { - p.setGameMode(target); - } - } - } - - // 更新记录 - if (toName != null) { - playerCurrentArea.put(uid, toName); - } else { - playerCurrentArea.remove(uid); - } - } - - - public Map getPendingClearTasks() { - return pendingClearTask; - } - - public Map> - getPlayerMarkedEffects() { - return playerMarkedEffects; - } - - public void handleInside(Player p, AreaConfig ac) { - clearBadEffects(p, ac); - - if (ac.peaceMode) { - banHostilesWithWhitelist(p, ac); - } else if (ac.denyRaid) { - banRaidMobs(p, ac); - } - - // 全白或区白 → 跳过所有限制性规则(包括没收) - if (isPlayerWhitelisted(p.getName(), ac)) return; - - handleConfiscate(p, ac); - } - - public void cleanupExpiredProtections() { - protectedEntities.entrySet().removeIf( - entry -> System.currentTimeMillis() > entry.getValue()); - } - - // 禁止袭击:只驱逐袭击怪物 - private void banRaidMobs(Player p, AreaConfig ac) { - for (Entity ent : p.getNearbyEntities(32, 32, 32)) { - String t = ent.getType().name(); - if (t.equals("PILLAGER") - || t.equals("VINDICATOR") - || t.equals("EVOKER") - || t.equals("RAVAGER") - || t.equals("VEX") - || t.equals("WITCH") - || t.equals("ILLUSIONER") - || t.equals("ZOMBIE_VILLAGER")) { - Location voidLoc = ent.getLocation().clone(); - voidLoc.setY(voidLoc.getWorld().getMinHeight() - 50); - ent.teleport(voidLoc); - } - } - } - - public Set getAreaNames() { - return areas.keySet(); - } - - public Collection getPendingTasks() { - return pendingClearTask.values(); - } - - - // 判断是否为远程攻击敌对生物 - private boolean isRangedHostile(String typeName) { - switch (typeName) { - // ===== 骷髅系 ===== - case "SKELETON": // 骷髅(弓箭) - case "STRAY": // 流浪者(迟缓之箭) - case "BOGGED": // 虫蚀骷髅(毒箭) - case "WITHER_SKELETON": // 凋灵骷髅(近战但算灾厄) - - // ===== 灾厄村民 ===== - case "PILLAGER": // 掠夺者(弩) - case "ILLUSIONER": // 幻术师(弓箭+失明) - case "EVOKER": // 幻魔者(唤魔) - case "VINDICATOR": // 卫道士(斧头近战) - case "VEX": // 恼鬼(剑近战,但会穿墙飞行) - - // ===== 法术/投射 ===== - case "BLAZE": // 烈焰人(火球) - case "WITCH": // 女巫(药水) - - // ===== 飞行/爆炸 ===== - case "GHAST": // 恶魂(火球) - case "PHANTOM": // 幻翼(俯冲) - case "BIG_GHAST": // 大恶魂(1.21新增) - - // ===== 水下 ===== - case "GUARDIAN": // 守卫者(激光) - case "ELDER_GUARDIAN": // 远古守卫者(激光) - - // ===== 远程特殊 ===== - case "SHULKER": // 潜影贝(潜影弹) - case "BREEZE": // 旋风人(风弹) - - // ===== 蜘蛛系(远程毒液攻击) ===== - case "SPIDER": // 蜘蛛(白天远程吐丝) - case "CAVE_SPIDER": // 洞穴蜘蛛(毒) - - // ===== 1.21新增 ===== - case "WIND_CHARGED": // 蓄风怪(风弹) - case "WEAVING": // 盘丝怪(吐丝) - case "OOZING": // 渗浆怪(史莱姆) - case "INFESTED": // 寄生怪(蠹虫) - - // ===== 盔甲/变种(部分版本) ===== - case "ARMORED_SKELETON": // 盔甲骷髅 - case "ARCHER_SKELETON": // 弓箭骷髅(部分版本) - case "SNOW_GOLEM": // 雪傀儡(不是敌对但远程) - - // ===== 亡灵骑乘 ===== - case "HUSK": // 尸壳(近战但属于灾厄系) - case "DROWNED": // 溺尸(三叉戟远程) - case "ZOMBIE_VILLAGER": // 僵尸村民 - case "ZOMBIE_PIGMAN": // 僵尸猪灵 - case "ZOMBIFIED_PIGLIN": // 僵尸猪灵(1.16+) - case "PIGLIN_BRUTE": // 猪灵蛮兵 - - // ===== 猪灵(弓箭) ===== - case "PIGLIN": // 猪灵(金弩远程) - - // ===== 末影系 ===== - case "ENDER_DRAGON": // 末影龙(远程吐息) - case "WARDEN": // 监守者(远程音波) - - // ===== 其他 ===== - case "CREPER": // 苦力怕(爆炸不算远程但危险) - case "CREEPER": // 苦力怕 - - return true; - default: - return false; - } - } - - - - private void applyEffects(Player p, AreaConfig ac) { - for (String[] parts : ac.giveEffects) { - String effName = parts[0]; - int level = 1; - int duration = 999; - try { level = Integer.parseInt(parts[1]); } - catch (NumberFormatException ignored) {} - try { duration = Integer.parseInt(parts[2]); } - catch (NumberFormatException ignored) {} - - PotionEffectType type = resolveEffectType(effName); - plugin.getLogger().info("[防护调试] 给予: " - + effName + " -> " + type); - if (type != null) { - p.addPotionEffect(new PotionEffect( - type, duration * 20, level - 1)); - plugin.getLogger().info("[防护调试] 已给予: " - + effName + " 等级" + level - + " 持续" + duration + "秒"); - } - } - } - - - @EventHandler - public void onBlockPlace(BlockPlaceEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockPlace - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止放置方块"); - } - } - - @EventHandler - public void onBlockBreak(BlockBreakEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - e.getBlock().getX(), - e.getBlock().getY(), - e.getBlock().getZ()); - if (ac != null && ac.denyBlockBreak - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏方块"); - } - } - - - @EventHandler - public void onDamage(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - // 保护性规则:对所有人无条件生效 - if (ac.denyAllDamage) { e.setCancelled(true); return; } - if (ac.denyFallDamage - && e.getCause() == EntityDamageEvent.DamageCause.FALL) - e.setCancelled(true); - } - - - @EventHandler - public void onPVP(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyPVP - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - - @EventHandler - public void onFood(FoodLevelChangeEvent e) { - if (!(e.getEntity() instanceof Player)) return; - Player p = (Player) e.getEntity(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyHunger) - e.setCancelled(true); - } - - - - @EventHandler - public void onDrop(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac != null && ac.denyDrop - && !isPlayerWhitelisted(p.getName(), ac)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止丢弃物品"); - } - } - - // 替换整个 onInteract 方法 - @EventHandler - public void onInteract(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (hand == null) return; - - // ===== 选区工具 ===== - if (hand.getType() == Material.BREEZE_ROD) { - ItemMeta meta = hand.getItemMeta(); - if (meta != null && meta.getDisplayName() != null - && meta.getDisplayName().contains("区域选择")) { - Action action = e.getAction(); - boolean isLeft = (action == Action.LEFT_CLICK_AIR - || action == Action.LEFT_CLICK_BLOCK); - boolean isRight = (action == Action.RIGHT_CLICK_AIR - || action == Action.RIGHT_CLICK_BLOCK); - if (isLeft || isRight) { - e.setCancelled(true); - Block block = e.getClickedBlock(); - if (block == null) { - try { block = p.getTargetBlockExact(5); } - catch (Exception ignored) {} - } - if (block == null) return; - UUID uid = p.getUniqueId(); - if (isLeft) { - pos1.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置1: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } else { - pos2.put(uid, block.getLocation()); - p.sendMessage("§a§l[防护] §f位置2: " - + block.getX() + ", " - + block.getY() + ", " - + block.getZ()); - } - } - return; - } - } - - // ===== 放置实体类物品时不检查(展示框、矿车、船、地图等)===== - Material mat = hand.getType(); - if (mat == Material.ITEM_FRAME - || mat == Material.GLOW_ITEM_FRAME - || mat == Material.MINECART - || mat == Material.TNT_MINECART - || mat == Material.CHEST_MINECART - || mat == Material.HOPPER_MINECART - || mat == Material.OAK_BOAT - || mat == Material.SPRUCE_BOAT - || mat == Material.BIRCH_BOAT - || mat == Material.JUNGLE_BOAT - || mat == Material.ACACIA_BOAT - || mat == Material.DARK_OAK_BOAT - || mat == Material.MANGROVE_BOAT - || mat == Material.CHERRY_BOAT - || mat == Material.BAMBOO_RAFT - || mat == Material.MAP - || mat == Material.ARMOR_STAND) { - return; - } - - // ===== 区域规则检查 ===== - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) return; - - // 末影珍珠禁止 - if (ac.denyEnderPearl - && hand.getType() == Material.ENDER_PEARL) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用末影珍珠"); - return; - } - - // 弓箭禁止 - if (ac.denyBow - && (hand.getType() == Material.BOW - || hand.getType() == Material.CROSSBOW)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用弓箭"); - return; - } - - // 白名单跳过 - if (isPlayerWhitelisted(p.getName(), ac)) return; - - // 禁止使用物品 - String typeId = hand.getType().name(); - if (ac.denyUseItems.contains(typeId)) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止使用此物品"); - return; - } - - // 没收检查 - handleConfiscate(p, ac); - } - - - - @EventHandler - public void onExplosion(EntityExplodeEvent e) { - Iterator it = e.blockList().iterator(); - while (it.hasNext()) { - Block b = it.next(); - AreaConfig ac = getArea( - b.getWorld().getName(), - b.getX(), b.getY(), b.getZ()); - if (ac != null && ac.denyExplosion) - it.remove(); - } - } - public void loadWhitelists() { - globalPlayerWhitelist.clear(); - areaPlayerWhitelist.clear(); - - if (!whitelistDir.exists()) { - whitelistDir.mkdirs(); - return; - } - - try { - // 加载全局白名单 - if (globalWhitelistFile.exists()) { - List lines = - java.nio.file.Files.readAllLines( - globalWhitelistFile.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - globalPlayerWhitelist.add(name); - } - } - } - - // 加载各区域白名单 - File[] files = whitelistDir.listFiles( - (File d, String n) -> n.endsWith(".txt") - && !n.equals("全局白名单.txt")); - if (files != null) { - for (File f : files) { - String areaName = f.getName() - .replace(".txt", ""); - Set set = - ConcurrentHashMap.newKeySet(); - List lines = - java.nio.file.Files.readAllLines( - f.toPath(), - StandardCharsets.UTF_8); - for (String line : lines) { - String name = line.trim(); - if (!name.isEmpty() - && !name.startsWith("#")) { - set.add(name); - } - } - if (!set.isEmpty()) { - areaPlayerWhitelist.put( - areaName, set); - } - } - } - - plugin.getLogger().info( - "[防护] 白名单加载完成,全局: " - + globalPlayerWhitelist.size() - + ",区域数: " - + areaPlayerWhitelist.size()); - } catch (Exception e) { - plugin.getLogger().warning( - "[防护] 加载白名单失败: " - + e.getMessage()); - } - } - - - @EventHandler - public void onPotionSplash(PotionSplashEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - AreaConfig ac = getArea( - shooter.getWorld().getName(), - shooter.getLocation().getBlockX(), - shooter.getLocation().getBlockY(), - shooter.getLocation().getBlockZ()); - if (ac != null && ac.denyPotion - && !isPlayerWhitelisted(shooter.getName(), ac)) { - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止使用药水"); - } - } - - /** - * 解析区域名:先精准匹配,再模糊匹配 - * @param input 用户输入的区域名 - * @return 匹配到的区域名,无匹配返回null - */ - public String resolveAreaName(String input) { - if (input == null || input.isEmpty()) return null; - if (input.equalsIgnoreCase("global")) return "global"; - - // 第一步:精准匹配 - for (String name : areas.keySet()) { - if (name.equalsIgnoreCase(input)) { - return name; - } - } - - // 第二步:包含匹配(用户只打了部分名字) - for (String name : areas.keySet()) { - if (name.contains(input) || input.contains(name)) { - return name; - } - } - - // 第三步:编辑距离模糊匹配(著称→主城) - String bestMatch = null; - int bestDistance = Integer.MAX_VALUE; - for (String name : areas.keySet()) { - int dist = levenshteinDistance( - input.toLowerCase(), name.toLowerCase()); - if (dist < bestDistance) { - bestDistance = dist; - bestMatch = name; - } - } - - // 容错阈值:编辑距离 <= 名字长度的一半 - if (bestMatch != null) { - int threshold = Math.max(1, bestMatch.length() / 2); - if (bestDistance <= threshold) { - return bestMatch; - } - } - - return null; - } - - /** - * 判断字符串是否是区域名(精准或模糊) - */ - private boolean isAreaName(String name) { - if (name.equalsIgnoreCase("global")) return false; - return resolveAreaName(name) != null; - } - - /** - * 获取模糊匹配的实际区域名(用于提示用户) - */ - private String getResolvedName(String input) { - return resolveAreaName(input); - } - - /** - * 编辑距离算法(莱文斯坦距离) - */ - private int levenshteinDistance(String s1, String s2) { - int[][] dp = new int[s1.length() + 1][s2.length() + 1]; - for (int i = 0; i <= s1.length(); i++) dp[i][0] = i; - for (int j = 0; j <= s2.length(); j++) dp[0][j] = j; - for (int i = 1; i <= s1.length(); i++) { - for (int j = 1; j <= s2.length(); j++) { - if (s1.charAt(i - 1) == s2.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1]; - } else { - dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], - Math.min(dp[i - 1][j], dp[i][j - 1])); - } - } - } - return dp[s1.length()][s2.length()]; - } - - /** - * 智能重排参数:找出子命令位置,重排为标准顺序 - * 标准顺序:/protect 子命令 参数1 参数2 ... - */ - private String[] smartReorderArgs(String[] original) { - if (original.length <= 1) return original; - - Set knownSubs = new HashSet<>(Arrays.asList( - "add", "remove", "additem", "removeitem", - "addname", "removename", "listname", - "addwhite", "removewhite", "listwhite", - "list", "listitem", "创建", "删除", "重载", - "工具", "expand", "contraction", - "on", "off", "tempon", "modeexempt" - )); - - String first = original[0].toLowerCase(); - if (knownSubs.contains(first)) { - return original; - } - - int subIndex = -1; - for (int i = 0; i < original.length; i++) { - if (knownSubs.contains(original[i].toLowerCase())) { - subIndex = i; - break; - } - } - - if (subIndex < 0) { - return original; - } - - String[] reordered = new String[original.length]; - reordered[0] = original[subIndex]; - int idx = 1; - for (int i = 0; i < original.length; i++) { - if (i == subIndex) continue; - reordered[idx] = original[i]; - idx++; - } - - return reordered; - } - - // ==================== 管理命令 ==================== - public boolean handleCommand(CommandSender sender, - String[] args) { - args = smartReorderArgs(args); - if (args.length == 0) { - showHelp(sender); - return true; - } - String sub = args[0].toLowerCase(); -// ===== 诊断命令 ===== - if (sub.equals("sdf1debug")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - sender.sendMessage("§e===== 调试信息 ====="); - sender.sendMessage("§7当前区域: " - + playerCurrentArea.get(uid)); - sender.sendMessage("§7标记效果数: " - + (playerMarkedEffects.containsKey(uid) - ? playerMarkedEffects.get(uid).size() - : "无标记")); - sender.sendMessage("§7待清理任务: " - + pendingClearTask.containsKey(uid)); - sender.sendMessage("§7区域数: " + areas.size()); - - if (playerMarkedEffects.containsKey(uid)) { - List marked = - playerMarkedEffects.get(uid); - for (PotionEffectType type : marked) { - sender.sendMessage("§7 标记: " - + type.getName() - + " 身上有=" - + (p.getPotionEffect(type) != null)); - } - } - return true; - } - -// ===== 强制清理命令(测试用)===== - if (sub.equals("testclear")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - UUID uid = p.getUniqueId(); - - List marked = - playerMarkedEffects.remove(uid); - if (marked != null) { - for (PotionEffectType type : marked) { - p.removePotionEffect(type); - } - sender.sendMessage("§a已强制清理 " - + marked.size() + " 个效果"); - } else { - sender.sendMessage("§c无标记效果可清理"); - } - - // 取消延时任务 - if (pendingClearTask.containsKey(uid)) { - pendingClearTask.get(uid).cancel(); - pendingClearTask.remove(uid); - pendingClearArea.remove(uid); - pendingClearEffects.remove(uid); - sender.sendMessage("§a已取消延时任务"); - } - - removePlayerEffects(uid, - playerCurrentArea.getOrDefault(uid, "")); - sender.sendMessage("§aDB已清理"); - return true; - } - - // ===== 工具 ===== - if (sub.equals("工具") || sub.equals("wand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - ItemStack tool = new ItemStack(WAND); - ItemMeta meta = tool.getItemMeta(); - meta.setDisplayName("§a§l区域选择工具"); - meta.setLore(Arrays.asList( - "§7左键: 位置1", "§7右键: 位置2")); - tool.setItemMeta(meta); - p.getInventory().addItem(tool); - p.sendMessage("§a§l[防护] §f已获取选择工具"); - return true; - } - - // ===== 创建 ===== - if (sub.equals("创建") || sub.equals("create")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 创建 <名>"); - return true; - } - Player p = (Player) sender; - UUID u = p.getUniqueId(); - if (!pos1.containsKey(u) - || !pos2.containsKey(u)) { - p.sendMessage("§c请先用工具选点"); - return true; - } - String areaName = args[1]; - File f = new File(rootDir, areaName + ".txt"); - if (f.exists()) { - p.sendMessage("§c区域已存在"); - return true; - } - Location l1 = pos1.get(u); - Location l2 = pos2.get(u); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 区域: " + areaName); - pw.println("起点: " + l1.getWorld().getName() - + "," + l1.getBlockX() - + "," + l1.getBlockZ()); - pw.println("终点: " + l2.getWorld().getName() - + "," + l2.getBlockX() - + "," + l2.getBlockZ()); - pw.println("高度范围: 0-255"); - pw.println(); - pw.println("# 禁止放置方块"); - pw.println("# 禁止破坏方块"); - pw.println("# 禁止PVP"); - pw.println("# 效果: 夜视 1 999"); - pw.println("# 进入提示: 欢迎来到保护区"); - pw.println("# 离开提示: 已离开保护区"); - pw.close(); - p.sendMessage("§a§l[防护] §f区域 " - + areaName + " 已创建"); - loadAllAreas(); - } catch (IOException ex) { - p.sendMessage("§c创建失败"); - } - return true; - } - - // ===== 和平白名单 ===== - if (sub.equals("addname")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别: " - + args[1] + " 或 " + args[2]); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 和平白名单"); - return true; - } - sender.sendMessage("§c用法: /protect addname " - + "<区域> <名字>"); - return true; - } - if (sub.equals("removename")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.peaceWhitelist.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " + parsed[0]); - return true; - } - sender.sendMessage("§c用法: /protect removename " - + "<区域> <名字>"); - return true; - } - if (sub.equals("listname")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listname <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 和平白名单:"); - if (ac.peaceWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.peaceWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 模式排除 ===== - if (sub.equals("addwhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.add(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已添加: " - + parsed[1] + " → " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect addwhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("removewhite")) { - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - AreaConfig ac = areas.get(parsed[0]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - ac.modeExempt.remove(parsed[1]); - saveAreaConfig(ac); - sender.sendMessage("§a已移除: " - + parsed[1] + " ← " - + parsed[0] + " 模式排除"); - return true; - } - sender.sendMessage("§c用法: /protect removewhite " - + "<区域> <玩家>"); - return true; - } - if (sub.equals("listwhite")) { - if (args.length < 2) { - sender.sendMessage( - "§c用法: /protect listwhite <区域>"); - return true; - } - AreaConfig ac = areas.get(args[1]); - if (ac == null) { - sender.sendMessage("§c区域不存在"); - return true; - } - sender.sendMessage("§a" + args[1] - + " 模式排除名单:"); - if (ac.modeExempt.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : ac.modeExempt) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - - // ===== 列表 ===== - if (sub.equals("列表") || sub.equals("list")) { - if (args.length == 2) { - if (args[1].equalsIgnoreCase("global")) { - sender.sendMessage("§a全局白名单:"); - if (globalPlayerWhitelist.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n - : globalPlayerWhitelist) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - String an = args[1]; - Set wl = - areaPlayerWhitelist.get(an); - sender.sendMessage("§a" + an + " 白名单:"); - if (wl == null || wl.isEmpty()) { - sender.sendMessage("§7 (空)"); - } else { - for (String n : wl) { - sender.sendMessage("§7 - " + n); - } - } - return true; - } - if (areas.isEmpty()) { - sender.sendMessage("§7暂无区域"); - } else { - sender.sendMessage("§e§l区域列表:"); - for (Map.Entry en - : areas.entrySet()) { - sender.sendMessage("§a " + en.getKey() - + " §7规则:" - + en.getValue().ruleCount() - + "条"); - } - } - return true; - } - - // ===== 重载 ===== - if (sub.equals("重载") || sub.equals("reload")) { - reload(); - sender.sendMessage("§a已重载 " - + areas.size() + " 个区域"); - return true; - } - - // ===== 物品黑名单 ===== - if (sub.equals("additem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + args[1]); - return true; - } - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (Material.matchMaterial(itemName) - == null) { - sender.sendMessage( - "§c无效物品ID: " + itemName); - return true; - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.add(itemName); - sender.sendMessage("§a已全局加黑: " - + itemName); - } else { - areaItemBlacklist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()) - .add(itemName); - sender.sendMessage("§a已加黑: " - + itemName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect additem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("removeitem")) { - if (args.length == 2) { - String itemName = args[1].toUpperCase(); - if (!globalItemBlacklist - .contains(itemName)) { - sender.sendMessage( - "§c全局黑名单中没有: " - + itemName); - return true; - } - globalItemBlacklist.remove(itemName); - sender.sendMessage("§a已移除: " + itemName); - return true; - } - if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); - String areaName; - String itemName; - if (r1 != null) { - areaName = r1; - itemName = args[2].toUpperCase(); - } else if (r2 != null) { - areaName = r2; - itemName = args[1].toUpperCase(); - } else { - areaName = "global"; - itemName = args[1].toUpperCase(); - } - if (areaName.equalsIgnoreCase("global")) { - globalItemBlacklist.remove(itemName); - } else { - Set list = - areaItemBlacklist.get(areaName); - if (list != null) { - list.remove(itemName); - } - } - sender.sendMessage("§a已移除: " - + itemName); - return true; - } - sender.sendMessage("§c用法: /protect removeitem " - + "<区域/物品> <区域/物品>"); - return true; - } - if (sub.equals("listitem")) { - if (args.length < 2) { - sender.sendMessage( - "§a全局物品黑名单:"); - if (globalItemBlacklist.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item - : globalItemBlacklist) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - Set list = - areaItemBlacklist.get(args[1]); - sender.sendMessage("§a" + args[1] - + " 物品黑名单:"); - if (list == null || list.isEmpty()) { - sender.sendMessage("§7(空)"); - } else { - for (String item : list) { - sender.sendMessage("§f- " + item); - } - } - return true; - } - - // ===== expand 扩建 ===== - if (sub.equals("expand")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ -= amount; - else if (dir.equals("南")) maxZ += amount; - else if (dir.equals("东")) maxX += amount; - else if (dir.equals("西")) minX -= amount; - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f扩建 §e" - + amount + "§f格"); - return true; - } - - // ===== contract 收缩 ===== - if (sub.equals("contraction") - || sub.equals("contract")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - int amount = 5; - if (args.length >= 2) { - try { - amount = Integer.parseInt(args[1]); - } catch (NumberFormatException ignored) {} - } - int minX = Math.min(ac.x1, ac.x2); - int maxX = Math.max(ac.x1, ac.x2); - int minZ = Math.min(ac.z1, ac.z2); - int maxZ = Math.max(ac.z1, ac.z2); - float yaw = p.getLocation().getYaw(); - String dir = yawToDir(yaw); - if (dir.equals("北")) minZ += amount; - else if (dir.equals("南")) maxZ -= amount; - else if (dir.equals("东")) maxX -= amount; - else if (dir.equals("西")) minX += amount; - if (minX > maxX || minZ > maxZ) { - p.sendMessage("§c收缩过度"); - return true; - } - ac.x1 = minX; - ac.x2 = maxX; - ac.z1 = minZ; - ac.z2 = maxZ; - saveAreaConfig(ac); - p.sendMessage("§a§l[防护] §f区域 §e" + ac.name - + " §f向 §e" + dir + " §f收缩 §e" - + amount + "§f格"); - return true; - } - - // ===== 删除 ===== - if (sub.equals("删除") || sub.equals("delete")) { - if (args.length < 2) { - sender.sendMessage( - "§e用法: /protect 删除 <名>"); - return true; - } - File f = new File(rootDir, args[1] + ".txt"); - if (f.exists()) { - f.delete(); - areas.remove(args[1]); - sender.sendMessage("§a已删除: " + args[1]); - } else { - sender.sendMessage("§c不存在: " + args[1]); - } - return true; - } - - // ===== add 加白名单 ===== - if (sub.equals("add")) { - if (args.length == 2) { - String name = args[1]; - if (globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 已在全局白名单中"); - return true; - } - globalPlayerWhitelist.add(name); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已全局加白: " - + playerName); - } else { - Set wl = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap - .newKeySet()); - wl.add(playerName); - saveWhitelists(); - sender.sendMessage("§a已加白: " - + playerName + " → " + areaName); - } - return true; - } - sender.sendMessage("§c用法: /protect add " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== remove 移除白名单 ===== - if (sub.equals("remove")) { - if (args.length == 2) { - String name = args[1]; - if (!globalPlayerWhitelist.contains(name)) { - sender.sendMessage("§e" + name - + " 不在全局白名单中"); - return true; - } - globalPlayerWhitelist.remove(name); - saveWhitelists(); - sender.sendMessage("§a已从全局白名单移除: " - + name); - return true; - } - if (args.length == 3) { - String[] parsed = parseAreaAndTarget( - args[1], args[2]); - if (parsed == null) { - sender.sendMessage("§c无法识别"); - return true; - } - String areaName = parsed[0]; - String playerName = parsed[1]; - if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.remove(playerName); - saveWhitelists(); - sender.sendMessage("§a已移除: " - + playerName); - } else { - Set wl = - areaPlayerWhitelist.get(areaName); - if (wl != null) { - wl.remove(playerName); - } - saveWhitelists(); - sender.sendMessage("§a已从 " + areaName - + " 移除: " + playerName); - } - return true; - } - sender.sendMessage("§c用法: /protect remove " - + "<区域/玩家> <区域/玩家>"); - return true; - } - - // ===== on/off/tempon 边框 ===== - if (sub.equals("on")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, false); - p.sendMessage("§a§l[防护] §f已显示 " - + ac.name + " 边框"); - return true; - } - if (sub.equals("off")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - cancelBorder((Player) sender); - ((Player) sender).sendMessage( - "§a§l[防护] §f已关闭边框显示"); - return true; - } - if (sub.equals("tempon")) { - if (!(sender instanceof Player)) { - sender.sendMessage("§c仅玩家可用"); - return true; - } - Player p = (Player) sender; - AreaConfig ac = getArea( - p.getWorld().getName(), - p.getLocation().getBlockX(), - p.getLocation().getBlockY(), - p.getLocation().getBlockZ()); - if (ac == null) { - p.sendMessage("§c你不在任何防护区域内"); - return true; - } - showBorder(p, ac, true); - p.sendMessage("§a§l[防护] §f临时显示 " - + ac.name + " 边框(15秒)"); - return true; - } - - showHelp(sender); - return true; - } - - - private String[] parseAreaAndTarget(String arg1, String arg2) { - String r1 = resolveAreaName(arg1); - if (r1 != null) { - return new String[]{r1, arg2}; - } - String r2 = resolveAreaName(arg2); - if (r2 != null) { - return new String[]{r2, arg1}; - } - return null; - } - - private boolean playerExists(String playerName) { - // 在线玩家 - for (Player online : Bukkit.getOnlinePlayers()) { - if (online.getName().equalsIgnoreCase(playerName)) return true; - } - // 离线玩家 - @SuppressWarnings("deprecation") - OfflinePlayer op = Bukkit.getOfflinePlayer(playerName); - return op.hasPlayedBefore(); - } - - - private boolean validateArea(CommandSender sender, String areaName) { - if (!areas.containsKey(areaName)) { - sender.sendMessage("§c区域不存在: " + areaName); - return false; - } - return true; - } - - - private String matname(Material mat) { - String name = mat.name(); - String[] parts = name.split("_"); - StringBuilder sb = new StringBuilder(); - for (String part : parts) { - if (sb.length() > 0) sb.append(" "); - sb.append(part.substring(0, 1).toUpperCase()) - .append(part.substring(1).toLowerCase()); - } - return sb.toString(); - } - private String yawToDir(float yaw) { - // MC中: 0=南, 90=西, 180=北, -90/270=东 - yaw = yaw % 360; - if (yaw < 0) yaw += 360; - if (yaw >= 315 || yaw < 45) return "南"; - if (yaw >= 45 && yaw < 135) return "西"; - if (yaw >= 135 && yaw < 225) return "北"; - return "东"; - } - private void saveAreaConfig(AreaConfig ac) { - File f = new File(rootDir, ac.name + ".txt"); - if (!f.exists()) return; - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - if (line.trim().startsWith("起点:")) { - lines.add("起点: " + ac.world - + "," + ac.x1 + "," + ac.z1); - } else if (line.trim().startsWith("终点:")) { - lines.add("终点: " + ac.world - + "," + ac.x2 + "," + ac.z2); - } else { - lines.add(line); - } - } - br.close(); - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - for (String l : lines) pw.println(l); - pw.close(); - } catch (IOException ignored) {} - } - - private void showHelp(CommandSender s) { - s.sendMessage("§e§l==== 区域防护 ===="); - s.sendMessage("§a/protect 工具 §7选地工具"); - s.sendMessage("§a/protect 创建 <名> §7创建区域"); - s.sendMessage("§a/protect 列表 §7区域列表"); - s.sendMessage("§a/protect 重载 §7重载配置"); - s.sendMessage("§a/protect 删除 <名> §7删除区域"); - s.sendMessage("§a/protect add [区域] <玩家> §7玩家加白"); - s.sendMessage("§a/protect remove [区域] <玩家> §7玩家删白"); - s.sendMessage("§a/protect additem [区域] <物品> §7物品加黑"); - s.sendMessage("§a/protect removeitem [区域] <物品> §7物品删黑"); - s.sendMessage("§a/protect on §7显示边框"); - s.sendMessage("§a/protect off §7关闭边框"); - s.sendMessage("§a/protect tempon §7临时边框(15秒)"); - s.sendMessage("§a/protect list §7列出全局白名单"); - s.sendMessage("§a/protect list <区域> §7列出区域白名单"); - s.sendMessage("§a/protect listitem §7列出全局物品黑名单"); - s.sendMessage("§a/protect listitem <区域> §7列出区域物品黑名单"); - - } -// ==================== 白名单管理公共方法 ==================== - - // ==================== 白名单操作 ==================== - - public boolean isPlayerGlobalWhitelisted( - String playerName) { - return globalPlayerWhitelist.contains(playerName); - } - - public boolean addPlayerToGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromGlobalWhitelist( - String playerName) { - boolean ok = globalPlayerWhitelist.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean isPlayerAreaWhitelisted( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - return wl != null && wl.contains(playerName); - } - - public boolean addPlayerToAreaWhitelist( - String areaName, String playerName) { - Set set = areaPlayerWhitelist - .computeIfAbsent(areaName, - k -> ConcurrentHashMap.newKeySet()); - boolean ok = set.add(playerName); - if (ok) saveWhitelists(); - return ok; - } - - public boolean removePlayerFromAreaWhitelist( - String areaName, String playerName) { - Set wl = - areaPlayerWhitelist.get(areaName); - boolean ok = wl != null && wl.remove(playerName); - if (ok) saveWhitelists(); - return ok; - } - - - - public int handleConfiscate(Player p, AreaConfig ac) { - if (isPlayerWhitelisted(p.getName(), ac)) return 0; - Set allItems = new HashSet<>(ac.confiscateItems); - allItems.addAll(globalItemBlacklist); - Set areaList = areaItemBlacklist.get(ac.name); - if (areaList != null) allItems.addAll(areaList); - - if (allItems.isEmpty()) return 0; - - int totalRemoved = 0; - String confiscatedNames = ""; - - for (String itemId : allItems) { - // 过滤掉区域名和无效ID - Material mat = Material.matchMaterial(itemId); - if (mat == null) { - plugin.getLogger().warning( - "[防护] 跳过无效物品: " + itemId); - continue; - } - ItemStack[] all = p.getInventory().getContents(); - int count = 0; - for (int i = 0; i < all.length; i++) { - if (all[i] != null && all[i].getType() == mat) { - count += all[i].getAmount(); - all[i] = null; - } - } - p.getInventory().setContents(all); - - ItemStack off = p.getInventory().getItemInOffHand(); - if (off != null && off.getType() == mat) { - count += off.getAmount(); - p.getInventory().setItemInOffHand(null); - } - - if (count > 0) { - totalRemoved += count; - if (!confiscatedNames.isEmpty()) - confiscatedNames += "§7、§e"; - confiscatedNames += "§e" + matname(mat) - + " x" + count; - } - } - - if (totalRemoved > 0) { - String msg = (ac.confiscateMsg != null - && !ac.confiscateMsg.isEmpty()) - ? ac.confiscateMsg - : "你携带了违禁品,已被没收"; - p.sendMessage("§c§l[区域防护] §f" + msg); - p.sendMessage("§7没收物品: " + confiscatedNames); - - if (ac.enableAnnounce - && ac.announceTemplate != null - && !ac.announceTemplate.isEmpty()) { - String ann = ac.announceTemplate - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)) - .replace("{items}", confiscatedNames); - Bukkit.broadcastMessage("§c§l[区域防护] §f" + ann); - } - for (String cmd : ac.punishCommands) { - String finalCmd = cmd - .replace("{player}", p.getName()) - .replace("{area}", ac.name) - .replace("{count}", String.valueOf(totalRemoved)); - Bukkit.getScheduler().runTask(plugin, - () -> Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), finalCmd)); - } - } - return totalRemoved; - } - public void clearBlacklists() { - globalItemBlacklist.clear(); - areaItemBlacklist.clear(); - plugin.getLogger().info("[防护] 已清空所有黑名单"); - } - - @EventHandler - public void onEntityDamage( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑 ===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - - // 展示框被点击(旋转/放入/取出) - @EventHandler - public void onItemFrameInteract( - PlayerInteractAtEntityEvent e) { - Entity clicked = e.getRightClicked(); - String typeName = clicked.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - Player p = e.getPlayer(); - - // 全局白放行 - Set global = globalWhitelist.get("global"); - if (global != null - && global.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(clicked); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set areaWl = areaWhitelist.get(ac.name); - if (areaWl != null - && areaWl.contains(p.getName().toLowerCase())) { - return; - } - - // 非白名单 → 拦截 - // 记录当前旋转角度 - final Rotation savedRotation; - final Entity frame = clicked; - if (clicked instanceof org.bukkit.entity.ItemFrame) { - savedRotation = - ((org.bukkit.entity.ItemFrame) clicked) - .getRotation(); - } else { - savedRotation = null; - } - - e.setCancelled(true); - - // 下一tick强制恢复旋转 - if (savedRotation != null) { - final Rotation restore = savedRotation; - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (frame.isValid() - && frame instanceof org.bukkit.entity.ItemFrame) { - ((org.bukkit.entity.ItemFrame) frame) - .setRotation(restore); - } - }, 1L); - } - - p.sendMessage("§c§l[区域防护] §f禁止交互展示框"); - - // 驱逐远程敌对生物 - banRangedHostiles(p, ac); - } - - - - // 展示框保护:检查展示框本身+周围方块所属区域 - - // ===== 展示框攻击拦截(左键/投射物破坏)===== - @EventHandler - public void onEntityDamageByEntity( - org.bukkit.event.entity.EntityDamageByEntityEvent e) { - Entity entity = e.getEntity(); - String typeName = entity.getType().name(); - - if (typeName.equals("ITEM_FRAME") - || typeName.equals("GLOW_ITEM_FRAME")) { - // 展示框被攻击 - if (!(e.getDamager() instanceof Player)) return; - Player p = (Player) e.getDamager(); - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(p.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(entity); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(p.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - return; - } - - // ===== PVP 逻辑(原有的,不动)===== - if (!(e.getDamager() instanceof Player)) return; - if (!(e.getEntity() instanceof Player)) return; - Player p2 = (Player) e.getEntity(); - AreaConfig ac2 = getArea( - p2.getWorld().getName(), - p2.getLocation().getBlockX(), - p2.getLocation().getBlockY(), - p2.getLocation().getBlockZ()); - if (ac2 != null && ac2.denyPVP - && !isPlayerWhitelisted(p2.getName(), ac2)) { - e.setCancelled(true); - p2.sendMessage("§c§l[区域防护] §f禁止PVP"); - } - } - private AreaConfig findFrameArea(Entity frame) { - Location loc = frame.getLocation(); - int bx = loc.getBlockX(); - int by = loc.getBlockY(); - int bz = loc.getBlockZ(); - String world = loc.getWorld().getName(); - - AreaConfig ac = getArea(world, bx, by, bz); - if (ac != null && ac.denyItemFrame) return ac; - - int[][] offsets = { - {0, 0, 1}, {0, 0, -1}, - {1, 0, 0}, {-1, 0, 0}, - {0, 1, 0}, {0, -1, 0} - }; - for (int[] off : offsets) { - ac = getArea(world, - bx + off[0], by + off[1], bz + off[2]); - if (ac != null && ac.denyItemFrame) return ac; - } - return null; - } - - // ===== 投射物击中展示框(弓箭/弩)===== - @EventHandler - public void onProjectileHit( - org.bukkit.event.entity.ProjectileHitEvent e) { - if (!(e.getEntity().getShooter() instanceof Player)) return; - Player shooter = (Player) e.getEntity().getShooter(); - Entity hit = e.getHitEntity(); - if (hit == null) return; - String typeName = hit.getType().name(); - if (!typeName.equals("ITEM_FRAME") - && !typeName.equals("GLOW_ITEM_FRAME")) { - return; - } - - // 全局白放行 - Set gwl = globalWhitelist.get("global"); - if (gwl != null - && gwl.contains(shooter.getName().toLowerCase())) { - return; - } - - AreaConfig ac = findFrameArea(hit); - if (ac == null || !ac.denyItemFrame) return; - - // 区域白放行 - Set awl = areaWhitelist.get(ac.name); - if (awl != null - && awl.contains(shooter.getName().toLowerCase())) { - return; - } - - e.setCancelled(true); - shooter.sendMessage("§c§l[区域防护] §f禁止破坏展示框"); - } - - - // ===== 区域外火焰不能蔓延进区域 ===== - @EventHandler - public void onBlockSpread(BlockSpreadEvent e) { - Block block = e.getBlock(); - AreaConfig tgtArea = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (tgtArea != null && tgtArea.denyFireSpread) { - e.setCancelled(true); - } - } - - // ===== 区域内禁止点燃方块 ===== - @EventHandler - public void onIgnite(BlockIgniteEvent e) { - Player p = e.getPlayer(); - if (p == null) return; // 非玩家点燃的(闪电等) - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) { - e.setCancelled(true); - p.sendMessage("§c§l[区域防护] §f此区域禁止点燃物品"); - } - } - - // ===== 区域内方块着火(TNT、岩浆等)也阻止 ===== - @EventHandler - public void onBlockBurn(BlockBurnEvent e) { - Block block = e.getBlock(); - AreaConfig ac = getArea( - block.getWorld().getName(), - block.getX(), block.getY(), block.getZ()); - if (ac != null && ac.denyFireSpread) - e.setCancelled(true); - } - - - private void savePeaceWhitelist(AreaConfig ac) { - File file = new File(rootDir, ac.name + ".txt"); - if (!file.exists()) return; - - try { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader( - new FileReader(file, StandardCharsets.UTF_8)); - String line; - boolean found = false; - - while ((line = reader.readLine()) != null) { - String trimmed = line.trim(); - if (trimmed.contains("和平") - && trimmed.contains("白名单")) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - found = true; - } else { - lines.add(line); - } - } - reader.close(); - - if (!found && !ac.peaceWhitelist.isEmpty()) { - StringBuilder sb = new StringBuilder(); - sb.append("和平模式白名单:"); - boolean first = true; - for (String name : ac.peaceWhitelist) { - if (!first) sb.append(","); - sb.append(name); - first = false; - } - lines.add(sb.toString()); - } - - FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8); - for (String l : lines) { - writer.write(l + "\n"); - } - writer.close(); - } catch (IOException e) { - plugin.getLogger().warning( - "[防护] 保存和平白名单失败: " + e.getMessage()); - } - } - - - @EventHandler - public void onPaintingPlace( - org.bukkit.event.player.PlayerBucketEmptyEvent e) { - // 暂不处理 - } - - - // ==================== 数据类 ==================== - - public static class AreaConfig { - public String name = ""; - public String world = ""; - public int x1, z1, x2, z2; - public int yMin = 0, yMax = 255; - public List confiscateItems = new ArrayList<>(); - public List denyUseItems = new ArrayList<>(); - public List clearEffects = new ArrayList<>(); // 清除单个效果名 - public boolean clearAllBadEffects = false; // 清除所有负面+中性 - public List giveEffects = new ArrayList<>(); // [效果名, 等级, 秒数] - public List punishCommands = new ArrayList<>(); - public boolean denyBlockPlace = false; - public boolean denyBlockBreak = false; - public boolean denyPVP = false; - public boolean denyFallDamage = false; - public boolean denyHunger = false; - public boolean denyAllDamage = false; - public boolean denyMount = false; - public boolean denyDrop = false; - public boolean denyEnderPearl = false; - public boolean denyBow = false; - public boolean denyPotion = false; - public boolean denyExplosion = false; - public boolean enableAnnounce = false; - public String announceTemplate = ""; - public String confiscateMsg = ""; - public String enterMsg = ""; - public String leaveMsg = ""; - public boolean denyRaid = false; - public boolean denyFireSpread = false; - public boolean denyAllEffects = false;; - public boolean peaceMode = false; - public boolean denyItemFrame = false; - public Set peaceWhitelist = new HashSet<>(); - public String enforceGameMode = null; // 强制游戏模式 - public Set modeExempt = new HashSet<>(); // 模式排除名单 - - - - - private static final Map EFFECT_MAP - = new HashMap<>(); - static { - // 负面效果 - EFFECT_MAP.put("缓慢", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("凋零", PotionEffectType.WITHER); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("霉运", PotionEffectType.UNLUCK); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("蓄风", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生", PotionEffectType.INFESTED); - // 中性效果 - EFFECT_MAP.put("不祥之兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("不祥征兆", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("袭击征兆", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("试炼征兆", PotionEffectType.TRIAL_OMEN); - // 正面效果 - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); - EFFECT_MAP.put("瞬间治疗", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("跳跃提升", PotionEffectType.JUMP_BOOST); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性提升", PotionEffectType.RESISTANCE); - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("生命提升", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("村庄英雄", PotionEffectType.HERO_OF_THE_VILLAGE); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); - EFFECT_MAP.put("潮涌能量", PotionEffectType.CONDUIT_POWER); - EFFECT_MAP.put("海豚的恩惠", PotionEffectType.DOLPHINS_GRACE); - - - // 带效果二字 - // 补充带"效果"后缀的别名 - EFFECT_MAP.put("黑暗效果", PotionEffectType.DARKNESS); - EFFECT_MAP.put("不祥之兆效果", PotionEffectType.BAD_OMEN); - EFFECT_MAP.put("袭击之兆效果", PotionEffectType.RAID_OMEN); - EFFECT_MAP.put("试炼之兆效果", PotionEffectType.TRIAL_OMEN); - EFFECT_MAP.put("缓慢效果", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("中毒效果", PotionEffectType.POISON); - EFFECT_MAP.put("凋零效果", PotionEffectType.WITHER); - EFFECT_MAP.put("失明效果", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("饥饿效果", PotionEffectType.HUNGER); - EFFECT_MAP.put("虚弱效果", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("反胃效果", PotionEffectType.NAUSEA); - EFFECT_MAP.put("飘浮效果", PotionEffectType.LEVITATION); - EFFECT_MAP.put("发光效果", PotionEffectType.GLOWING); - EFFECT_MAP.put("霉运效果", PotionEffectType.UNLUCK); - EFFECT_MAP.put("蓄风效果", PotionEffectType.WIND_CHARGED); - EFFECT_MAP.put("盘丝效果", PotionEffectType.WEAVING); - EFFECT_MAP.put("渗浆效果", PotionEffectType.OOZING); - EFFECT_MAP.put("寄生效果", PotionEffectType.INFESTED); - EFFECT_MAP.put("挖掘疲劳效果", PotionEffectType.MINING_FATIGUE); - EFFECT_MAP.put("瞬间伤害效果", PotionEffectType.INSTANT_DAMAGE); - EFFECT_MAP.put("村庄英雄效果", PotionEffectType.HERO_OF_THE_VILLAGE); - } - - - public int ruleCount() { - int c = 0; - if (!confiscateItems.isEmpty()) c++; - if (denyBlockPlace) c++; - if (denyBlockBreak) c++; - if (denyPVP) c++; - if (denyFallDamage) c++; - if (denyHunger) c++; - if (denyAllDamage) c++; - if (denyDrop) c++; - if (denyMount) c++; - if (denyEnderPearl) c++; - if (denyBow) c++; - if (denyRaid) c++; - if (denyPotion) c++; - if (denyExplosion) c++; - if (denyFireSpread) c++; - if (denyAllEffects) c++; - if (!clearEffects.isEmpty()) c++; - if (clearAllBadEffects) c++; - if (!giveEffects.isEmpty()) c++; - if (enableAnnounce) c++; - if (!punishCommands.isEmpty()) c++; - if (!denyUseItems.isEmpty()) c++; - if (peaceMode) c++; - if (denyItemFrame) c++; - if (!peaceWhitelist.isEmpty()) c++; - - return c; - } - } - } \ No newline at end of file diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/BondManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/BondManager.java deleted file mode 100644 index ea78152..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/BondManager.java +++ /dev/null @@ -1,697 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import java.io.File; -import java.sql.*; -import java.util.*; - -public class BondManager { - - private final Main plugin; - private Connection db; - - // ===== 交易类型常量 ===== - public static final String TX_ADMIN_GIVE = "admin_give"; - public static final String TX_ADMIN_DEDUCT = "admin_deduct"; - public static final String TX_TRANSFER_OUT = "transfer_out"; - public static final String TX_TRANSFER_IN = "transfer_in"; - public static final String TX_REDEEM = "redeem"; - public static final String TX_SHOP_BUY = "shop_buy"; - public static final String TX_DAILY_SIGN = "daily_sign"; - public static final String TX_FREEZE = "freeze"; - public static final String TX_UNFREEZE = "unfreeze"; - - - public static final int MAX_WATER_DAYS = 14; - public static final int ADMIN_MAX_WATER_DAYS = 30; - - public BondManager(Main plugin) { - this.plugin = plugin; - initDB(); - } - - // ======================== 数据库初始化 ======================== - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), "bond.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 债券余额(含冻结状态) - st.execute("CREATE TABLE IF NOT EXISTS bonds (" - + "player_name TEXT PRIMARY KEY," - + "amount INTEGER DEFAULT 0," - + "status TEXT DEFAULT 'normal')"); - - // 兼容旧表 - try { - st.execute("ALTER TABLE bonds " - + "ADD COLUMN status " - + "TEXT DEFAULT 'normal'"); - } catch (SQLException ignored) {} - - // 旧流水(保留兼容) - st.execute("CREATE TABLE IF NOT EXISTS bond_log (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "amount INTEGER NOT NULL," - + "reason TEXT DEFAULT ''," - + "time INTEGER DEFAULT 0)"); - - // 新交易流水表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "bond_transaction (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "type TEXT NOT NULL," - + "amount INTEGER NOT NULL," - + "target_player TEXT DEFAULT ''," - + "operator TEXT DEFAULT ''," - + "reason TEXT DEFAULT ''," - + "balance_after INTEGER DEFAULT 0," - + "time INTEGER DEFAULT 0)"); - - try { - st.execute("CREATE INDEX " - + "IF NOT EXISTS idx_tx_pt " - + "ON bond_transaction(" - + "player_name, time)"); - } catch (SQLException ignored) {} - try { - st.execute("ALTER TABLE bond_transaction " - + "ADD COLUMN balance_before " - + "INTEGER DEFAULT 0"); - } catch (SQLException ignored) {} - - - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[Bond] DB init failed: " - + e.getMessage(), e); - } - } - - // ======================== 账户状态 ======================== - - public boolean isFrozen(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT status FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String s = rs.getString("status"); - rs.close(); ps.close(); - return "frozen".equals(s); - } - rs.close(); ps.close(); - } catch (SQLException ignored) {} - return false; - } - - public String getAccountStatus(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT status FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String s = rs.getString("status"); - rs.close(); ps.close(); - return s != null ? s : "normal"; - } - rs.close(); ps.close(); - } catch (SQLException ignored) {} - return "normal"; - } - - public void freezeAccount(String player) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET status='frozen' " - + "WHERE player_name=?"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - } - - public void unfreezeAccount(String player) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET status='normal' " - + "WHERE player_name=?"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - } - - private void ensureAccount(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR IGNORE INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,0,'normal')"); - ps.setString(1, player); - ps.executeUpdate(); ps.close(); - } catch (SQLException ignored) {} - } - - // ======================== 余额查询 ======================== - - public int getBonds(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT amount FROM bonds " - + "WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - int v = 0; - if (rs.next()) v = rs.getInt("amount"); - rs.close(); ps.close(); - return v; - } catch (SQLException e) { return 0; } - } - - /** 该玩家是否在 bond 表中有真实记录 */ - public boolean hasBondAccount(String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM bonds " - + "WHERE player_name=? " - + "AND amount > 0"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - boolean exists = rs.next(); - rs.close(); ps.close(); - return exists; - } catch (SQLException e) { - return false; - } - } - - // ======================== 增加债券 ======================== - - public int addBonds(String player, int amount) { - return addBondsInternal(player, amount, - TX_ADMIN_GIVE, "", "", - "管理员发放"); - } - - public int addBonds(String player, int amount, - String type, - String targetPlayer, - String operator, - String reason) { - if (isFrozen(player)) return -1; - return addBondsInternal(player, amount, - type, targetPlayer, operator, reason); - } - - private int addBondsInternal(String player, - int amount, - String type, - String targetPlayer, - String operator, - String reason) { - int before = getBonds(player); - int after = before + amount; - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status)" - + " VALUES (?,?, 'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, after); - ps.setInt(3, after); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - logTransaction(player, type, amount, - targetPlayer, operator, reason, - before, after); - logLegacy(player, amount); - return after; - } - - // ======================== 扣除债券 ======================== - - - - private boolean deductBondsInternal(String player, - int amount, - String type, - String targetPlayer, - String operator, - String reason) { - int before = getBonds(player); - if (before < amount) return false; - int after = before - amount; - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE bonds SET amount=? " - + "WHERE player_name=?"); - ps.setInt(1, after); - ps.setString(2, player); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - logTransaction(player, type, amount, - targetPlayer, operator, reason, - before, after); - logLegacy(player, -amount); - return true; - } - - // ======================== 流水写入 ======================== - - private void logTransaction(String player, - String type, - int amount, - String targetPlayer, - String operator, - String reason, - int balanceBefore, - int balanceAfter) { - try { - // 合并模式:shop_buy 30秒内同商品合并 - if ("shop_buy".equals(type) - && reason != null - && !reason.isEmpty()) { - long mergeWindow = - System.currentTimeMillis() - 30000L; - PreparedStatement chk = - db.prepareStatement( - "SELECT id, amount " - + "FROM bond_transaction " - + "WHERE player_name=? " - + "AND type=? " - + "AND reason=? " - + "AND time > ? " - + "ORDER BY time DESC " - + "LIMIT 1"); - chk.setString(1, player); - chk.setString(2, type); - chk.setString(3, reason); - chk.setLong(4, mergeWindow); - ResultSet rs = chk.executeQuery(); - if (rs.next()) { - int existId = rs.getInt("id"); - int existAmt = rs.getInt("amount"); - rs.close(); - chk.close(); - PreparedStatement upd = - db.prepareStatement( - "UPDATE bond_transaction " - + "SET amount=?, " - + "balance_after=?, " - + "time=? " - + "WHERE id=?"); - upd.setInt(1, existAmt + amount); - upd.setInt(2, balanceAfter); - upd.setLong(3, - System.currentTimeMillis()); - upd.setInt(4, existId); - upd.executeUpdate(); - upd.close(); - return; - } - rs.close(); - chk.close(); - } - - // 原始插入 - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bond_transaction " - + "(player_name, type, amount," - + " target_player, operator," - + " reason, balance_before," - + " balance_after, time)" - + " VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, player); - ps.setString(2, type); - ps.setInt(3, amount); - ps.setString(4, - targetPlayer != null ? targetPlayer : ""); - ps.setString(5, - operator != null ? operator : ""); - ps.setString(6, - reason != null ? reason : ""); - ps.setInt(7, balanceBefore); - ps.setInt(8, balanceAfter); - ps.setLong(9, System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - // ======================== 扣除债券 ======================== - - /** 完整版:含冻结检查 + 流水记录 */ - public boolean deductBonds(String player, int amount, - String type, - String targetPlayer, - String operator, - String reason) { - if (isFrozen(player)) return false; - return deductBondsInternal(player, amount, - type, targetPlayer, operator, reason); - } - - public boolean checkoutCart(String player, - String[] itemNames, - int[] subtotals) { - int total = 0; - for (int s : subtotals) total += s; - if (isFrozen(player)) return false; - - int before = getBonds(player); - if (before < total) return false; - int after = before - total; - - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,?,'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, after); - ps.setInt(3, after); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - - int running = before; - for (int i = 0; i < itemNames.length; i++) { - int next = running - subtotals[i]; - logTransaction(player, "shop_buy", subtotals[i], - "", "商店系统", - "购物车购买-" + itemNames[i], - running, next); - running = next; - } - - logLegacy(player, -total); - return true; - } - - public boolean setBonds(String player, int amount) { - ensureAccount(player); - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bonds " - + "(player_name,amount,status) " - + "VALUES (?,?, 'normal') " - + "ON CONFLICT(player_name) " - + "DO UPDATE SET amount=?"); - ps.setString(1, player); - ps.setInt(2, amount); - ps.setInt(3, amount); - ps.executeUpdate(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return true; - } - - // ======================== 转账 ======================== - - public TransferResult transfer(String from, - String to, - int amount) { - if (from.equalsIgnoreCase(to)) - return new TransferResult(false, - "不能给自己转账"); - if (isFrozen(from)) - return new TransferResult(false, - "你的账户已被冻结"); - if (isFrozen(to)) - return new TransferResult(false, - to + " 的账户已被冻结"); - if (isSameIP(from, to)) - return new TransferResult(false, - "不能与同IP玩家转账"); - - if (!deductBondsInternal(from, amount, - TX_TRANSFER_OUT, to, from, - "转账给" + to)) - return new TransferResult(false, "债券不足"); - - addBondsInternal(to, amount, - TX_TRANSFER_IN, from, from, - "收到来自" + from + "的转账"); - - return new TransferResult(true, "转账成功"); - } - - // ======================== 同IP检测 ======================== - - public boolean isSameIP(String p1, String p2) { - String ip1 = getPlayerIPAny(p1); - String ip2 = getPlayerIPAny(p2); - if (ip1 == null || ip2 == null) return false; - return ip1.equals(ip2); - } - - /** 获取同IP下的所有其他在线玩家 */ - public List getSameIPPlayers(String player) { - String ip = getPlayerIPAny(player); - if (ip == null) return Collections.emptyList(); - List list = new ArrayList<>(); - for (Player p : - plugin.getServer().getOnlinePlayers()) { - String pIp = plugin.getPlayerIP(p); - if (pIp != null && pIp.equals(ip) - && !p.getName() - .equalsIgnoreCase(player)) { - list.add(p.getName()); - } - } - return list; - } - - private String getPlayerIPAny(String player) { - // 在线:直接取 - Player p = plugin.getServer() - .getPlayerExact(player); - if (p != null) { - String ip = plugin.getPlayerIP(p); - if (ip != null) return ip; - } - // 离线:查主库 - try { - Object v = plugin.getDb() - .getField(player, "last_login_ip"); - if (v != null) return v.toString(); - } catch (Exception ignored) {} - try { - Object v = plugin.getDb() - .getField(player, "register_ip"); - if (v != null) return v.toString(); - } catch (Exception ignored) {} - return null; - } - - // ======================== 流水查询 ======================== - - public List> getTransactions( - String player, int days) { - days = Math.min(days, MAX_WATER_DAYS); - long cutoff = System.currentTimeMillis() - - (long) days * 24 * 60 * 60 * 1000; - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "AND time>=? " - + "ORDER BY time DESC"); - ps.setString(1, player); - ps.setLong(2, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("operator", - rs.getString("operator")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return list; - } -// 放在 getTransactions 方法后面: - - /** - * 获取该玩家全部流水(不限时间,供打印使用) - */ - public List> - getAllTransactions(String player) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "ORDER BY time ASC"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("player_name", - rs.getString("player_name")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("operator", - rs.getString("operator")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } -// 放在 getAllTransactions 方法后面: - - /** - * 获取所有有流水记录的玩家名(去重) - */ - public List getAllPlayerNames() { - List list = new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT DISTINCT player_name " - + "FROM bond_transaction " - + "ORDER BY player_name ASC"); - while (rs.next()) { - list.add(rs.getString("player_name")); - } - rs.close(); st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTransferTransactions( - String player, int days) { - days = Math.min(days, MAX_WATER_DAYS); - long cutoff = System.currentTimeMillis() - - (long) days * 24 * 60 * 60 * 1000; - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM bond_transaction " - + "WHERE player_name=? " - + "AND time>=? " - + "AND (type=? OR type=?) " - + "ORDER BY time DESC"); - ps.setString(1, player); - ps.setLong(2, cutoff); - ps.setString(3, TX_TRANSFER_OUT); - ps.setString(4, TX_TRANSFER_IN); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", rs.getInt("id")); - row.put("type", rs.getString("type")); - row.put("amount", - rs.getInt("amount")); - row.put("target_player", - rs.getString("target_player")); - row.put("reason", - rs.getString("reason")); - row.put("balance_after", - rs.getInt("balance_after")); - row.put("balance_before", rs.getInt("balance_before")); - row.put("time", rs.getLong("time")); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { e.printStackTrace(); } - return list; - } - - - private void logLegacy(String player, int amount) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO bond_log " - + "(player_name,amount,time) " - + "VALUES (?,?,?)"); - ps.setString(1, player); - ps.setInt(2, amount); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); ps.close(); - } catch (SQLException ignored) {} - } - - public Map getLog( - String player, int limit) { - Map info = new LinkedHashMap<>(); - info.put("balance", getBonds(player)); - return info; - } - - // ======================== 转账结果 ======================== - - public static class TransferResult { - public final boolean success; - public final String message; - public TransferResult(boolean success, - String message) { - this.success = success; - this.message = message; - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/BondPrinter.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/BondPrinter.java deleted file mode 100644 index 674ec3c..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/BondPrinter.java +++ /dev/null @@ -1,378 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -public class BondPrinter { - - private final Main plugin; - private final File outputDir; - - private static final SimpleDateFormat FILE_SDF = - new SimpleDateFormat( - "yyyy-MM-dd_HH-mm-ss"); - private static final SimpleDateFormat CELL_SDF = - new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - private static final LinkedHashMap - TYPE_NAMES = new LinkedHashMap<>(); - - static { - TYPE_NAMES.put("admin_give", "管理员发放"); - TYPE_NAMES.put("admin_deduct", "管理员扣除"); - TYPE_NAMES.put("transfer_out", "转出"); - TYPE_NAMES.put("transfer_in", "转入"); - TYPE_NAMES.put("redeem", "CDK兑换"); - TYPE_NAMES.put("shop_buy", "商城购买"); - TYPE_NAMES.put("shop_sell", "商城出售"); // ← 补上 - TYPE_NAMES.put("daily_sign", "签到奖励"); - TYPE_NAMES.put("freeze", "冻结"); - TYPE_NAMES.put("unfreeze", "解冻"); - - } - - public BondPrinter(Main plugin) { - this.plugin = plugin; - this.outputDir = new File( - plugin.getDataFolder(), "printer"); - if (!this.outputDir.exists()) { - this.outputDir.mkdirs(); - } - } - - // ============ 打印单个玩家 ============ - - public void printPlayer(String player, - CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doPrintPlayer( - player, sender)); - } - - private void doPrintPlayer(String player, - CommandSender sender) { - try { - List> txs = - plugin.getBonds() - .getAllTransactions(player); - - String fileName = player + "_" - + FILE_SDF.format(new Date()) - + ".xls"; - File file = new File(outputDir, fileName); - - deleteOld(player); - - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8)); - try { - writeXmlHeader(pw, - escXml(player)); - writeXmlRow(pw, - new String[]{ - "时间", "类型", - "金额", "对象", - "操作者", "理由", - "操作前余额", - "操作后余额"}); - - for (Map tx : txs) { - writeXmlRow(pw, new String[]{ - txTime(tx), - txTypeName(tx), - String.valueOf( - txInt(tx, - "amount")), - txStr(tx, - "target_player"), - txStr(tx, "operator"), - txStr(tx, "reason"), - String.valueOf( - txInt(tx, - "balance_before")), - String.valueOf( - txInt(tx, - "balance_after")) - }); - } - - writeXmlFooter(pw); - pw.flush(); - } finally { - pw.close(); - } - - if (sender != null) { - sender.sendMessage("§a[打印] §f" - + player + " §a流水已导出"); - sender.sendMessage("§7文件: §f" - + fileName); - sender.sendMessage("§7共 §f" - + txs.size() - + " §7条,§724小时后删除"); - } - } catch (Exception e) { - if (sender != null) { - sender.sendMessage("§c导出失败: " - + e.getMessage()); - } - plugin.getLogger().severe( - "[Printer] " + e.getMessage()); - } - } - - // ============ 打印全服 ============ - - public void printAll(CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doPrintAll(sender)); - } - - private void doPrintAll(CommandSender sender) { - try { - List players = - plugin.getBonds() - .getAllPlayerNames(); - if (players.isEmpty()) { - if (sender != null) { - sender.sendMessage("§c没有流水数据"); - } - return; - } - - String fileName = "全服流水_" - + FILE_SDF.format(new Date()) - + ".xls"; - File file = new File(outputDir, fileName); - - // ===== 收集所有流水 ===== - // key = 时间戳+玩家+金额,用于去重 - // value = 一行数据 - List allRows = new ArrayList<>(); - Set seenKeys = new HashSet<>(); - - for (String player : players) { - List> txs = - plugin.getBonds() - .getAllTransactions(player); - for (Map tx : txs) { - int txId = ((Number) tx.get("id")) - .intValue(); - String dedupKey = - String.valueOf(txId); - if (!seenKeys.add(dedupKey)) { - continue; - } - - String timeStr = txTime(tx); - long timeMs = 0; - Object t = tx.get("time"); - if (t != null) { - timeMs = - ((Number) t).longValue(); - } - - allRows.add(new String[]{ - player, - timeStr, - txTypeName(tx), - String.valueOf( - txInt(tx, "amount")), - txStr(tx, "target_player"), - txStr(tx, "operator"), - txStr(tx, "reason"), - String.valueOf( - txInt(tx, - "balance_before")), - String.valueOf( - txInt(tx, - "balance_after")), - // 额外存时间戳用于排序 - String.valueOf(timeMs) - }); - } - } - - // ===== 按时间戳排序 ===== - allRows.sort((a, b) -> { - long ta = Long.parseLong( - a[a.length - 1]); - long tb = Long.parseLong( - b[b.length - 1]); - return Long.compare(ta, tb); - }); - - // ===== 写入文件 ===== - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(file), - StandardCharsets.UTF_8)); - try { - writeXmlHeader(pw, "全服流水"); - writeXmlRow(pw, new String[]{ - "玩家", "时间", "类型", - "金额", "对象", "操作者", - "理由", "操作前余额", - "操作后余额" - }); - - for (String[] row : allRows) { - // 去掉最后的时间戳列 - String[] cells = - new String[row.length - 1]; - System.arraycopy(row, 0, - cells, 0, cells.length); - writeXmlRow(pw, cells); - } - - writeXmlFooter(pw); - pw.flush(); - - if (sender != null) { - sender.sendMessage( - "§a[打印] §f全服流水 §a已导出"); - sender.sendMessage("§7文件: §f" - + fileName); - sender.sendMessage("§7共 §f" - + players.size() - + " 名玩家,§f" - + allRows.size() + " §7条"); - sender.sendMessage( - "§7按时间从早到晚排序"); - sender.sendMessage( - "§724小时后自动删除"); - } - } finally { - pw.close(); - } - } catch (Exception e) { - if (sender != null) { - sender.sendMessage("§c导出失败: " - + e.getMessage()); - } - plugin.getLogger().severe( - "[Printer] " + e.getMessage()); - } - } - - // ============ 清理 ============ - - public void cleanupOldFiles() { - long now = System.currentTimeMillis(); - long maxAge = 24L * 60 * 60 * 1000; - File[] files = outputDir.listFiles( - (d, n) -> n.endsWith(".xls")); - if (files == null) { - return; - } - int count = 0; - for (File f : files) { - if (now - f.lastModified() > maxAge) { - if (f.delete()) { - count++; - } - } - } - if (count > 0) { - plugin.getLogger().info( - "[Printer] 清理 " - + count + " 个过期文件"); - } - } - - private void deleteOld(String player) { - File[] old = outputDir.listFiles( - (d, n) -> n.startsWith(player + "_") - && n.endsWith(".xls")); - if (old != null) { - for (File f : old) { - f.delete(); - } - } - } - - // ============ XML工具 ============ - - private void writeXmlHeader(PrintWriter pw, - String sheetName) { - pw.println(""); - pw.println(""); - pw.println(""); - pw.println(" "); - pw.println(" "); - } - - private void writeXmlRow(PrintWriter pw, - String[] cells) { - pw.println(" "); - for (String cell : cells) { - pw.println(" " - + "" - + escXml(cell != null ? cell : "") - + ""); - } - pw.println(" "); - } - - private void writeXmlFooter(PrintWriter pw) { - pw.println("
"); - pw.println("
"); - pw.println("
"); - } - - private String escXml(String val) { - if (val == null) { - return ""; - } - return val.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """); - } - - private String txTime(Map tx) { - Object t = tx.get("time"); - if (t == null) return ""; - long ms = ((Number) t).longValue(); - return CELL_SDF.format(new Date(ms)); - } - - private String txTypeName( - Map tx) { - String type = (String) tx.get("type"); - return TYPE_NAMES.getOrDefault(type, type); - } - - private int txInt(Map tx, - String key) { - Object v = tx.get(key); - if (v == null) return 0; - return ((Number) v).intValue(); - } - - private String txStr(Map tx, - String key) { - Object v = tx.get(key); - return v != null ? v.toString() : ""; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CDKManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/CDKManager.java deleted file mode 100644 index e07569f..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CDKManager.java +++ /dev/null @@ -1,293 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.*; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.nio.charset.StandardCharsets; - -public class CDKManager { - - private final Main plugin; - private Connection db; - private final Map pendingInputs - = new ConcurrentHashMap<>(); - - public CDKManager(Main plugin) { - this.plugin = plugin; - initDB(); - } - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), "bond.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS cdk (" - + "code TEXT PRIMARY KEY," - + "amount INTEGER DEFAULT 0," - + "type TEXT DEFAULT 'bond'," - + "used INTEGER DEFAULT 0," - + "used_by TEXT DEFAULT ''," - + "created_time INTEGER DEFAULT 0," - + "used_time INTEGER DEFAULT 0)"); - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[CDK] DB init failed: " - + e.getMessage(), e); - } - } - - // ===== CDK兑换(供SDF1调用) ===== - - /** - * 兑换CDK,返回结果字符串 - * @return "success:金额:余额前:余额后" - * 或 "fail:原因" - */ - public String redeem(String code, String player) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT amount,type,used FROM cdk " - + "WHERE code=?"); - ps.setString(1, code); - ResultSet rs = ps.executeQuery(); - if (!rs.next()) { - rs.close(); ps.close(); - return "fail:not_found"; - } - int amount = rs.getInt("amount"); - String type = rs.getString("type"); - int used = rs.getInt("used"); - rs.close(); ps.close(); - - if (used == 1) - return "fail:already_used"; - - if (plugin.getBonds() - .isFrozen(player)) - return "fail:frozen"; - - PreparedStatement up = db.prepareStatement( - "UPDATE cdk SET used=1," - + "used_by=?,used_time=? " - + "WHERE code=?"); - up.setString(1, player); - up.setLong(2, - System.currentTimeMillis()); - up.setString(3, code); - up.executeUpdate(); up.close(); - - if ("bond".equals(type)) { - int bef = plugin.getBonds() - .getBonds(player); - plugin.getBonds().addBonds(player, - amount, - BondManager.TX_REDEEM, - "", "CDK系统", - "CDK兑换: " + code); - int aft = plugin.getBonds() - .getBonds(player); - return "success:" + amount - + ":" + bef + ":" + aft; - } - return "fail:unknown_type"; - } catch (SQLException e) { - return "fail:" + e.getMessage(); - } - } - - // ===== 聊天输入系统 ===== - - public void requestInput(Player p, - String type, - String context) { - CDKInput input = new CDKInput(); - input.type = type; - input.context = context; - input.time = System.currentTimeMillis(); - pendingInputs.put(p.getName(), input); - } - - public boolean isListening(String playerName) { - return pendingInputs - .containsKey(playerName); - } - - public void onChat(Player p, String msg) { - CDKInput input = pendingInputs - .remove(p.getName()); - if (input == null) return; - - if (System.currentTimeMillis() - - input.time > 60000) { - p.sendMessage("§c输入超时"); - return; - } - - if ("cdk".equals(input.type)) { - String result = redeem( - msg.trim(), p.getName()); - if (result != null - && result.startsWith("success:")) { - String[] parts = result.split(":"); - int amt = Integer.parseInt(parts[1]); - int bef = Integer.parseInt(parts[2]); - int aft = Integer.parseInt(parts[3]); - p.sendMessage("§e§l[CDK] §f兑换成功!" - + "获得 §c" + amt + "§f债券"); - p.sendMessage("§7余额: §e" + bef - + " §7-> §a" + aft); - } else { - p.sendMessage("§c§l[CDK] §f" - + (result != null - ? result.replace("fail:", "") - : "无效的兑换码")); - } - return; - } - - if ("econ".equals(input.type)) { - handleEconInput(p, msg.trim(), - input.context); - return; - } - - if ("bond".equals(input.type)) { - handleBondInput(p, msg.trim(), - input.context); - } - } - - private void handleEconInput(Player admin, - String msg, - String target) { - net.milkbowl.vault.economy.Economy econ - = null; - try { - var reg = plugin.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault - .economy - .Economy.class); - if (reg != null) - econ = reg.getProvider(); - } catch (Exception ignored) {} - if (econ == null) { - admin.sendMessage("§c经济系统不可用"); - return; - } - if (!msg.startsWith("+") - && !msg.startsWith("-")) { - admin.sendMessage("§c格式: §a+100" - + " §7或 §c-50"); - return; - } - try { - String numStr = - msg.substring(1).trim(); - double amount = - Double.parseDouble(numStr); - if (amount <= 0) { - admin.sendMessage("§c金额必须>0"); - return; - } - if (msg.startsWith("-")) { - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp == null) { - admin.sendMessage("§c" + target - + " 不在线"); - return; - } - if (!econ.has(tp, amount)) { - admin.sendMessage("§c余额不足"); - return; - } - econ.withdrawPlayer(tp, amount); - admin.sendMessage("§a扣款成功"); - } else { - econ.depositPlayer(target, amount); - admin.sendMessage("§a到账成功"); - } - } catch (NumberFormatException e) { - admin.sendMessage("§c格式错误"); - } - } - - private void handleBondInput(Player admin, - String msg, - String target) { - if (!msg.startsWith("+") - && !msg.startsWith("-")) { - admin.sendMessage("§c格式: §a+100" - + " §7或 §c-50"); - return; - } - try { - String sign = msg.substring(0, 1); - String rest = - msg.substring(1).trim(); - String[] parts = - rest.split("\\s+", 2); - int amount = Integer.parseInt(parts[0]); - String reason = parts.length >= 2 - ? parts[1].trim() - : "管理员手动调整"; - if (amount <= 0) { - admin.sendMessage("§c金额必须>0"); - return; - } - String opName = admin.getName(); - String txType = "-".equals(sign) - ? BondManager.TX_ADMIN_DEDUCT - : BondManager.TX_ADMIN_GIVE; - - if ("-".equals(sign)) { - if (plugin.getBonds() - .isFrozen(target)) { - admin.sendMessage("§c账户冻结"); - return; - } - if (plugin.getBonds().deductBonds( - target, amount, txType, - "", opName, reason)) { - admin.sendMessage("§a扣除成功"); - } else { - admin.sendMessage("§c债券不足"); - } - } else { - if (plugin.getBonds() - .isFrozen(target)) { - admin.sendMessage("§c账户冻结"); - return; - } - plugin.getBonds().addBonds(target, - amount, txType, - "", opName, reason); - admin.sendMessage("§a发放成功"); - } - } catch (NumberFormatException e) { - admin.sendMessage("§c格式错误"); - } - } - - public void cancelInput(String player) { - pendingInputs.remove(player); - } - - static class CDKInput { - String type; - String context; - long time; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ChatFilterManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/ChatFilterManager.java deleted file mode 100644 index 59cb5de..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ChatFilterManager.java +++ /dev/null @@ -1,612 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.*; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ChatFilterManager { - - private final JavaPlugin plugin; - private final List whitelistUrls = - new ArrayList<>(); - private final Set whitelistPlayers = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); - private final Map - punishmentRules = - new LinkedHashMap<>(); - private final Map - punishmentDurations = - new LinkedHashMap<>(); - private final Map - violationCount = - new ConcurrentHashMap<>(); - private final Map - mutedPlayers = - new ConcurrentHashMap<>(); - private final Map messages = - new LinkedHashMap<>(); - private boolean notifyAdmin = true; - private boolean notifyAll = false; - private int muteDuration = 300; - private boolean enabled = true; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean v) { - enabled = v; - } - - - private static final Pattern URL_PATTERN = - Pattern.compile( - "(?i)(?:https?://\\S+)" - + "|(?:(?:[a-zA-Z0-9]" - + "(?:[a-zA-Z0-9\\-]*" - + "[a-zA-Z0-9])?\\.)+" - + "[a-zA-Z]{2,})" - + "(?:/\\S*)?"); - private static final Pattern DOT_VARIANTS = - Pattern.compile( - "[\uff0e\u3002\u2025\u2026\u00b7]"); - private static final Pattern COLOR_CODES = - Pattern.compile( - "\u00a7[0-9a-fk-orA-FK-OR]"); - - private static final Set FILE_EXT; - static { - Set s = new HashSet<>( - Arrays.asList( - "txt", "yml", "yaml", - "json", "xml", "java", - "class", "jar", "png", - "jpg", "jpeg", "gif", - "bmp", "svg", "ico", - "webp", "mp3", "mp4", - "avi", "mkv", "wav", - "flac", "ogg", "zip", - "rar", "7z", "tar", - "gz", "cfg", "conf", - "ini", "log", "db", - "sql", "sh", "bat", - "html", "htm", "css", - "js", "md", "pdf", - "dll", "so", "exe", - "bin", "c", "cpp", - "py", "rb", "go", - "rs", "kt", "lua", - "php", "asp", "jsp")); - FILE_EXT = Collections.unmodifiableSet(s); - } - - public ChatFilterManager(JavaPlugin plugin) { - this.plugin = plugin; - } - - // ========== 配置加载 ========== - - public void loadConfig() { - whitelistUrls.clear(); - whitelistPlayers.clear(); - punishmentRules.clear(); - punishmentDurations.clear(); - messages.clear(); - - File f = new File( - plugin.getDataFolder(), "chat.txt"); - if (!f.exists()) writeDefaultFile(f); - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("白名单") - || t.equals("白名单:")) { - section = "whitelist"; - continue; - } - if (t.equals("处罚规则") - || t.equals("处罚规则:")) { - section = "punishment"; - continue; - } - if (t.equals("白名单玩家") - || t.equals("白名单玩家:")) { - section = "players"; - continue; - } - if (t.contains(":")) { - String[] kv = t.split(":", 2); - String k = kv[0].trim(); - String v = kv[1].trim(); - if (equals("启用过滤") - || equals("enabled")) { - enabled = parseBool(v); - continue; - } - - if (k.equals("通知管理员")) { - notifyAdmin = parseBool(v); - continue; - } - if (k.equals("全服通报")) { - notifyAll = parseBool(v); - continue; - } - if (k.equals("禁言时长")) { - try { - muteDuration = - Integer.parseInt(v); - } catch (Exception ignored) { - } - continue; - } - } - switch (section) { - case "whitelist": - whitelistUrls.add( - t.toLowerCase()); - break; - case "punishment": - String[] parts = t.split(":"); - if (parts.length >= 2) { - try { - int cnt = - Integer.parseInt( - parts[0] - .trim()); - String type = - parts[1].trim() - .toLowerCase(); - int dur = - parts.length >= 3 - ? Integer.parseInt( - parts[2] - .trim()) - : muteDuration; - punishmentRules.put( - cnt, type); - punishmentDurations.put( - cnt, dur); - } catch (Exception ignored) { - } - } - break; - case "players": - whitelistPlayers.add(t); - break; - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[Sdf1_chat] 配置加载失败: " - + e.getMessage()); - } - - // 从消息.txt读取chat_开头的消息 - loadMessages(); - } - - private void loadMessages() { - File msgFile = new File( - plugin.getDataFolder(), - "消息.txt"); - if (!msgFile.exists()) return; - try (BufferedReader mr = - new BufferedReader( - new InputStreamReader( - new FileInputStream( - msgFile), - StandardCharsets.UTF_8))) { - String mline; - while ((mline = mr.readLine()) != null) { - mline = mline.trim(); - if (mline.isEmpty() - || mline.startsWith("#")) - continue; - // 支持 = 和 : 两种分隔符 - int eqIdx = mline.indexOf('='); - int coIdx = mline.indexOf(':'); - int splitIdx = -1; - if (eqIdx >= 0 && coIdx >= 0) { - splitIdx = Math.min(eqIdx, coIdx); - } else if (eqIdx >= 0) { - splitIdx = eqIdx; - } else if (coIdx >= 0) { - splitIdx = coIdx; - } - if (splitIdx < 0) continue; - String k = mline.substring(0, - splitIdx).trim(); - String v = mline.substring( - splitIdx + 1).trim(); - if (k.startsWith("chat_")) { - messages.put(k, v); - } - } - } catch (Exception ignored) { - } - } - - - private void writeDefaultFile(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("启用过滤: " + enabled); - pw.println("通知管理员: true"); - pw.println("全服通报: false"); - pw.println("禁言时长: 300"); - pw.println(); - pw.println("白名单:"); - pw.println("*.minecraft.net"); - pw.println("baidu.com"); - pw.println("github.com"); - pw.println(); - pw.println("处罚规则:"); - pw.println("1:warn:0"); - pw.println("2:warn:0"); - pw.println("3:mute:300"); - pw.println("5:mute:600"); - pw.println("8:kick:0"); - pw.println("10:ban:0"); - pw.println(); - pw.println("白名单玩家:"); - } catch (IOException ignored) { - } - } - - public void saveConfig() { - File f = new File( - plugin.getDataFolder(), "chat.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# Sdf1_chat 聊天过滤配置"); - pw.println("通知管理员: " + notifyAdmin); - pw.println("全服通报: " + notifyAll); - pw.println("禁言时长: " + muteDuration); - pw.println(); - pw.println("白名单:"); - for (String u : whitelistUrls) - pw.println(u); - pw.println(); - pw.println("处罚规则:"); - for (Map.Entry entry - : punishmentRules.entrySet()) { - int dur = punishmentDurations - .getOrDefault(entry.getKey(), - muteDuration); - pw.println(entry.getKey() + ":" - + entry.getValue() - + ":" + dur); - } - pw.println(); - pw.println("白名单玩家:"); - for (String p : whitelistPlayers) - pw.println(p); - } catch (IOException e) { - plugin.getLogger().severe( - "[Sdf1_chat] 保存失败: " - + e.getMessage()); - } - } - - // ========== 消息 ========== - - public String msg(String key, - String... args) { - String template = messages.getOrDefault( - key, key); - for (int i = 0; i < args.length - 1; - i += 2) { - template = template.replace( - "{" + args[i] + "}", - args[i + 1]); - } - return template; - } - - // ========== URL检测 ========== - - private String normalizeMessage(String msg) { - msg = COLOR_CODES.matcher(msg) - .replaceAll(""); - msg = DOT_VARIANTS.matcher(msg) - .replaceAll("."); - return msg; - } - - public List extractUrls( - String message) { - String normalized = - normalizeMessage(message); - List urls = new ArrayList<>(); - Matcher m = URL_PATTERN - .matcher(normalized); - while (m.find()) { - String url = m.group().toLowerCase(); - url = url.replaceAll( - "[\\s.,;!?,。;!?、)》」』】" - + "\\]\\[\\((]+$", - ""); - if (!url.isEmpty()) urls.add(url); - } - return urls; - } - - public boolean isWhitelisted(String url) { - String clean = url - .replaceAll("^https?://", "") - .toLowerCase(); - int colon = clean.indexOf(':'); - int slash = clean.indexOf('/'); - if (colon > 0 && (slash < 0 - || colon < slash)) { - clean = clean.substring(0, colon); - } - slash = clean.indexOf('/'); - if (slash > 0) { - clean = clean.substring(0, slash); - } - for (String entry : whitelistUrls) { - String e = entry.toLowerCase().trim(); - if (e.startsWith("*.")) { - String suffix = e.substring(1); - if (clean.endsWith(suffix) - || clean.equals( - suffix.substring(1))) - return true; - } else { - if (clean.equals(e) - || clean.endsWith( - "." + e)) - return true; - } - } - return false; - } - - public boolean isLikelyDomain(String url) { - int lastDot = url.lastIndexOf('.'); - if (lastDot < 0 - || lastDot >= url.length() - 1) - return false; - String ext = url.substring(lastDot + 1) - .toLowerCase(); - return !FILE_EXT.contains(ext); - } - - // ========== 玩家操作 ========== - - public boolean isMuted(String name) { - Long expiry = mutedPlayers.get(name); - if (expiry == null) return false; - if (System.currentTimeMillis() - >= expiry) { - mutedPlayers.remove(name); - return false; - } - return true; - } - - public String fmtDuration(int seconds) { - if (seconds <= 0) return "0秒"; - int day = seconds / 86400; - int hr = (seconds % 86400) / 3600; - int min = (seconds % 3600) / 60; - int sec = seconds % 60; - StringBuilder sb = new StringBuilder(); - if (day > 0) sb.append(day).append("天"); - if (hr > 0) - sb.append(hr).append("小时"); - if (min > 0) - sb.append(min).append("分"); - if (sec > 0 || sb.length() == 0) - sb.append(sec).append("秒"); - return sb.toString(); - } - - public void applyPunishment(Player player, - int violation) { - String type = "warn"; - int duration = muteDuration; - int maxRule = 0; - for (Map.Entry entry - : punishmentRules.entrySet()) { - if (violation >= entry.getKey() - && entry.getKey() > maxRule) { - maxRule = entry.getKey(); - type = entry.getValue(); - duration = punishmentDurations - .getOrDefault( - entry.getKey(), - muteDuration); - } - } - if (type.equals("mute")) { - if (isMuted(player.getName())) - return; - mutedPlayers.put(player.getName(), - System.currentTimeMillis() - + (long) duration - * 1000L); - } - final String fType = type; - final int fDur = duration; - final int fV = violation; - final String fName = player.getName(); - Bukkit.getScheduler().runTask(plugin, - () -> { - Player p = Bukkit.getPlayer(fName); - if (p == null) return; - switch (fType) { - case "warn": - // 警告不处罚,只提示 - break; - case "mute": - p.sendMessage( - msg("chat_muted")); - break; - case "kick": - p.kickPlayer( - msg("chat_muted")); - break; - case "ban": - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban " + fName - + " §c多次发送违规链接"); - break; - case "banip": - InetSocketAddress addr = - p.getAddress(); - if (addr != null) { - String ip = addr - .getAddress() - .getHostAddress(); - Bukkit.dispatchCommand( - Bukkit.getConsoleSender(), - "ban-ip " + ip - + " §c发送违规链接"); - } - break; - } - }); - } - - public void unmutePlayer(String name) { - mutedPlayers.remove(name); - } - - public void resetPlayer(String name) { - mutedPlayers.remove(name); - violationCount.remove(name); - } - - public boolean isPlayerWhitelisted( - String name) { - return whitelistPlayers.contains(name); - } - - public boolean isNotifyAdmin() { - return notifyAdmin; - } - - public boolean isNotifyAll() { - return notifyAll; - } - - public int getViolationCount(String name) { - return violationCount - .getOrDefault(name, 0); - } - - public void incrementViolation( - String name) { - violationCount.put(name, - getViolationCount(name) + 1); - } - - public List getWhitelistUrls() { - return whitelistUrls; - } - - public Set getWhitelistPlayers() { - return whitelistPlayers; - } - - public Map - getPunishmentRules() { - return punishmentRules; - } - - public Map - getPunishmentDurations() { - return punishmentDurations; - } - - public Map - getMutedPlayers() { - return mutedPlayers; - } - - public void setNotifyAdmin(boolean v) { - notifyAdmin = v; - } - - public void setNotifyAll(boolean v) { - notifyAll = v; - } - - public void setMuteDuration(int v) { - muteDuration = v; - } - - public void addUrl(String url) { - whitelistUrls.add( - url.toLowerCase().trim()); - saveConfig(); - } - - public boolean removeUrl(String url) { - boolean r = whitelistUrls.remove( - url.toLowerCase().trim()); - saveConfig(); - return r; - } - - public void addWhitelistPlayer( - String name) { - whitelistPlayers.add(name); - saveConfig(); - } - - public boolean removeWhitelistPlayer( - String name) { - boolean r = - whitelistPlayers.remove(name); - saveConfig(); - return r; - } - - public void cleanExpired() { - long now = System.currentTimeMillis(); - mutedPlayers.entrySet().removeIf( - e -> e.getValue() <= now); - } - - private boolean parseBool(String v) { - String s = v.toLowerCase().trim(); - return s.equals("true") - || s.equals("on") - || s.equals("yes") - || s.equals("1") - || s.contains("开启") - || s.contains("启用") - || s.contains("是"); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ChatInputManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/ChatInputManager.java deleted file mode 100644 index 49f53fd..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ChatInputManager.java +++ /dev/null @@ -1,842 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; - -public class ChatInputManager { - - public enum InputType { - NONE, - CHANGE_PWD_STEP1, - CHANGE_PWD_STEP2, - SET_EMAIL, - SET_EMAIL_VERIFY, - ADMIN_AUTH, - ADMIN_SET_POINTS, - ADMIN_SET_PWD, - ADMIN_DELETE_CONFIRM, - ADMIN_SET_AFK_TIME, - SMTP_CONFIG, - SMTP_VERIFY, - BACK_CHECKIN, - INVITE_INPUT_CODE, - TICKET_TITLE, - TICKET_DESC, - TICKET_SCORE, - TICKET_REJECT, - TICKET_ASSIGN, - TICKET_INVITE_PROVIDER, - TICKET_REMOVE_PROVIDER, - TICKET_COMPLETE, - TICKET_REPORT_ISSUE, - TICKET_REPLY, - TICKET_ADMIN_TYPE, - TICKET_ADMIN_TITLE, - TICKET_ADMIN_DESC, - TICKET_ADMIN_PROVIDER, - BACK_CHECK_ECONOMY_CONFIRM, - USER_MGMT_SEARCH, - - } - - public static class PlayerState { - public InputType type = InputType.NONE; - public String targetPlayer = ""; - public String ticketType = ""; - public String ticketTitle = ""; - public String ticketDesc = ""; - public int ticketId = 0; - public int smtpStep = 0; - public int scoreValue = 0; - - } - - private final Map states = - new HashMap<>(); - private Main plugin; - - public PlayerState getState(Player p) { - return states.computeIfAbsent( - p.getName(), - k -> new PlayerState()); - } - - public void reset(Player p) { - states.remove(p.getName()); - } - public boolean isInFlow(Player p) { - PlayerState state = states.get(p.getName()); - return state != null - && state.type != InputType.NONE; - } - - public void handleInput(Player p, String msg, - Main mainPlugin) { - if (this.plugin == null) - this.plugin = mainPlugin; - PlayerState state = getState(p); - ConfigManager config = - mainPlugin.getConfig2(); - DatabaseManager db = mainPlugin.getDb(); - - switch (state.type) { - - case CHANGE_PWD_STEP1: { - String name = p.getName(); - String salt = (String) db.getField( - name, "password_salt"); - String hash = - PasswordUtils.hash(msg, salt); - boolean mainOk = - db.checkPassword(name, hash); - if (mainOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "main"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - boolean tempOk = - db.checkPasswordOrTemp( - name, hash); - if (tempOk) { - state.type = - InputType.CHANGE_PWD_STEP2; - state.ticketTitle = "temp"; - p.sendMessage( - "§e请输入新密码:"); - return; - } - p.sendMessage(config.msg( - "password_wrong")); - return; - } - - case CHANGE_PWD_STEP2: { - String name = p.getName(); - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String oldHash = - (String) db.getField(name, - "password_hash"); - String oldSalt = - (String) db.getField(name, - "password_salt"); - String newSalt = - PasswordUtils.generateSalt(); - String newHash = - PasswordUtils.hash( - msg, newSalt); - if (newHash.equals(oldHash)) { - p.sendMessage(config.msg( - "password_same")); - reset(p); - return; - } - mainPlugin.recordPasswordChange( - name, oldHash, oldSalt); - db.setField(name, - "password_hash", newHash); - db.setField(name, - "password_salt", newSalt); - mainPlugin - .getNeedsPasswordChange() - .remove(name); - p.sendMessage(config.msg( - "password_changed")); - reset(p); - return; - } - - case SET_EMAIL: { - if (!msg.contains("@") - || !msg.contains(".")) { - p.sendMessage(config.msg( - "email_format_error")); - reset(p); - return; - } - String oldEmail = (String) - db.getField(p.getName(), - "email"); - String emailAddr = msg.trim(); - // 已绑定邮箱:验证登录密码 - if (oldEmail != null - && !oldEmail.isEmpty()) { - state.targetPlayer = emailAddr; - state.type = - InputType.SET_EMAIL_VERIFY; - state.ticketTitle = - "need_pwd"; - p.sendMessage( - "§e您已绑定邮箱: " - + oldEmail); - p.sendMessage( - "§e请验证登录密码以确认修改:"); - return; - } - // 未绑定邮箱:直接发验证码 - String code = String.valueOf( - (int) (Math.random() - * 900000 - + 100000)); - state.targetPlayer = emailAddr; - state.ticketTitle = code; - state.type = - InputType.SET_EMAIL_VERIFY; - final String to = emailAddr; - final String c = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - boolean sent = mainPlugin - .getEmail() - .sendVerifyCode( - to, c); - Bukkit.getScheduler() - .runTask(mainPlugin, - () -> { - if (sent) { - fp.sendMessage( - "§a验证码已发送到 " - + to); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败"); - reset(fp); - } - }); - }); - return; - } - - - case SET_EMAIL_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§c邮箱设置已取消"); - reset(p); - return; - } - String expected = - state.ticketTitle; - if (msg.trim().equals(expected)) { - db.setField(p.getName(), - "email", - state.targetPlayer); - p.sendMessage( - "§a邮箱已设置: " - + state.targetPlayer); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case ADMIN_AUTH: { - if (msg.equals( - config.adminPassword)) { - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openAdmin(p)); - } else { - p.sendMessage( - "§c管理密码错误"); - } - reset(p); - return; - } - - case ADMIN_SET_POINTS: { - try { - int pts = Integer.parseInt( - msg.trim()); - db.setField( - state.targetPlayer, - "points", pts); - p.sendMessage("§a已将 " - + state.targetPlayer - + " 积分设为 " + pts); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - String tgt = - state.targetPlayer; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getGui() - .openUserDetail( - p, tgt)); - reset(p); - return; - } - - case ADMIN_SET_PWD: { - if (!PasswordUtils.validate(msg)) { - p.sendMessage(config.msg( - "password_format_error")); - reset(p); - return; - } - String tgt = state.targetPlayer; - String salt = - PasswordUtils - .generateSalt(); - String hash = - PasswordUtils.hash( - msg, salt); - db.setField(tgt, - "temp_password", hash); - db.setField(tgt, "temp_pw_expire", - System.currentTimeMillis() - + 300000L); - db.setField(tgt, - "temp_pw_used", 0); - p.sendMessage("§a已为 " + tgt - + " 设置临时密码"); - Player tp = - Bukkit.getPlayer(tgt); - if (tp != null && tp.isOnline()) - tp.kickPlayer( - "§c管理员已设置临时密码," - + "请使用临时密码登录"); - // 邮件通知 - String emailAddr = (String) - db.getField(tgt, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String time = - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()); - String body = "玩家 " + tgt - + ":\n您的密码已于 " - + time - + " 被管理员重置为临时密码。\n" - + "如果是本人操作请忽略。\n" - + "如非本人操作请立即联系管理员。"; - final String to = emailAddr; - final String fBody = body; - final String fName = tgt; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> - mainPlugin.getEmail() - .sendTempPassword( - to, - fName, - fBody)); - } - reset(p); - return; - } - - case ADMIN_DELETE_CONFIRM: { - if (msg.equals( - state.targetPlayer)) { - db.deleteUser( - state.targetPlayer); - p.sendMessage("§a已删除 " - + state.targetPlayer); - Player tgt = - Bukkit.getPlayer( - state.targetPlayer); - if (tgt != null - && tgt.isOnline()) - tgt.kickPlayer( - "§c账号已被删除"); - } else { - p.sendMessage( - "§c输入不匹配,已取消"); - } - reset(p); - return; - } - - case ADMIN_SET_AFK_TIME: { - try { - int minutes = - Integer.parseInt( - msg.trim()); - config.afkTimeout = - minutes * 60; - config.saveSettings(); - p.sendMessage( - "§a挂机时长已设为 " - + minutes - + " 分钟"); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效数字"); - } - reset(p); - return; - } - - case SMTP_CONFIG: { - String m = msg.trim(); - if ("000".equals(m) - || "取消取消取消" - .equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if ("取消".equals(m)) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - int step = state.smtpStep; - if (step <= 1) { - if (!"0".equals(m)) - config.setSmtp( - "smtp地址", m); - state.smtpStep = 2; - p.sendMessage("§e当前端口: " - + config.getSmtp( - "smtp端口")); - p.sendMessage( - "§e输入端口(0跳过):"); - } else if (step == 2) { - if (!"0".equals(m)) - config.setSmtp( - "smtp端口", m); - state.smtpStep = 3; - p.sendMessage( - "§e输入发件邮箱:"); - } else if (step == 3) { - if (!"0".equals(m)) - config.setSmtp( - "smtp账号", m); - state.smtpStep = 4; - p.sendMessage( - "§e输入SMTP密码(0跳过):"); - } else if (step == 4) { - if (!"0".equals(m)) - config.setSmtp( - "smtp密码", m); - state.smtpStep = 5; - p.sendMessage( - "§e输入接收邮箱(留空发到发件邮箱):"); - } else if (step == 5) { - String recv = m; - if (recv.isEmpty() - || "0".equals(recv)) - recv = config.getSmtp( - "smtp账号"); - config.setSmtp( - "验证码接收邮箱", recv); - if (!config - .canSendSmtpVerify()) { - p.sendMessage( - "§c发送间隔不足1分钟"); - reset(p); - return; - } - String code = config - .generateSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String acct = config.getSmtp( - "smtp账号"); - if (acct.isEmpty() - || addr.isEmpty()) { - p.sendMessage( - "§c地址或账号为空"); - reset(p); - return; - } - config.recordSmtpSendTime(); - final String fRecv = recv; - final String fCode = code; - final Player fp = p; - Bukkit.getScheduler() - .runTaskAsynchronously( - mainPlugin, () -> { - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> fp.sendMessage( - "§e正在发送验证码...")); - boolean sent = - mainPlugin.getEmail() - .sendVerifyCode( - fRecv, - fCode); - final boolean fSent = sent; - Bukkit.getScheduler() - .runTask( - mainPlugin, - () -> { - if (fSent) { - state.type = - InputType - .SMTP_VERIFY; - state.smtpStep = 0; - fp.sendMessage( - "§a验证码已发送到 " - + fRecv); - fp.sendMessage( - "§e输入6位验证码(0取消):"); - } else { - fp.sendMessage( - "§c发送失败,查看控制台"); - reset(fp); - } - }); - }); - } - return; - } - - case SMTP_VERIFY: { - if ("0".equals(msg.trim()) - || "取消".equals( - msg.trim())) { - p.sendMessage( - "§cSMTP配置已取消"); - reset(p); - return; - } - if (config.checkSmtpVerifyCode( - msg.trim())) { - config.saveSmtp(); - config.clearSmtpVerifyCode(); - String addr = config.getSmtp( - "smtp地址"); - String port = config.getSmtp( - "smtp端口"); - String acct = config.getSmtp( - "smtp账号"); - String recv = config.getSmtp( - "验证码接收邮箱"); - boolean ssl = config.smtpSsl; - p.sendMessage("§a§l=== SMTP配置完成 ==="); - p.sendMessage("§7地址: §f" + addr); - p.sendMessage("§7端口: §f" + port); - p.sendMessage("§7账号: §f" + acct); - p.sendMessage("§7接收: §f" + recv); - p.sendMessage("§7加密: §f" + ssl); - p.sendMessage("§a§l===================="); - reset(p); - } else { - p.sendMessage( - "§c验证码错误(0取消):"); - } - return; - } - - case BACK_CHECKIN: { - String result = - mainPlugin.getCheckIn() - .backCheckIn(p, msg); - if (result == null) { - // 等待选择(NEED_CONFIRM) - return; - } - if (result.startsWith( - "NEED_CONFIRM:")) { - String[] parts = - result.split(":"); - // 存储到Main的Map - mainPlugin.pendingBackCheck.put( - p.getName(), - parts[1] + ":" + parts[2]); - return; - } - if (result.startsWith("§c")) { - p.sendMessage(result); - } else { - p.sendMessage(result); - } - reset(p); - return; - } - - - case INVITE_INPUT_CODE: { - String code = msg.trim(); - String myName = p.getName(); - String myCode = - (String) db.getField( - myName, - "invite_code"); - if (code.isEmpty()) { - p.sendMessage( - "§c邀请码不能为空"); - reset(p); - return; - } - if (code.equals(myCode)) { - p.sendMessage( - "§c不能用自己邀请码"); - reset(p); - return; - } - boolean found = false; - for (Map u : - db.getAllUsers()) { - String uc = (String) - u.getOrDefault( - "invite_code", ""); - if (code.equals(uc)) { - String inv = (String) - u.get("player_name"); - db.setField(myName, - "invited_by", inv); - db.addPoints(inv, 50); - p.sendMessage( - "§a邀请成功!邀请人: " - + inv); - Player ip = - Bukkit.getPlayer(inv); - if (ip != null - && ip.isOnline()) - ip.sendMessage("§a" - + myName - + " 使用了您的邀请码"); - found = true; - break; - } - } - if (!found) - p.sendMessage( - "§c邀请码不存在"); - reset(p); - return; - } - - case TICKET_TITLE: { - state.ticketTitle = msg; - state.type = - InputType.TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return; - } - - case TICKET_DESC: { - String tt = state.ticketType; - String tl = state.ticketTitle; - reset(p); - mainPlugin.getTicket() - .submitTicket( - p, tt, tl, msg); - return; - } - - case TICKET_SCORE: { - int tid = state.ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - reset(p); - mainPlugin.getTicket() - .confirmAndScore( - p, tid, sc); - } catch ( - NumberFormatException e) { - reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return; - } - - case TICKET_REJECT: { - int tid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .rejectTicket( - p, tid, msg); - return; - } - - case TICKET_ASSIGN: { - int tid = state.ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - reset(p); - mainPlugin.getTicket() - .assignToProvider( - p, tid, prov, - price); - final int fTid = tid; - Bukkit.getScheduler().runTask( - mainPlugin, () -> - mainPlugin - .getTicket() - .openDetail( - p, fTid)); - return; - } - - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return; - } - db.addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return; - } - - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - reset(p); - if (!db.isServiceProvider(tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return; - } - db.removeServiceProvider(tgt); - p.sendMessage("§a已移除 " + tgt); - return; - } - - case TICKET_COMPLETE: { - try { - int tid = Integer.parseInt( - msg.trim()); - mainPlugin.getTicket() - .completeAsProvider( - p, tid); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c请输入有效工单号"); - } - reset(p); - return; - } - - case TICKET_REPORT_ISSUE: { - String report = msg.trim(); - reset(p); - p.sendMessage("§e异常已上报"); - String tag = config.adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§c§l[工单异常] §f" - + p.getName() - + ": " + report); - } - } - return; - } - - case TICKET_REPLY: { - int replyTid = state.ticketId; - reset(p); - mainPlugin.getTicket() - .replyTicket( - p, replyTid, msg); - return; - } - - case TICKET_ADMIN_TYPE: { - state.ticketType = msg.trim(); - state.type = - InputType - .TICKET_ADMIN_TITLE; - p.sendMessage( - "§e请输入工单标题:"); - return; - } - - case TICKET_ADMIN_TITLE: { - state.ticketTitle = msg; - state.type = - InputType - .TICKET_ADMIN_DESC; - p.sendMessage( - "§e请输入工单描述:"); - return; - } - - case TICKET_ADMIN_DESC: { - state.targetPlayer = msg; - state.type = - InputType - .TICKET_ADMIN_PROVIDER; - p.sendMessage( - "§e输入服务商名和价格:"); - p.sendMessage( - "§7例: playerA 20"); - return; - } - - case TICKET_ADMIN_PROVIDER: { - String[] parts2 = msg.trim() - .split("\\s+"); - String prov2 = parts2[0]; - int price2 = 0; - if (parts2.length >= 2) { - try { - price2 = Integer.parseInt( - parts2[1]); - } catch ( - NumberFormatException e) { - } - } - String t2 = state.ticketType; - String ti2 = state.ticketTitle; - String d2 = state.targetPlayer; - reset(p); - mainPlugin.getTicket() - .adminCreateAndAssign( - p, t2, ti2, - d2, prov2, price2); - return; - } - - default: - reset(p); - return; - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CheckInManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/CheckInManager.java deleted file mode 100644 index b594b0f..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CheckInManager.java +++ /dev/null @@ -1,302 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -public class CheckInManager { - - private final Main plugin; - - public CheckInManager(Main plugin) { - this.plugin = plugin; - } - - // ===== 今日是否已签到 ===== - public boolean isCheckedInToday( - String name) { - String last = (String) plugin.getDb() - .getField(name, - "last_checkin_date"); - String today = new SimpleDateFormat( - "yyyy-MM-dd") - .format(new Date()); - return today.equals(last); - } - - // ===== 签到 ===== - // ===== 签到 ===== - public String checkIn(Player p) { - String name = p.getName(); - if (isCheckedInToday(name)) { - return plugin.getConfig2() - .msg("checkin_already"); - } - - Object streakObj = plugin.getDb() - .getField(name, "checkin_streak"); - int streak = streakObj != null - ? ((Number) streakObj).intValue() - : 0; - - String lastDate = (String) plugin.getDb() - .getField(name, "last_checkin_date"); - String today = new SimpleDateFormat("yyyy-MM-dd") - .format(new Date()); - - if (lastDate != null && !lastDate.isEmpty()) { - try { - java.util.Date lastD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(lastDate); - java.util.Date todayD = - new SimpleDateFormat("yyyy-MM-dd") - .parse(today); - long diff = todayD.getTime() - - lastD.getTime(); - if (diff > 86400000L * 2) { - streak = 0; - } else if (diff > 86400000L) { - streak++; - } - } catch (Exception ignored) { - } - } - - if (streak == 0) streak = 1; - else streak++; - - double multi = 1.0; - if (streak >= 21) multi = 1.5; - else if (streak >= 14) multi = 1.3; - else if (streak >= 7) multi = 1.1; - - int basePoints = - plugin.getConfig2().checkinPoints; - int points = (int) (basePoints * multi); - - plugin.getDb().addPoints(name, points); - plugin.getDb().setField(name, - "checkin_streak", streak); - plugin.getDb().setField(name, - "last_checkin_date", today); - - Object totalObj = plugin.getDb() - .getField(name, "total_checkin_days"); - int total = totalObj != null - ? ((Number) totalObj).intValue() - : 0; - plugin.getDb().setField(name, - "total_checkin_days", total + 1); - - // ★ 债券盲盒奖励(6参数版,写入流水)★ - int bondMin = - plugin.getConfig2().checkinBondMin; - int bondMax = - plugin.getConfig2().checkinBondMax; - if (bondMin <= 0) bondMin = 1; - if (bondMax < bondMin) bondMax = bondMin; - int bondReward = bondMin - + new java.util.Random() - .nextInt(bondMax - bondMin + 1); - plugin.getBonds().addBonds(name, bondReward, - "daily_sign", "", "签到系统", - "每日签到连续" + streak + "天"); - - return plugin.getConfig2() - .msg("checkin_success") - .replace("{points}", - String.valueOf(points)) - .replace("{streak}", - String.valueOf(streak)) - .replace("{multi}", - String.valueOf(multi)) - + " §e+" + bondReward + "债券"; - } - - // ===== 获取连续天数 ===== - public int getStreak(String name) { - Object val = plugin.getDb() - .getField(name, - "checkin_streak"); - if (val instanceof Number) { - return ((Number) val) - .intValue(); - } - return 0; - } - // 标准化日期 - private String normalizeDate(String input) { - String s = input.trim().replace("/", "-"); - if (s.matches("\\d{8}")) { - return s.substring(0, 4) + "-" - + s.substring(4, 6) + "-" - + s.substring(6, 8); - } - return s; - } - // ===== 补签入口 ===== - public String backCheckIn( - Player p, String dateStr) { - String name = p.getName(); - String normalized = - normalizeDate(dateStr); - - // 严格日期验证 - java.text.SimpleDateFormat sdf = - new java.text.SimpleDateFormat( - "yyyy-MM-dd"); - sdf.setLenient(false); - java.util.Date target; - try { - target = sdf.parse(normalized); - } catch (Exception ex) { - return "§c无效日期: " + dateStr; - } - String reformat = sdf.format(target); - if (!reformat.equals(normalized)) { - return "§c无效日期: " + dateStr; - } - - String today = sdf.format(new Date()); - if (normalized.compareTo(today) >= 0) { - return "§c今天不需要补签"; - } - - String threeDaysAgo = getPastDate(3); - if (normalized.compareTo( - threeDaysAgo) < 0) { - return "§c只能补签3天内" - + "(最多到 " + threeDaysAgo + ")"; - } - - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - boolean alreadyBacked = false; - if (!existing.isEmpty()) { - for (String d : existing.split(",")) { - if (normalizeDate(d.trim()) - .equals(normalized)) { - alreadyBacked = true; - break; - } - } - } - if (alreadyBacked) { - return "§c该日期已补签: " + normalized; - } - - Object normalCheck = - plugin.getDb().getField( - name, "last_checkin_date"); - String lastCheckin = normalCheck != null - ? normalCheck.toString() : ""; - if (lastCheckin.equals(normalized)) { - return "§c该日期已签到: " + normalized; - } - - // ★ 补签消耗:3债券 + 10积分 - // 积分不够时:用 9债券(3倍)代替 ★ - int bondCost = 3; - int pointCost = 10; - int bondAltCost = 9; // 3倍债券替代 - - int myBonds = plugin.getBonds() - .getBonds(name); - Object ptsObj = plugin.getDb() - .getField(name, "points"); - int myPoints = ptsObj != null - ? ((Number) ptsObj).intValue() - : 0; - - - // 方案A:3债券 + 10积分 - boolean canA = myBonds >= bondCost - && myPoints >= pointCost; - // 方案B:9债券(积分不够时) - boolean canB = myBonds >= bondAltCost - && myPoints < pointCost; - - if (!canA && !canB) { - if (myPoints < pointCost - && myBonds < bondAltCost) { - return "§c补签费用不足" - + "(需3债券+10积分," - + "或9债券代替)" - + " 当前: " - + myBonds + "债券 " - + myPoints + "积分"; - } - if (myBonds < bondCost) { - return "§c债券不足(需" - + bondCost + "枚)"; - } - return "§c积分不足(需" - + pointCost + "分)"; - } - - // 执行扣款 - if (canA) { - // 方案A:3债券 + 10积分 - plugin.getBonds().deductBonds( - name, bondCost, - "admin_deduct", "", - "补签系统", "补签扣款"); - plugin.getDb().deductPoints( - name, pointCost); - } else { - // 方案B:9债券代替 - plugin.getBonds().deductBonds( - name, bondAltCost, - "admin_deduct", "", - "补签系统", "补签扣款(债券替代)"); - } - - doBackCheckIn(p, normalized); - - String costMsg = canA - ? bondCost + "债券+" + pointCost + "积分" - : bondAltCost + "债券(替代)"; - return "§a补签成功!日期: " - + normalized - + " §7消耗: " + costMsg; - } - - - - // ===== 执行补签 ===== - // ===== 执行补签 ===== - public void doBackCheckIn( - Player p, String dateStr) { - String name = p.getName(); - Object val = plugin.getDb() - .getField(name, "backcheck_dates"); - String existing = val != null - ? val.toString() : ""; - String updated = existing.isEmpty() - ? dateStr - : existing + "," + dateStr; - plugin.getDb().setField(name, - "backcheck_dates", updated); - int streak = getStreak(name); - plugin.getDb().setField(name, - "checkin_streak", streak + 1); - // ★ 补签不发放奖励,只记录 - } - - // ===== 获取N天前日期 ===== - private String getPastDate( - int daysAgo) { - Calendar cal = - Calendar.getInstance(); - cal.add(Calendar.DAY_OF_MONTH, - -daysAgo); - return new SimpleDateFormat( - "yyyy-MM-dd") - .format(cal.getTime()); - } -} \ No newline at end of file diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CommissionManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/CommissionManager.java deleted file mode 100644 index cb24687..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CommissionManager.java +++ /dev/null @@ -1,227 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.logging.Logger; - -public class CommissionManager { - - private final Main main; - private final Logger logger; - private final List rules = - new ArrayList<>(); - - public CommissionManager(Main main) { - this.main = main; - this.logger = main.getLogger(); - loadRules(); - } - - public void loadRules() { - rules.clear(); - File f = new File( - main.getDataFolder(), - "提成规则.txt"); - if (!f.exists()) { - writeDefault(f); - } - try (BufferedReader br = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets - .UTF_8))) { - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() - || line.startsWith("#")) - continue; - // 格式: 邀请人数:提成百分比 - // 如 "5:15" = 邀请5人提成15% - String[] parts = - line.split(":"); - if (parts.length >= 2) { - try { - int count = Integer.parseInt( - parts[0].trim()); - double rate = Double - .parseDouble( - parts[1] - .trim()) / 100.0; - rules.add(new double[]{ - count, rate}); - } catch (Exception ignored) { - } - } - } - // 按人数排序(从大到小) - rules.sort((a, b) -> - Double.compare(b[0], a[0])); - logger.info( - "[Commission] 加载 " - + rules.size() + " 条提成规则"); - } catch (Exception e) { - logger.warning( - "[Commission] 加载失败: " - + e.getMessage()); - } - } - - private void writeDefault(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# 邀请提成规则"); - pw.println("# 格式: 邀请人数:提成百分比"); - pw.println("# 邀请10人提成20%"); - pw.println("10:20"); - pw.println("# 邀请5人提成15%"); - pw.println("5:15"); - pw.println("# 邀请1人提成10%"); - pw.println("1:10"); - } catch (IOException ignored) { - } - } - - /** - * 获取邀请人的提成比例 - * 根据已邀请人数匹配最高规则 - */ - public double getCommissionRate( - String inviter) { - int inviteCount = getInviteCount( - inviter); - for (double[] rule : rules) { - if (inviteCount >= (int) rule[0]) { - return rule[1]; - } - } - return 0; - } - - /** - * 获取邀请人数 - */ - public int getInviteCount( - String inviter) { - try { - Object val = - main.getDb().getField( - inviter, - "monthly_invite_count"); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - Object total = - main.getDb().getField( - inviter, - "total_invite_count"); - if (total instanceof Number) { - return ((Number) total).intValue(); - } - } catch (Exception ignored) { - } - return 0; - } - - /** - * 发放提成 - * 被邀请人获得奖励时,邀请人获得提成 - */ - public void payCommission( - String invitee, - double originalAmount) { - // 查找邀请人 - String inviter = getInviter(invitee); - if (inviter == null - || inviter.isEmpty()) return; - - double rate = getCommissionRate( - inviter); - if (rate <= 0) return; - - double commission = - originalAmount * rate; - if (commission <= 0) return; - - // 通过 Vault 发放 - var rsp = main.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault - .economy.Economy.class); - if (rsp == null) return; - net.milkbowl.vault.economy.Economy econ = - rsp.getProvider(); - if (econ == null) return; - - econ.depositPlayer(inviter, commission); - - // 通知邀请人 - Player inviterP = - main.getServer() - .getPlayer(inviter); - if (inviterP != null - && inviterP.isOnline()) { - inviterP.sendMessage( - "§a§l[邀请提成] §f" - + invitee + " 完成任务," - + "你获得 §e" - + String.format("%.2f", - commission) - + " §f游戏币提成 (" - + (int)(rate * 100) + "%)"); - } - - logger.info( - "[Commission] 提成: " - + inviter + " ← " - + invitee + " | " - + String.format("%.2f", - originalAmount) - + " × " - + (int)(rate * 100) + "% = " - + String.format("%.2f", - commission)); - } - - /** - * 获取邀请人 - */ - private String getInviter(String player) { - try { - Object val = - main.getDb().getField( - player, - "invited_by"); - if (val != null - && !val.toString() - .isEmpty()) { - return val.toString(); - } - } catch (Exception ignored) { - } - return null; - } - - /** - * 判断金额是否包含经济内容 - */ - public boolean hasEconomyContent( - String text) { - String t = text.toLowerCase(); - return t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额"); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ConfigManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/ConfigManager.java deleted file mode 100644 index a15ada0..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ConfigManager.java +++ /dev/null @@ -1,372 +0,0 @@ -package Sdf1_login; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; - -public class ConfigManager { - - private final File dataFolder; - private final Map messages - = new LinkedHashMap<>(); - private final Map smtpSettings - = new LinkedHashMap<>(); - - // ===== 公共字段 ===== - public String adminTag = "admin"; - public String adminPassword = "qweasd"; - public int maxAccountsPerIP = 3; - public String approvalMode = "manual"; - public int autoApproveDelayMinutes = 30; - public int loginTimeout = 60; - public boolean afkEnabled = false; - public int afkTimeout = 300; - public boolean smtpSsl = true; - public String smtpVerifyCode = ""; - public long smtpVerifyExpire = 0; - public long smtpLastSendTime = 0; - public String smtpConfiguringPlayer = ""; - public String broadcastServerUrl = ""; - public int httpPort = 8080; - public String garbageTag = "admin"; - - public double checkinRewardFixed = 0; - public double checkinRewardMin = 0; - public double checkinRewardMax = 0; - public boolean checkinGivePoints = true; - public int checkinPoints = 10; - public String checkinRewardType = "none"; - public int backCheckPointMultiplier = 5; - public int checkinBondMin = 1; - public int checkinBondMax = 3; - - public double providerPointsPerScore = 2.0; - public int baseEconomyReward = 100; - public double providerEconomyPerScore = 50.0; - public int requesterPointsReward = 10; - - public boolean garbageEnabled = true; - public int garbageInterval = 300; - public int garbageMaxRounds = 1; - - // ★ 构造函数接收 File,和 Main.java 调用一致 ★ - public ConfigManager(File dataFolder) { - this.dataFolder = dataFolder; - } - public String rewardChannel = "债券"; // bonds = 债券,economy = Vault经济 - - - // ==================== 消息 ==================== - - public void loadMessages() { - messages.clear(); - File file = new File(dataFolder, "消息.txt"); - if (!file.exists()) createDefaultMessages(file); - loadMap(file, messages); - } - - public String msg(String key) { - String val = messages.getOrDefault(key, key); - val = val.replace("", "&l"); - val = val.replace("", "&r"); - val = val.replace("
", "\n"); - val = val.replace("\\n", "\n"); - val = val.replaceAll( - "&([0-9a-fk-orA-FK-OR])", "§$1"); - return val; - } - - private void createDefaultMessages(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 消息配置 ====="); - L.add("login_timeout=§c登录超时"); - L.add("not_registered=§e您尚未注册"); - L.add("not_logged_in=§e您尚未登录"); - L.add("already_registered=§c您已注册过"); - L.add("already_logged_in=§c您已登录"); - L.add("reg_success=§a注册成功!"); - L.add("login_success=§a登录成功!"); - L.add("login_failed=§c密码错误"); - L.add("password_wrong=§c密码错误"); - L.add("password_changed=§a密码修改成功!"); - L.add("password_format_error=§c密码需6位以上含大小写和数字"); - L.add("password_same=§c新密码不能与旧密码相同"); - L.add("checkin_already=§c您今日已签到"); - L.add("checkin_success=§a签到成功!获得 {points} 积分"); - L.add("chat_muted=§c你已被禁言"); - L.add("未知参数=§c未知参数或权限不足"); - writeLines(f, L); - } - - // ==================== SMTP ==================== - - public void loadSmtp() { - smtpSettings.clear(); - File file = new File(dataFolder, "SMTP设置.txt"); - if (!file.exists()) createDefaultSmtp(file); - loadMap(file, smtpSettings); - } - - public String getSmtp(String key) { - return smtpSettings.getOrDefault(key, ""); - } - - public void setSmtp(String key, String value) { - smtpSettings.put(key, value); - } - - public void saveSmtp() { - File file = new File(dataFolder, "SMTP设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== SMTP 配置 ====="); - for (Map.Entry e - : smtpSettings.entrySet()) { - L.add(e.getKey() + "=" + e.getValue()); - } - writeLines(file, L); - } - - private void createDefaultSmtp(File f) { - List L = new ArrayList<>(); - L.add("# ===== SMTP 配置 ====="); - L.add("smtp地址=smtp.example.com"); - L.add("smtp端口=465"); - L.add("smtp账号="); - L.add("smtp密码="); - L.add("发件人名称=Sdf1_login"); - L.add("smtp加密=true"); - writeLines(f, L); - } - - // ==================== 插件设置 ==================== - - public void loadSettings() { - File file = new File(dataFolder, "插件设置.txt"); - if (!file.exists()) createDefaultSettings(file); - Map m = new LinkedHashMap<>(); - loadMap(file, m); - - adminTag = m.getOrDefault("管理标签", adminTag); - adminPassword = m.getOrDefault("管理密码", adminPassword); - maxAccountsPerIP = parseInt(m.getOrDefault("每IP最大账号数", "3"), 3); - approvalMode = m.getOrDefault("审批模式", approvalMode); - autoApproveDelayMinutes = parseInt(m.getOrDefault("自动审批延迟分钟", "30"), 30); - loginTimeout = parseInt(m.getOrDefault("登录超时秒数", "60"), 60); - afkEnabled = Boolean.parseBoolean(m.getOrDefault("挂机踢出", "false")); - afkTimeout = parseInt(m.getOrDefault("挂机超时秒数", "300"), 300); - smtpSsl = Boolean.parseBoolean(m.getOrDefault("smtp加密", "true")); - httpPort = parseInt(m.getOrDefault("http-port", "8080"), 8080); - broadcastServerUrl = m.getOrDefault("resource-pack-url", ""); - - providerPointsPerScore = parseDouble(m.getOrDefault("服务商积分倍率", "2.0"), 2.0); - baseEconomyReward = parseInt(m.getOrDefault("基础经济奖励", "100"), 100); - providerEconomyPerScore = parseDouble(m.getOrDefault("经济奖励每评分", "50.0"), 50.0); - requesterPointsReward = parseInt(m.getOrDefault("报单人积分奖励", "10"), 10); - - garbageEnabled = Boolean.parseBoolean(m.getOrDefault("垃圾站_启用", "true")); - garbageInterval = parseInt(m.getOrDefault("垃圾站_清理间隔秒", "300"), 300); - garbageMaxRounds = parseInt(m.getOrDefault("垃圾站_保留轮数", "1"), 1); - - checkinRewardFixed = parseDouble(m.getOrDefault("签到固定金额", "0"), 0); - checkinRewardMin = parseDouble(m.getOrDefault("签到最小金额", "0"), 0); - checkinRewardMax = parseDouble(m.getOrDefault("签到最大金额", "0"), 0); - checkinGivePoints = Boolean.parseBoolean(m.getOrDefault("签到给积分", "true")); - checkinPoints = parseInt(m.getOrDefault("签到积分数量", "10"), 10); - backCheckPointMultiplier = parseInt(m.getOrDefault("补签积分倍率", "5"), 5); - checkinBondMin = parseInt(m.getOrDefault("签到债券最小", "1"), 1); - checkinBondMax = parseInt(m.getOrDefault("签到债券最大", "3"), 3); - if (checkinBondMax < checkinBondMin) checkinBondMax = checkinBondMin; - - if (checkinRewardFixed > 0) checkinRewardType = "fixed"; - else if (checkinRewardMin > 0 && checkinRewardMax > checkinRewardMin) checkinRewardType = "range"; - else checkinRewardType = "none"; - rewardChannel = m.getOrDefault("奖励发放方式", "债券"); - rewardChannel = normalizeRewardChannel(rewardChannel); - -// 验证合法值 - if (!"economy".equalsIgnoreCase(rewardChannel) - && !"bonds".equalsIgnoreCase(rewardChannel)) { - rewardChannel = "bonds"; - } - - - - // ★ 自动补充新配置项 ★ - boolean needAppend = false; - List newKeys = new ArrayList<>(); - if (!m.containsKey("签到债券最小")) { needAppend = true; newKeys.add("签到债券最小=" + checkinBondMin); } - if (!m.containsKey("签到债券最大")) { needAppend = true; newKeys.add("签到债券最大=" + checkinBondMax); } - if (!m.containsKey("奖励发放方式")) { - needAppend = true; - newKeys.add("奖励发放方式=债券"); - } - if (needAppend) { - try { - FileWriter fw = new FileWriter(file, true); - BufferedWriter bw = new BufferedWriter(fw); - bw.newLine(); - for (String key : newKeys) { bw.write(key); bw.newLine(); } - bw.flush(); bw.close(); - System.out.println("[Config] 已补充签到债券配置"); - - } catch (IOException ignored) {} - if (!m.containsKey("奖励发放方式")) { - try { - FileWriter fw = new FileWriter(file, true); - BufferedWriter bw = new BufferedWriter(fw); - bw.newLine(); - bw.write("奖励发放方式=bonds"); - bw.newLine(); - bw.flush(); bw.close(); - } catch (IOException ignored) {} - } - - } - } - /** - * 奖励方式标准化:中英文全兼容 - * "债券" "bonds" "Bond" "BONDS" → "bonds" - * "经济" "economy" "Economy" "ECONOMY" → "economy" - * 其他任何值 → "bonds"(默认债券) - */ - private String normalizeRewardChannel(String raw) { - if (raw == null) return "bonds"; - String s = raw.trim().toLowerCase(); - // 中文 - if (s.contains("债券")) return "bonds"; - if (s.contains("经济")) return "economy"; - // 英文 - if (s.contains("bond")) return "bonds"; - if (s.contains("econ")) return "economy"; - if (s.contains("Vault")) return "economy"; - // 兜底 - return "bonds"; - } - - public void saveSettings() { - File file = new File(dataFolder, "插件设置.txt"); - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=" + adminTag); - L.add("管理密码=" + adminPassword); - L.add("每IP最大账号数=" + maxAccountsPerIP); - L.add("审批模式=" + approvalMode); - L.add("自动审批延迟分钟=" + autoApproveDelayMinutes); - L.add("登录超时秒数=" + loginTimeout); - L.add("挂机踢出=" + afkEnabled); - L.add("挂机超时秒数=" + afkTimeout); - L.add("resource-pack-url=" + broadcastServerUrl); - L.add("http-port=" + httpPort); - L.add("垃圾站_启用=" + garbageEnabled); - L.add("垃圾站_清理间隔秒=" + garbageInterval); - L.add("垃圾站_保留轮数=" + garbageMaxRounds); - L.add("签到固定金额=" + checkinRewardFixed); - L.add("签到最小金额=" + checkinRewardMin); - L.add("签到最大金额=" + checkinRewardMax); - L.add("签到给积分=" + checkinGivePoints); - L.add("签到积分数量=" + checkinPoints); - L.add("签到债券最小=" + checkinBondMin); - L.add("签到债券最大=" + checkinBondMax); - L.add("补签积分倍率=" + backCheckPointMultiplier); - L.add("奖励发放方式=债券"); - writeLines(file, L); - } - - private void createDefaultSettings(File f) { - List L = new ArrayList<>(); - L.add("# ===== Sdf1_login 插件设置 ====="); - L.add("管理标签=admin"); - L.add("管理密码=qweasd"); - L.add("每IP最大账号数=3"); - L.add("审批模式=manual"); - L.add("自动审批延迟分钟=30"); - L.add("登录超时秒数=60"); - L.add("挂机踢出=false"); - L.add("挂机超时秒数=300"); - L.add("resource-pack-url="); - L.add("http-port=8080"); - L.add("垃圾站_启用=true"); - L.add("垃圾站_清理间隔秒=300"); - L.add("垃圾站_保留轮数=1"); - L.add("签到固定金额=0"); - L.add("签到最小金额=50"); - L.add("签到最大金额=500"); - L.add("签到给积分=true"); - L.add("签到积分数量=10"); - L.add("签到债券最小=1"); - L.add("签到债券最大=3"); - L.add("补签积分倍率=5"); - L.add("奖励发放方式=债券"); - writeLines(f, L); - } - public List getAfkWhitelist() { - List list = new ArrayList<>(); - File file = new File(dataFolder, "挂机白名单.txt"); - if (!file.exists()) { - try { - file.createNewFile(); - FileWriter fw = new FileWriter(file); - fw.write("# 每行一个玩家名\n"); - fw.flush(); - fw.close(); - } catch (IOException ignored) {} - return list; - } - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(file), - StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (!line.isEmpty() && !line.startsWith("#")) - list.add(line); - } - } catch (IOException ignored) {} - return list; - } - - // ==================== 其他 ==================== - - public String generateSmtpVerifyCode() { - String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(); - java.util.Random r = new java.util.Random(); - for (int i = 0; i < 6; i++) sb.append(chars.charAt(r.nextInt(chars.length()))); - smtpVerifyCode = sb.toString(); - smtpVerifyExpire = System.currentTimeMillis() + 300000L; - return smtpVerifyCode; - } - - public boolean checkSmtpVerifyCode(String code) { - if (System.currentTimeMillis() > smtpVerifyExpire) { smtpVerifyCode = ""; return false; } - return smtpVerifyCode.equalsIgnoreCase(code); - } - - public void clearSmtpVerifyCode() { smtpVerifyCode = ""; smtpVerifyExpire = 0; } - public boolean canSendSmtpVerify() { return System.currentTimeMillis() - smtpLastSendTime >= 60000L; } - public void recordSmtpSendTime() { smtpLastSendTime = System.currentTimeMillis(); } - - // ==================== 文件工具 ==================== - - private void loadMap(File file, Map map) { - try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq > 0) map.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); - } - } catch (IOException ignored) {} - } - - private void writeLines(File file, List lines) { - try (BufferedWriter w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { - for (String l : lines) { w.write(l); w.newLine(); } - } catch (IOException ignored) {} - } - - private double parseDouble(String s, double def) { try { return Double.parseDouble(s.trim()); } catch (Exception e) { return def; } } - private int parseInt(String s, int def) { try { return Integer.parseInt(s.trim()); } catch (Exception e) { return def; } } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CypayCommand.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/CypayCommand.java deleted file mode 100644 index 7fd9067..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/CypayCommand.java +++ /dev/null @@ -1,577 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; - -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.stream.Collectors; - -public class CypayCommand - implements CommandExecutor, TabCompleter { - - private final Main plugin; - private static final SimpleDateFormat SDF = - new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - - public CypayCommand(Main plugin) { - this.plugin = plugin; - } - - private boolean hasAdminPerm(CommandSender s) { - if (s.isOp()) return true; - if (!(s instanceof Player)) return true; - Player p = (Player) s; - String tag = - plugin.getConfig2().adminTag; - return !tag.isEmpty() - && p.getScoreboardTags().contains(tag); - } - - private int parseAmount(String s) { - return Integer.parseInt( - s.trim().replaceAll("[+]", "")); - } - - private List filter( - List list, String prefix) { - String low = prefix.toLowerCase(); - return list.stream() - .filter(x -> x.toLowerCase() - .startsWith(low)) - .collect(Collectors.toList()); - } - - @Override - public boolean onCommand(CommandSender sender, - Command cmd, - String label, - String[] args) { - - // ===== /cypay — 查自己 ===== - if (args.length == 0) { - String name = sender instanceof Player - ? sender.getName() : "Console"; - int bonds = - plugin.getBonds().getBonds(name); - String status = plugin.getBonds() - .getAccountStatus(name); - sender.sendMessage( - "§e§l[债券] §f" + name - + " 余额: §a" + bonds); - sender.sendMessage( - "§e§l[债券] §f状态: " - + ("frozen".equals(status) - ? "§c冻结中" - : "§a正常")); - return true; - } - - // ===== /cypay import ===== - /* if (args.length == 2 - && "import".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String file = args[1]; - if (!file.endsWith(".txt")) - file += ".txt"; - int result = plugin.getCDK() - .importFromFile(file); - if (result == -1) - sender.sendMessage("§c文件不存在: " - + file); - else - sender.sendMessage("§a导入完成: " - + result + " 条"); - return true; - }*/ - - // ===== /cypay freeze ===== - if (args.length == 2 - && "freeze".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - if (plugin.getBonds().isFrozen(target)) { - sender.sendMessage("§c" + target - + " 已处于冻结状态"); - return true; - } - plugin.getBonds().freezeAccount(target); - sender.sendMessage("§a已冻结 " + target - + " 的债券账户"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§c§l[系统] 你的债券账户" - + "已被管理员冻结"); - return true; - } - - // ===== /cypay unfreeze ===== - if (args.length == 2 - && "unfreeze".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - if (!plugin.getBonds() - .isFrozen(target)) { - sender.sendMessage("§c" + target - + " 未被冻结"); - return true; - } - plugin.getBonds().unfreezeAccount(target); - sender.sendMessage("§a已解冻 " + target - + " 的债券账户"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§a§l[系统] 你的债券账户" - + "已被解冻"); - return true; - } - - // ===== /cypay water [days] ===== - if (args.length >= 2 - && "water".equalsIgnoreCase( - args[0])) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - int days = 7; - if (args.length >= 3) { - try { - days = Integer.parseInt(args[2]); - } catch (NumberFormatException ignored) { - } - } - days = Math.min(days, - BondManager.ADMIN_MAX_WATER_DAYS); - - if (days <= 0) days = 1; - - List> txs = - plugin.getBonds() - .getTransactions( - target, days); - - sender.sendMessage( - "§e§l===== " + target - + " 流水记录 (近" - + days + "天) ====="); - sender.sendMessage("§f当前余额: §a" - + plugin.getBonds() - .getBonds(target) - + " §f状态: " - + (plugin.getBonds() - .isFrozen(target) - ? "§c冻结" : "§a正常")); - - if (txs.isEmpty()) { - sender.sendMessage("§7 (暂无记录)"); - } else { - for (Map tx : txs) { - sender.sendMessage( - formatTxLine(tx)); - } - } - sender.sendMessage( - "§e§l==========================" - + "=========="); - return true; - } - // ===== /cypay printer [player] ===== - if ("printer".equalsIgnoreCase(args[0])) { - if (!hasAdminPerm(sender) - && !(sender instanceof Player) - == false) { - // 控制台也可以用 - if (!(sender instanceof Player)) - ; // 放行 - else if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - } - if (args.length == 1) { - // 打印全服 - sender.sendMessage( - "§e正在导出全服流水..."); - plugin.getBonds(); // 确保实例就绪 - if (plugin.getBondPrinter() != null) { - plugin.getBondPrinter() - .printAll(sender); - } else { - sender.sendMessage("§cPrinter未初始化"); - } - return true; - } - if (args.length == 2) { - // 打印指定玩家 - String target = args[1]; - sender.sendMessage("§e正在导出 " - + target + " 的流水..."); - if (plugin.getBondPrinter() != null) { - plugin.getBondPrinter() - .printPlayer(target, sender); - } else { - sender.sendMessage("§cPrinter未初始化"); - } - return true; - } - } - - // ===== /cypay give/remove ===== - if (args.length == 3) { - String action = args[0].toLowerCase(); - if ("give".equals(action) - || "remove".equals(action)) { - if (!hasAdminPerm(sender)) { - sender.sendMessage("§c权限不足"); - return true; - } - String target = args[1]; - try { - int amount = - parseAmount(args[2]); - if (amount <= 0) { - sender.sendMessage( - "§c金额必须大于0"); - return true; - } - String txType = - "give".equals(action) - ? BondManager - .TX_ADMIN_GIVE - : BondManager - .TX_ADMIN_DEDUCT; - String opName = - sender instanceof Player - ? sender.getName() - : "Console"; - - if ("give".equals(action)) { - plugin.getBonds().addBonds( - target, amount, - txType, "", - opName, "管理员发放"); - sender.sendMessage( - "§a已给 " + target - + " " + amount - + " 债券"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage( - "§a管理员给了你 " - + amount - + " 债券"); - } else { - if (plugin.getBonds() - .deductBonds(target, - amount, - txType, "", - opName, - "管理员扣除")) { - sender.sendMessage( - "§a已扣除 " + target - + " " + amount - + " 债券"); - Player tp = plugin - .getServer() - .getPlayerExact( - target); - if (tp != null) - tp.sendMessage( - "§c管理员扣除了" - + "你的 " - + amount - + " 债券"); - } else { - sender.sendMessage( - "§c" + target - + " 债券不足" - + "或账户冻结"); - } - } - } catch (NumberFormatException e) { - sender.sendMessage( - "§c金额格式错误"); - } - return true; - } - } - - // ===== /cypay — 仅玩家转账 ===== - if (args.length == 2) { - try { - int amount = parseAmount(args[1]); - if (amount <= 0) { - sender.sendMessage( - "§c金额必须大于0"); - return true; - } - // ★ 控制台禁止转账 - if (!(sender instanceof Player)) { - sender.sendMessage( - "§c控制台不能转账,请使用 " - + "/cypay give <玩家> <金额>"); - return true; - } - Player p = (Player) sender; - String target = args[0]; - BondManager.TransferResult r = - plugin.getBonds().transfer( - p.getName(), - target, amount); - if (r.success) { - p.sendMessage("§e§l[债券转账] §f向 §a" - + target + " §f转出 §c" - + amount + " §f枚债券"); - Player tp = plugin.getServer() - .getPlayerExact(target); - if (tp != null) - tp.sendMessage("§e§l[债券转账] §f收到 §a" - + p.getName() - + " §f转入 §a" - + amount + " §f枚债券"); - } else { - p.sendMessage("§c" + r.message - + ",当前: " - + plugin.getBonds() - .getBonds(p.getName())); - } - return true; - } catch (NumberFormatException ignored) { - } - } - - // ===== /cypay — 查指定玩家 ===== - if (args.length == 1) { - String target = args[0]; - // ★ 验证玩家在 login.db 中存在 ★ - if (!plugin.getDb().userExists(target)) { - sender.sendMessage("§c玩家 " + target - + " 不存在于服务器"); - return true; - } - - // String target = args[0]; - if (hasAdminPerm(sender)) { - int bonds = plugin.getBonds() - .getBonds(target); - String status = plugin.getBonds() - .getAccountStatus(target); - sender.sendMessage( - "§e§l[债券] §f" + target - + " 余额: §a" - + bonds); - sender.sendMessage( - "§e§l[债券] §f状态: " - + ("frozen".equals( - status) - ? "§c冻结中" - : "§a正常")); - } else { - int bonds = plugin.getBonds() - .getBonds( - sender.getName()); - sender.sendMessage( - "§e§l[债券] §f你的余额: §a" - + bonds); - } - return true; - } - - showHelp(sender); - return true; - } - - // ===== 流水行格式化 ===== - - private String formatTxLine( - Map tx) { - 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 pos = amount > 0; - String sign = pos ? "+" : ""; - String color = pos ? "§a" : "§c"; - - StringBuilder sb = new StringBuilder(); - sb.append("§7[") - .append(SDF.format(new Date(time))) - .append("] ") - .append(formatTxType(type)) - .append(" ") - .append(color).append(sign) - .append(amount).append("§7"); - if (tgt != null && !tgt.isEmpty()) - sb.append(" §e↔ ").append(tgt); - if (reason != null && !reason.isEmpty()) - sb.append(" §7(").append(reason) - .append(")"); - sb.append(" §7余额:§f").append(bal); - return sb.toString(); - } - - private String formatTxType(String type) { - if (type == null) return "§7未知"; - switch (type) { - case BondManager.TX_ADMIN_GIVE: - return "§a管理员发放"; - case BondManager.TX_ADMIN_DEDUCT: - return "§c管理员扣除"; - case BondManager.TX_TRANSFER_OUT: - return "§c转出"; - case BondManager.TX_TRANSFER_IN: - return "§a转入"; - case BondManager.TX_REDEEM: - return "§aCDK兑换"; - case BondManager.TX_SHOP_BUY: - return "§e商城购买"; - case BondManager.TX_DAILY_SIGN: - return "§a签到奖励"; - case BondManager.TX_FREEZE: - return "§c冻结"; - case BondManager.TX_UNFREEZE: - return "§a解冻"; - default: - return "§7" + type; - } - } - - private void showHelp(CommandSender sender) { - sender.sendMessage("§e§l债券命令帮助:"); - sender.sendMessage( - "§e/cypay §7- 查询自己余额"); - sender.sendMessage( - "§e/cypay <玩家> §7- 查余额(管理员)"); - sender.sendMessage( - "§e/cypay <玩家> <金额> §7- 转账"); - if (hasAdminPerm(sender)) { - sender.sendMessage( - "§e/cypay give <玩家> <金额> §7- 给债券"); - sender.sendMessage( - "§e/cypay remove <玩家> <金额> §7- 扣债券"); - sender.sendMessage( - "§e/cypay import <文件> §7- 导入CDK"); - sender.sendMessage( - "§e/cypay freeze <玩家> §7- 冻结"); - sender.sendMessage( - "§e/cypay unfreeze <玩家> §7- 解冻"); - sender.sendMessage( - "§e/cypay water <玩家> [天数] §7- 流水"); - sender.sendMessage( - "§e/cypay printer §7- 导出全服流水xlsx"); - sender.sendMessage( - "§e/cypay printer <玩家> §7- 导出指定玩家xlsx"); - - } - } - - @Override - public List onTabComplete( - CommandSender sender, Command command, - String label, String[] args) { - List list = new ArrayList<>(); - - if (args.length == 1) { - String last = args[0].toLowerCase(); - if (hasAdminPerm(sender)) { - list.add("give"); - list.add("remove"); - list.add("import"); - list.add("freeze"); - list.add("unfreeze"); - list.add("water"); - list.add("printer"); - - File cdkDir = new File( - plugin.getDataFolder(), - "cdk"); - if (cdkDir.exists()) { - File[] files = cdkDir.listFiles( - (d, n) -> n - .endsWith(".txt")); - if (files != null) - for (File f : files) - list.add(f.getName()); - } - } - for (Player o : - Bukkit.getOnlinePlayers()) - list.add(o.getName()); - return filter(list, last); - } - - if (args.length == 2) { - String first = args[0].toLowerCase(); - if ("give".equals(first) - || "remove".equals(first) - || "freeze".equals(first) - || "unfreeze".equals(first) - || "water".equals(first)) { - for (Player o : - Bukkit.getOnlinePlayers()) - list.add(o.getName()); - return filter(list, - args[1].toLowerCase()); - } - list.add("10"); - list.add("50"); - list.add("100"); - return filter(list, - args[1].toLowerCase()); - } - - if (args.length == 3) { - String first = args[0].toLowerCase(); - if ("give".equals(first) - || "remove".equals(first)) { - list.add("10"); - list.add("50"); - list.add("100"); - list.add("999"); - return filter(list, - args[2].toLowerCase()); - } - if ("water".equals(first)) { - list.add("1"); - list.add("3"); - list.add("7"); - list.add("14"); - return filter(list, - args[2].toLowerCase()); - } - } - - return list; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/DatabaseManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/DatabaseManager.java deleted file mode 100644 index cb2b8f7..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/DatabaseManager.java +++ /dev/null @@ -1,1758 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.logging.Logger; - -public class DatabaseManager { - - private Connection db; - private final File dataFolder; - private final Logger logger; - - public DatabaseManager(File dataFolder) { - this.dataFolder = dataFolder; - this.logger = Logger.getLogger("Sdf1_login"); - } - - // ==================== 初始化 ==================== - - public void init() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(dataFolder, - "login.db"); - db = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - - // 用户表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "users (" - + "player_name TEXT PRIMARY KEY," - + "password_hash TEXT NOT NULL," - + "password_salt TEXT NOT NULL," - + "email TEXT DEFAULT ''," - + "register_time INTEGER DEFAULT 0," - + "last_login_time INTEGER DEFAULT 0," - + "total_online_time INTEGER DEFAULT 0," - + "last_online_check INTEGER DEFAULT 0," - + "is_logged_in INTEGER DEFAULT 0," - + "blocks_broken INTEGER DEFAULT 0," - + "blocks_placed INTEGER DEFAULT 0," - + "gift_stage INTEGER DEFAULT 0," - + "gift_claimed TEXT DEFAULT ''," - + "invite_code TEXT DEFAULT ''," - + "invited_by TEXT DEFAULT ''," - + "monthly_invite_count INTEGER DEFAULT 0," - + "invite_month TEXT DEFAULT ''," - + "points INTEGER DEFAULT 0," - + "checkin_streak INTEGER DEFAULT 0," - + "last_checkin_date TEXT DEFAULT ''," - + "total_checkin_days INTEGER DEFAULT 0," - + "temp_password TEXT DEFAULT ''," - + "temp_pw_expire INTEGER DEFAULT 0," - + "temp_pw_used INTEGER DEFAULT 0," - + "ip_address TEXT DEFAULT ''," - + "tasks_completed TEXT DEFAULT ''," - + "premium_uuid TEXT DEFAULT ''," - + "register_ip TEXT DEFAULT ''," - + "register_type TEXT DEFAULT 'manual'," - + "backcheck_dates TEXT DEFAULT ''" - + ")"); - - - // 安全添加 users 缺失列 - safeAdd(st, "tasks_completed", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_password", - "TEXT DEFAULT ''"); - safeAdd(st, "temp_pw_expire", - "INTEGER DEFAULT 0"); - safeAdd(st, "temp_pw_used", - "INTEGER DEFAULT 0"); - safeAdd(st, "ip_address", - "TEXT DEFAULT ''"); - safeAdd(st, "premium_uuid", - "TEXT DEFAULT ''"); - safeAdd(st, "register_ip", - "TEXT DEFAULT ''"); - safeAdd(st, "register_type", - "TEXT DEFAULT 'manual'"); - - // ===== 背包备份表:安全迁移 ===== - // 检查旧表是否存在(用 inventory_data 列判断) - boolean hasOldTable = false; - try { - ResultSet rs = st.executeQuery( - "SELECT inventory_data " - + "FROM inventory_backups " - + "LIMIT 1"); - hasOldTable = true; - rs.close(); - } catch (SQLException e) { - hasOldTable = false; - } - - if (hasOldTable) { - // 旧表存在,迁移数据到新表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups_new (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - // 迁移旧数据 - try { - st.execute("INSERT INTO " - + "inventory_backups_new " - + "(player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience) " - + "SELECT player_name, " - + "inventory_data, " - + "armor_data, " - + "extra_data, " - + "player_level, " - + "player_exp " - + "FROM inventory_backups"); - } catch (SQLException ignored) { - } - st.execute("DROP TABLE " - + "inventory_backups"); - st.execute("ALTER TABLE " - + "inventory_backups_new " - + "RENAME TO " - + "inventory_backups"); - } else { - // 无旧表或已迁移,直接创建 - st.execute("CREATE TABLE IF NOT EXISTS " - + "inventory_backups (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "contents_b64 TEXT DEFAULT ''," - + "armor_b64 TEXT DEFAULT ''," - + "extra_b64 TEXT DEFAULT ''," - + "level INTEGER DEFAULT 0," - + "experience REAL DEFAULT 0," - + "save_time INTEGER DEFAULT 0" - + ")"); - } - - // 安全报警表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "security_alerts (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "alert_ip TEXT NOT NULL," - + "alert_time INTEGER NOT NULL," - + "alert_date TEXT NOT NULL," - + "notified INTEGER DEFAULT 0," - + "notified_time INTEGER DEFAULT 0)"); - // PVP区域表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "name TEXT NOT NULL UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER DEFAULT 0" - + ")"); - -// PVP击杀总榜表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT NOT NULL," - + "region_name TEXT NOT NULL," - + "kills INTEGER DEFAULT 0," - + "deaths INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, region_name)" - + ")"); - - -// 菜单图标表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "menu_icons (" - + "player_name TEXT PRIMARY KEY," - + "icon_b64 TEXT DEFAULT ''," - + "item_name TEXT DEFAULT ''," - + "save_time INTEGER DEFAULT 0" - + ")"); - - // 工单表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "tickets (" - + "id INTEGER PRIMARY KEY AUTOINCREMENT," - + "type TEXT NOT NULL," - + "status TEXT DEFAULT 'pending'," - + "priority TEXT DEFAULT 'normal'," - + "requester TEXT NOT NULL," - + "assigned_to TEXT DEFAULT ''," - + "title TEXT DEFAULT ''," - + "description TEXT DEFAULT ''," - + "reward_amount INTEGER DEFAULT 0," - + "score INTEGER DEFAULT 0," - + "target_player TEXT DEFAULT ''," - + "create_time INTEGER NOT NULL," - + "update_time INTEGER DEFAULT 0," - + "complete_time INTEGER DEFAULT 0," - + "admin_confirmed INTEGER DEFAULT 0," - + "reject_reason TEXT DEFAULT ''" - + ")"); - - // 工单回复表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "ticket_replies (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "ticket_id INTEGER NOT NULL," - + "sender TEXT NOT NULL," - + "message TEXT NOT NULL," - + "create_time INTEGER NOT NULL" - + ")"); - - // 服务商表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "service_providers (" - + "player_name TEXT PRIMARY KEY," - + "role TEXT DEFAULT 'waiter'," - + "active INTEGER DEFAULT 1," - + "join_time INTEGER DEFAULT 0" - + ")"); - // 垃圾回收站物品表 - st.execute("CREATE TABLE IF NOT EXISTS " - + "items (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "player_name TEXT NOT NULL," - + "item_data TEXT DEFAULT ''," - + "amount INTEGER DEFAULT 1," - + "create_time INTEGER DEFAULT 0" - + ")"); - safeAdd(st, "menu_snowball", - "INTEGER DEFAULT 1"); - st.close(); - logger.info("[Sdf1_login] 数据库初始化完成"); - } catch (Exception e) { - throw new RuntimeException( - "[Sdf1_login] DB初始化失败: " - + e.getMessage(), e); - } - } - - - private void safeAdd(Statement st, - String col, String def) { - try { - st.executeUpdate("ALTER TABLE users " - + "ADD COLUMN " + col + " " + def); - } catch (SQLException ignored) { - } - } - - public void close() { - try { - if (db != null && !db.isClosed()) - db.close(); - } catch (Exception ignored) { - } - } -// ==================== 菜单图标 ==================== - - public void saveMenuIcon(String name, - String iconB64, String itemName) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO menu_icons " - + "(player_name, icon_b64, " - + "item_name, save_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, iconB64); - ps.setString(3, itemName); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT icon_b64 FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - String b64 = null; - if (rs.next()) { - b64 = rs.getString("icon_b64"); - } - rs.close(); - ps.close(); - return b64; - } catch (SQLException e) { - return null; - } - } - - public void deleteMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM menu_icons " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasMenuIcon(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM menu_icons " - + "WHERE player_name=? " - + "AND icon_b64 IS NOT NULL " - + "AND icon_b64 != '' " - + "LIMIT 1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - // ==================== 用户操作 ==================== - - public boolean userExists(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public void createUser(String name, String hash, - String salt) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO users " - + "(player_name, " - + "password_hash, " - + "password_salt, " - + "register_time) " - + "VALUES (?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, hash); - ps.setString(3, salt); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ========== 临时密码管理 ========== - - public void clearTempPassword(String name) { - setField(name, "temp_password", null); - setField(name, "temp_pw_expire", 0); - setField(name, "temp_pw_used", 1); - } - - public void clearAllTempPasswords() { - try { - Statement st = db.createStatement(); - st.executeUpdate( - "UPDATE users SET " - + "temp_password=NULL," - + "temp_pw_expire=0," - + "temp_pw_used=1 " - + "WHERE temp_password " - + "IS NOT NULL"); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public String getPlayerIPByName( - String name) { - Object ip = getField(name, "ip"); - return ip != null - ? String.valueOf(ip) : null; - } - - public boolean checkPasswordOrTemp( - String name, String hash) { - // 先检查主密码 - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String mainHash = - rs.getString("password_hash"); - rs.close(); - ps.close(); - if (hash.equals(mainHash)) - return true; - } else { - rs.close(); - ps.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - // 再检查临时密码 - try { - PreparedStatement ps2 = db.prepareStatement( - "SELECT temp_password, " - + "temp_pw_expire, " - + "temp_pw_used " - + "FROM users " - + "WHERE player_name=?"); - ps2.setString(1, name); - ResultSet rs2 = ps2.executeQuery(); - if (rs2.next()) { - String tempHash = - rs2.getString( - "temp_password"); - long expire = - rs2.getLong( - "temp_pw_expire"); - int used = - rs2.getInt( - "temp_pw_used"); - rs2.close(); - ps2.close(); - if (tempHash != null - && !tempHash.isEmpty() - && hash.equals(tempHash) - && System.currentTimeMillis() - < expire - && used != 1) { - return true; - } - } else { - rs2.close(); - ps2.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - public Map getUser(String name) { - Map r = - new LinkedHashMap<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - r.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return r; - } - - public void setField(String name, String field, - Object val) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE users SET " + field - + "=? " - + "WHERE player_name=?"); - if (val instanceof Integer) - ps.setInt(1, (Integer) val); - else if (val instanceof Long) - ps.setLong(1, (Long) val); - else - ps.setString(1, - String.valueOf(val)); - ps.setString(2, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Object getField(String name, - String field) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT " + field - + " FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - Object val = rs.next() - ? rs.getObject(1) : null; - rs.close(); - ps.close(); - return val; - } catch (SQLException e) { - return null; - } - } - - public boolean checkPassword(String name, - String hash) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT password_hash " - + "FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String stored = - rs.getString("password_hash"); - rs.close(); - ps.close(); - return hash.equals(stored); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - - - public boolean isUsingTempPassword(String name) { - String h = (String) getField(name, - "temp_password"); - return h != null && !h.isEmpty(); - } - - public void setLoggedIn(String name, - boolean val) { - setField(name, "is_logged_in", - val ? 1 : 0); - } - - public void addPoints(String name, int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - setField(name, "points", curVal + amount); - } - - public boolean deductPoints(String name, - int amount) { - Object cur = getField(name, "points"); - int curVal = cur instanceof Number - ? ((Number) cur).intValue() : 0; - if (curVal < amount) return false; - setField(name, "points", - curVal - amount); - return true; - } - public List> - getAllUsers() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM users"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - public List> - getTicketsByProviderAll( - String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deleteUser(String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM users " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void recordIP(String name, String ip) { - if (ip != null && !ip.isEmpty()) { - setField(name, "ip_address", ip); - } - } - - // ==================== IP查询 ==================== - - /** - * 统计指定IP注册的账号数量 - */ - public int countIPAccounts(String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address=?"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count; - } catch (SQLException e) { - return 0; - } - } - - /** - * 获取指定IP下所有账号详情 - */ - public List> - getAccountDetailsByIP(String ip) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, " - + "register_time " - + "FROM users " - + "WHERE ip_address=? " - + "ORDER BY register_time"); - ps.setString(1, ip); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("register_time", - rs.getLong("register_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 背包备份 ==================== - - public void saveInventoryBackup(String name, - String contents, String armor, - String extra, int level, - double experience) { - try { - PreparedStatement del = - db.prepareStatement( - "DELETE FROM " - + "inventory_backups " - + "WHERE player_name=?"); - del.setString(1, name); - del.executeUpdate(); - del.close(); - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO " - + "inventory_backups" - + " (player_name, " - + "contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience, " - + "save_time) " - + "VALUES " - + "(?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, contents); - ps.setString(3, armor); - ps.setString(4, extra); - ps.setInt(5, level); - ps.setDouble(6, experience); - ps.setLong(7, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public String[] loadInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time " - + "DESC LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void deleteInventoryBackup( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM inventory_backups " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean hasInventoryBackup( - String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id FROM " - + "inventory_backups " - + "WHERE player_name=? " - + "LIMIT 1"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - boolean has = rs.next(); - rs.close(); - ps.close(); - return has; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } -// ==================== PVP区域 ==================== - - public void savePVPRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions " - + "(name, world, " - + "x1, y1, z1, " - + "x2, y2, z2, " - + "create_time) " - + "VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map - getPVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getAllPVPRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - db.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void deletePVPRegion(String name) { - try { - PreparedStatement ps = - db.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - -// ==================== PVP击杀 ==================== - - public int getPVPKills( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int kills = 0; - if (rs.next()) - kills = rs.getInt("kills"); - rs.close(); - ps.close(); - return kills; - } catch (SQLException e) { - return 0; - } - } - - public int getPVPDeaths( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=? " - + "AND region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = ps.executeQuery(); - int deaths = 0; - if (rs.next()) - deaths = rs.getInt("deaths"); - rs.close(); - ps.close(); - return deaths; - } catch (SQLException e) { - return 0; - } - } - - public void addPVPKill( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,1,0) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "kills=kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void addPVPDeath( - String player, String region) { - try { - PreparedStatement ps = - db.prepareStatement( - "INSERT INTO pvp_kills " - + "(player_name, " - + "region_name, " - + "kills, deaths) " - + "VALUES (?,?,0,1) " - + "ON CONFLICT" - + "(player_name, " - + "region_name) " - + "DO UPDATE SET " - + "deaths=deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getPVPKillTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT player_name, " - + "kills, deaths " - + "FROM pvp_kills " - + "WHERE " - + "region_name=? " - + "AND kills>0 " - + "ORDER BY kills " - + "DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString("player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - // ==================== 安全报警 ==================== - - public void recordSecurityAlert( - String playerName, String ip) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO security_alerts " - + "(player_name, alert_ip, " - + "alert_time, alert_date) " - + "VALUES (?,?,?,?)"); - ps.setString(1, playerName); - ps.setString(2, ip); - long now = System.currentTimeMillis(); - ps.setLong(3, now); - ps.setString(4, - new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date( - now))); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - - } - - public List> - getUnnotifiedAlerts( - String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM security_alerts " - + "WHERE player_name=? " - + "AND notified=0 " - + "ORDER BY alert_time ASC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void markAlertNotified(int alertId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE security_alerts " - + "SET notified=1, " - + "notified_time=? " - + "WHERE id=?"); - ps.setLong(1, - System.currentTimeMillis()); - ps.setInt(2, alertId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - /** - * 获取玩家最近N条背包备份 - */ - public List> - getInventoryBackups(String name, - int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT id, save_time " - + "FROM inventory_backups " - + "WHERE player_name=? " - + "ORDER BY save_time DESC " - + "LIMIT ?"); - ps.setString(1, name); - ps.setInt(2, limit); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("id", - rs.getInt("id")); - row.put("save_time", - rs.getLong("save_time")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - - /** - * 获取指定ID的备份数据 - */ - public String[] getInventoryBackupById( - int backupId) { - try { - PreparedStatement ps = - db.prepareStatement( - "SELECT contents_b64, " - + "armor_b64, " - + "extra_b64, " - + "level, " - + "experience " - + "FROM inventory_backups " - + "WHERE id=?"); - ps.setInt(1, backupId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - String[] result = - new String[5]; - result[0] = rs.getString( - "contents_b64"); - result[1] = rs.getString( - "armor_b64"); - result[2] = rs.getString( - "extra_b64"); - result[3] = String.valueOf( - rs.getInt("level")); - result[4] = String.valueOf( - rs.getDouble("experience")); - rs.close(); - ps.close(); - return result; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - - // ==================== 工单操作 ==================== - - public int createTicket(String type, - String status, String priority, - String requester, String title, - String description, - int rewardAmount, - String targetPlayer) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO tickets " - + "(type, status, priority, " - + "requester, title, " - + "description, " - + "reward_amount, " - + "target_player, " - + "create_time) " - + "VALUES (?,?,?,?,?,?,?,?,?)"); - ps.setString(1, type); - ps.setString(2, status); - ps.setString(3, priority); - ps.setString(4, requester); - ps.setString(5, title); - ps.setString(6, description); - ps.setInt(7, rewardAmount); - ps.setString(8, targetPlayer); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ResultSet rs = - ps.getGeneratedKeys(); - int id = -1; - if (rs.next()) id = rs.getInt(1); - rs.close(); - ps.close(); - return id; - } catch (SQLException e) { - e.printStackTrace(); - return -1; - } - } - - public void updateTicketStatus(int ticketId, - String status) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET status=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, status); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void assignTicket(int ticketId, - String provider) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "assigned_to=?, " - + "status='processing', " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, provider); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void completeTicket(int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='completed', " - + "complete_time=?, " - + "update_time=? " - + "WHERE id=?"); - long now = System.currentTimeMillis(); - ps.setLong(1, now); - ps.setLong(2, now); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void confirmTicket(int ticketId, - int score) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "admin_confirmed=1, " - + "score=?, " - + "status='resolved', " - + "update_time=? " - + "WHERE id=?"); - ps.setInt(1, score); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - - public void rejectTicketComplete(int ticketId, - String reason) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " - + "status='rejected', " - + "reject_reason=?, " - + "update_time=? " - + "WHERE id=?"); - ps.setString(1, reason); - ps.setLong(2, - System.currentTimeMillis()); - ps.setInt(3, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 更新工单任意字段 - */ - public void updateTicketField(int ticketId, - String field, Object value) { - try { - PreparedStatement ps = db.prepareStatement( - "UPDATE tickets SET " + field - + "=? WHERE id=?"); - if (value instanceof Integer) - ps.setInt(1, (Integer) value); - else if (value instanceof Long) - ps.setLong(1, (Long) value); - else - ps.setString(1, - String.valueOf(value)); - ps.setInt(2, ticketId); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public Map getTicket( - int ticketId) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE id=?"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public List> - getTicketsByStatus(String status) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status=? " - + "ORDER BY create_time DESC"); - ps.setString(1, status); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public void addTicketReply(int ticketId, - String sender, String message) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT INTO ticket_replies " - + "(ticket_id, sender, " - + "message, create_time) " - + "VALUES (?,?,?,?)"); - ps.setInt(1, ticketId); - ps.setString(2, sender); - ps.setString(3, message); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - // 更新工单状态为已回复 - updateTicketStatus(ticketId, - "replied"); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public List> - getTicketReplies(int ticketId) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM ticket_replies " - + "WHERE ticket_id=? " - + "ORDER BY create_time ASC"); - ps.setInt(1, ticketId); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - /** - * 获取已回复但超时未互动的工单 - */ - public List> - getRepliedStaleTickets( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='replied' " - + "AND update_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByPlayer(String playerName) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE requester=? " - + "ORDER BY create_time DESC"); - ps.setString(1, playerName); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getTicketsByProvider(String provider) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE assigned_to=? " - + "AND (status='processing' " - + "OR status='replied') " - + "ORDER BY create_time DESC"); - ps.setString(1, provider); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public List> - getUnconfirmedCompleted( - long timeoutMs) { - List> list = - new ArrayList<>(); - try { - long cutoff = - System.currentTimeMillis() - - timeoutMs; - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM tickets " - + "WHERE status='completed' " - + "AND admin_confirmed=0 " - + "AND complete_time < ?"); - ps.setLong(1, cutoff); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - // ==================== 服务商操作 ==================== - - public void addServiceProvider(String name, - String role) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO " - + "service_providers " - + "(player_name, role, " - + "active, join_time) " - + "VALUES (?,?,1,?)"); - ps.setString(1, name); - ps.setString(2, role); - ps.setLong(3, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void removeServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM service_providers " - + "WHERE player_name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public boolean isServiceProvider( - String name) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM service_providers " - + "WHERE player_name=? " - + "AND active=1"); - ps.setString(1, name); - boolean r = ps.executeQuery().next(); - ps.close(); - return r; - } catch (SQLException e) { - return false; - } - } - - public List> - getAllServiceProviders() { - List> list = - new ArrayList<>(); - try { - Statement st = db.createStatement(); - ResultSet rs = st.executeQuery( - "SELECT * FROM service_providers " - + "WHERE active=1"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs.getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Connection getDb() { - return db; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/EmailManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/EmailManager.java deleted file mode 100644 index b6906e5..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/EmailManager.java +++ /dev/null @@ -1,284 +0,0 @@ -package Sdf1_login; - -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.Socket; -import java.util.Base64; -import java.util.logging.Logger; - -public class EmailManager { - - private final ConfigManager config; - private final Logger logger; - - public EmailManager(ConfigManager config) { - this.config = config; - this.logger = Logger.getLogger("Sdf1_login"); - } - - public boolean isConfigured() { - String host = config.getSmtp("smtp地址"); - String user = config.getSmtp("smtp账号"); - String pass = config.getSmtp("smtp密码"); - return host != null && !host.isEmpty() - && user != null && !user.isEmpty() - && pass != null && !pass.isEmpty(); - } - - public boolean sendVerifyCode(String to, - String code) { - if (!isConfigured()) { - logger.warning( - "[Sdf1_login] SMTP未配置," - + "无法发送验证码"); - return false; - } - logger.info("[Sdf1_login] 发送验证码到 " + to); - return sendEmailSync(to, - "[Sdf1_login] 验证码", - "您的验证码: " + code - + "\n5分钟内有效。\n" - + "如非本人操作请忽略。"); - } - - public boolean sendTempPassword(String to, - String player, String tempPwd) { - if (!isConfigured()) { - logger.warning( - "[Sdf1_login] SMTP未配置"); - return false; - } - return sendEmailSync(to, - "[Sdf1_login] 临时密码", - "玩家 " + player - + " 的临时密码: " + tempPwd - + "\n请尽快登录并修改密码!"); - } - - public boolean sendBody(String to, String subject, String body) { - if (!isConfigured()) { - logger.warning("[Sdf1_login] SMTP未配置"); - return false; - } - return sendEmailSync(to, subject, body); - } - - - private boolean sendEmailSync(String to, - String subject, String body) { - final boolean[] result = {false}; - Thread t = new Thread(() -> { - try { - result[0] = sendEmail( - to, subject, body); - } catch (Exception e) { - logger.severe( - "[Sdf1_login] 邮件线程异常: " - + e.getMessage()); - } - }, "Sdf1_login-SMTP"); - t.setDaemon(true); - t.start(); - try { - t.join(15000); - } catch (InterruptedException ignored) { - } - if (t.isAlive()) { - t.interrupt(); - logger.severe( - "[Sdf1_login] SMTP超时(15秒)"); - return false; - } - return result[0]; - } - - private boolean sendEmail(String to, - String subject, String body) { - String host = config.getSmtp("smtp地址"); - String portStr = config.getSmtp("smtp端口"); - String user = config.getSmtp("smtp账号"); - String pass = config.getSmtp("smtp密码"); - String fromName = config.getSmtp( - "发件人名称"); - - int port; - try { - port = Integer.parseInt(portStr); - } catch (Exception e) { - port = 465; - } - - try { - String encSubject = "=?UTF-8?B?" - + Base64.getEncoder() - .encodeToString( - subject.getBytes("UTF-8")) - + "?="; - String encBody = Base64.getEncoder() - .encodeToString( - body.getBytes("UTF-8")); - - if (port == 465) { - SSLSocketFactory factory = - (SSLSocketFactory) - SSLSocketFactory - .getDefault(); - SSLSocket socket = (SSLSocket) - factory.createSocket( - host, port); - socket.setSoTimeout(10000); - doSmtp(socket, host, user, pass, - to, encSubject, encBody, - fromName); - socket.close(); - } else { - Socket plainSocket = - new Socket(host, port); - plainSocket.setSoTimeout(10000); - BufferedReader preReader = - new BufferedReader( - new InputStreamReader( - plainSocket - .getInputStream(), - "UTF-8")); - PrintWriter preWriter = - new PrintWriter( - plainSocket - .getOutputStream(), - true); - checkResponse(preReader, "220"); - sendLine(preWriter, - "EHLO " + host); - checkResponse(preReader, "250"); - sendLine(preWriter, "STARTTLS"); - checkResponse(preReader, "220"); - SSLSocketFactory factory = - (SSLSocketFactory) - SSLSocketFactory - .getDefault(); - SSLSocket sslSocket = (SSLSocket) - factory.createSocket( - plainSocket, host, - port, true); - sslSocket.startHandshake(); - doSmtp(sslSocket, host, user, pass, - to, encSubject, encBody, - fromName); - sslSocket.close(); - } - logger.info( - "[Sdf1_login] 邮件发送成功 -> " - + to); - return true; - } catch (Exception e) { - logger.severe( - "[Sdf1_login] 邮件异常: " - + e.getClass() - .getSimpleName() - + ": " + e.getMessage()); - if (e.getCause() != null) { - logger.severe( - "[Sdf1_login] 根因: " - + e.getCause() - .getMessage()); - } - return false; - } - } - - private void doSmtp(SSLSocket socket, - String host, String user, String pass, - String to, String subject, String body, - String fromName) throws Exception { - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - socket.getInputStream(), - "UTF-8")); - OutputStream rawOut = - socket.getOutputStream(); - PrintWriter writer = - new PrintWriter( - new java.io.BufferedOutputStream( - rawOut), true); - - checkResponse(reader, "220"); - sendLine(writer, "EHLO " + host); - checkResponse(reader, "250"); - sendLine(writer, "AUTH LOGIN"); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder() - .encodeToString( - user.getBytes("UTF-8"))); - checkResponse(reader, "334"); - sendLine(writer, Base64.getEncoder() - .encodeToString( - pass.getBytes("UTF-8"))); - checkResponse(reader, "235"); - sendLine(writer, - "MAIL FROM:<" + user + ">"); - checkResponse(reader, "250"); - sendLine(writer, - "RCPT TO:<" + to + ">"); - checkResponse(reader, "250"); - sendLine(writer, "DATA"); - checkResponse(reader, "354"); - sendLine(writer, - "From: " + fromName - + " <" + user + ">"); - sendLine(writer, - "To: <" + to + ">"); - sendLine(writer, - "Reply-To: <" + user + ">"); - sendLine(writer, - "Subject: " + subject); - sendLine(writer, "MIME-Version: 1.0"); - sendLine(writer, - "Content-Type: text/plain; " - + "charset=UTF-8"); - sendLine(writer, - "Content-Transfer-Encoding: " - + "base64"); - sendLine(writer, ""); - sendLine(writer, body); - sendLine(writer, "."); - checkResponse(reader, "250"); - sendLine(writer, "QUIT"); - } - - private void sendLine(PrintWriter writer, - String line) { - writer.print(line + "\r\n"); - writer.flush(); - } - - private void checkResponse( - BufferedReader reader, - String expected) throws Exception { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - if (line.length() >= 4 - && Character.isDigit( - line.charAt(0)) - && line.charAt(3) == ' ') - break; - } - String resp = sb.toString().trim(); - if (!resp.startsWith(expected)) { - if (resp.startsWith("535")) - logger.severe( - "[Sdf1_login] SMTP 535 " - + "认证失败"); - throw new Exception( - "SMTP响应异常: 期望 " - + expected - + ",实际: " + resp); - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/GUIManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/GUIManager.java deleted file mode 100644 index cbc7102..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/GUIManager.java +++ /dev/null @@ -1,2681 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.*; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - - - - -import java.util.*; - -public class GUIManager implements Listener { - - private final Main plugin; - - public static final String T_MAIN = - "§6§l服务器菜单"; - public static final String T_MORE = - "§6§l更多功能"; - public static final String T_EDITOR = - "§6§l编辑菜单项"; - public static final String T_BIOME = - "§b§l目标群系传送"; - public static final String T_MY_INFO = - "§6§l我的信息"; - public static final String T_GIFT_STAGES = - "§6§l礼包阶段"; - public static final String T_ADMIN = - "§6§l管理员面板"; - public static final String T_INVITE = - "§6§l邀请数据"; - public static final String T_USER_MGMT = - "§6§l用户管理"; - public static final String T_TASK_CENTER = - "§6§l任务中心"; - private final Map userMgmtPages = new HashMap<>(); - private static final int UMGMT_PAGE_SIZE = 45; - - - - public GUIManager(Main plugin) { - this.plugin = plugin; - } - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_MAIN); -// ===== 自定义菜单项 ===== - if (plugin.getMenu() != null) { - List customs = - plugin.getMenu().getItems(); - - // 已占用的槽位 - Set occupied = new HashSet<>(); - occupied.add(10); - occupied.add(12); - occupied.add(14); - occupied.add(16); - occupied.add(19); - occupied.add(21); - occupied.add(23); - occupied.add(25); - occupied.add(29); - occupied.add(31); - occupied.add(32); - occupied.add(34); - - // 自定义区域:第1排(0-8) + 第5排(36-44) + 第6排(45-53) - int[] customArea = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, - 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 52, 53 - }; - - int autoIdx = 0; - for (int ci = 0; - ci < customs.size() && ci < 27; ci++) { - MenuManager.MenuItem mi = - customs.get(ci); - - // 权限过滤 - if ("OP".equals(mi.permType) - && !p.isOp() - && !isAdmin(p)) continue; - - int targetSlot = -1; - - // 玩家指定了格子? - if (mi.slot != null) { - targetSlot = findBestSlot( - mi.slot - 1, occupied); - } - - // 未指定或冲突 → 自动排队 - if (targetSlot < 0) { - while (autoIdx < customArea.length) { - int s = customArea[autoIdx++]; - if (!occupied.contains(s)) { - targetSlot = s; - break; - } - } - } - - if (targetSlot < 0 - || targetSlot >= 54) continue; - occupied.add(targetSlot); - - // 放置物品 - ItemStack icon = - mi.icon.clone(); - ItemMeta im = - icon.getItemMeta(); - im.setDisplayName( - "§e§l" + mi.title); - im.setLore(java.util.Arrays.asList( - "§7指令: §f" - + mi.command, - "§7权限: §f" - + mi.permType, - "", - "§a左键执行")); - icon.setItemMeta(im); - g.setItem(targetSlot, icon); - } - } - -// 背景填充 - fillBg(g); - p.openInventory(g); - - // ===== Row1(9-17): 核心功能 ===== - g.setItem(10, mkItem(Material.PLAYER_HEAD, - "§a§l我的信息")); - g.setItem(12, mkItem(Material.PAPER, - "§b§l邀请数据")); - g.setItem(14, mkItem(Material.EMERALD, - "§d§l积分商城")); - g.setItem(16, mkItem(Material.COMPASS, - "§b§l群系传送", - "§7传送到指定群系")); - - // ===== Row2(18-26): 日常功能 ===== - g.setItem(19, mkItem(Material.BOOK, - "§6工单系统")); - g.setItem(21, mkItem(Material.EMERALD_BLOCK, - "§a§l每日签到", - "§7签到获取积分和惊喜奖励")); - g.setItem(23, mkItem(Material.ENDER_CHEST, - "§6§l任务中心")); - if (p.isOp() || isAdmin(p)) { - g.setItem(25, mkItem(Material.HOPPER, - "§6§l垃圾回收站", - "§7查看和取回清理的物品")); - - - } - // ===== Row3(27-35): 管理功能 ===== - // ★ 商店 - 所有人都能看 ★ - g.setItem(27, mkItem(Material.CHEST, - "§6§l商店", - "§7浏览、购买、出售物品", - "§7货币: 债券")); - - if (isAdmin(p)) { - g.setItem(29, mkItem( - Material.REDSTONE_BLOCK, - "§c§l管理员面板")); - } - if (p.isOp()) { - g.setItem(31, mkItem(Material.ECHO_SHARD, - "§c§l管理菜单", - "§7添加、编辑、删除菜单项")); - } - // 在 openMain 方法中,Row3 区域加: - - // ★ 余额查询 - 所有人都能看 ★ - double myBal = 0; - try { - var reg = plugin.getServer() - .getServicesManager() - .getRegistration( - net.milkbowl.vault.economy - .Economy.class); - if (reg != null) - myBal = reg.getProvider() - .getBalance(p); - } catch (Exception ignored) {} - int myBonds = plugin.getBonds().getBonds(p.getName()); - g.setItem(32, mkItem(Material.EMERALD, - "§b§l我的钱包", - "§7查看债券余额与流水记录")); - -// ★ 余额操作 - 仅OP和tag玩家 ★ - if (isAdmin(p)) { - g.setItem(34, mkItem(Material.GOLD_INGOT, - "§6§l余额操作", - "§7给钱、扣钱")); - } - - - - // ===== Row0(0-8): 自定义菜单项 ===== - if (plugin.getMenu() != null) { - List customs = - plugin.getMenu().getItems(); - for (int i = 0; - i < customs.size() && i < 9; - i++) { - MenuManager.MenuItem mi = - customs.get(i); - if ("OP".equals(mi.permType) - && !p.isOp() - && !isAdmin(p)) continue; - ItemStack icon = - mi.icon.clone(); - ItemMeta im = - icon.getItemMeta(); - im.setDisplayName( - "§e§l" + mi.title); - im.setLore(Arrays.asList( - "§7指令: §f" - + mi.command, - "§7权限: §f" - + mi.permType, - "", - "§a左键执行")); - icon.setItemMeta(im); - g.setItem(i, icon); - } - } - - // 背景最后填充(不覆盖物品) - fillBg(g); - p.openInventory(g); - } - private int findBestSlot( - int requested, - Set occupied) { - // 目标位空闲直接用 - if (requested >= 0 && requested < 54 - && !occupied.contains(requested)) { - return requested; - } - // BFS上下左右找最近空位 - int row = requested / 9; - int col = requested % 9; - boolean[][] visited = - new boolean[6][9]; - java.util.Queue queue = - new java.util.LinkedList<>(); - queue.add(new int[]{row, col}); - visited[row][col] = true; - int[][] dirs = { - {-1, 0}, {1, 0}, - {0, -1}, {0, 1}}; - while (!queue.isEmpty()) { - int[] cur = queue.poll(); - for (int[] d : dirs) { - int nr = cur[0] + d[0]; - int nc = cur[1] + d[1]; - if (nr >= 0 && nr < 6 - && nc >= 0 && nc < 9 - && !visited[nr][nc]) { - visited[nr][nc] = true; - int s = nr * 9 + nc; - if (!occupied.contains(s)) { - return s; - } - queue.add(new int[]{nr, nc}); - } - } - } - return -1; - } - - public void openMenuManager(Player p) { - List list = - plugin.getMenu().getItems(); - Inventory g = Bukkit.createInventory( - null, 27, - "§c§l菜单管理"); - fillBg(g); - - for (int i = 0; - i < list.size() && i < 9; i++) { - MenuManager.MenuItem mi = list.get(i); - g.setItem(i, mkItem( - mi.icon.getType(), - "§e" + mi.title, - "§7指令: " + mi.command, - "§7权限: " + mi.permType, - "", - "§a左键编辑", - "§7右键切换权限")); - } - - g.setItem(22, mkItem(Material.LIME_WOOL, - "§a§l添加菜单项")); - - g.setItem(26, mkItem(Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - - public void openBalanceOps(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§d§l余额操作"); - fillBg(g); - - net.milkbowl.vault.economy.Economy eco = null; - try { - var reg = plugin.getServer().getServicesManager() - .getRegistration(net.milkbowl.vault.economy.Economy.class); - if (reg != null) eco = reg.getProvider(); - } catch (Exception ignored) {} - - int slot = 0; - for (Player op : Bukkit.getOnlinePlayers()) { - if (slot >= 45) break; - double bal = 0; - if (eco != null) bal = eco.getBalance(op); - boolean isSelf = op.getName().equals(p.getName()); - int bondBal = plugin.getBonds().getBonds(op.getName()); - g.setItem(slot, mkItem( - isSelf ? Material.EMERALD_BLOCK : Material.PLAYER_HEAD, - "§e" + op.getName(), - "§7金币: §a$" + String.format("%.2f", bal), - "§7债券: §e" + bondBal + " §6枚", - "", - "§a左键经济(±) §6右键债券(±)")); - slot++; - } - - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - p.openInventory(g); - } - - public void openShop(Player p) { - p.closeInventory(); - plugin.getShopManager().openShopMain(p); - } - - public void openTicket(Player p) { - p.closeInventory(); - p.performCommand("sdf1_login ticket"); - } - @EventHandler - public void onInvClose( - - InventoryCloseEvent e) { - if (!(e.getPlayer() instanceof Player)) - return; - if (!(e.getPlayer() instanceof Player)) return; - Player p = (Player) e.getPlayer(); - Player p3 = (Player) e.getPlayer(); - String t = e.getView().getTitle(); - if (!(e.getPlayer() instanceof Player)) return; - Player p2 = (Player) e.getPlayer(); - - if (!t.equals(T_EDITOR)) return; - - // ★ 编辑器关闭时清理空项 ★ - List list = - plugin.getMenu().getItems(); - if (!list.isEmpty()) { - MenuManager.MenuItem last = - list.get(list.size() - 1); - if ((last.command == null - || last.command.trim().isEmpty() - || last.command.trim() - .equals("/")) - && "新菜单项" - .equals(last.title)) { - list.remove(list.size() - 1); - } - } - } - - public void openSubMenu( - Player p, String fileName) { - String menuTitle = - "§6§l" + fileName - .replace(".txt", ""); - Inventory g = Bukkit.createInventory( - null, 54, menuTitle); - fillBg(g); - - java.io.File f = new java.io.File( - plugin.getDataFolder(), fileName); - if (!f.exists()) { - p.sendMessage("§c文件不存在: " - + fileName); - return; - } - try { - List lines = - java.nio.file.Files - .readAllLines( - f.toPath(), - java.nio.charset - .StandardCharsets - .UTF_8); - List items = - new ArrayList<>(); - StringBuilder buf = - new StringBuilder(); - boolean inItem = false; - - for (String raw : lines) { - String l = raw.trim(); - if (l.startsWith("#") - || l.startsWith("//")) - continue; - if (l.contains("")) { - inHtml = false; - l = l.substring( - l.indexOf("-->") + 3) - .trim(); - } else { - plugin.getLogger().info( - "[Menu] L" + lineNum - + ": [HTML注释中]"); - continue; - } - } - - // ===== 第3优先:整行注释(最高裁决) ===== - // 以 # 或 // 开头的行,直接跳过 - // 不管后面包含什么 /* "); - pw.println(); - pw.println("{"); - pw.println(" 标题:主城传送"); - pw.println(" 指令:/spawn"); - pw.println(" 权限类型:玩家"); - pw.println(" 图标:COMPASS"); - pw.println("}"); - pw.println(); - pw.println("{"); - pw.println(" 标题:随机传送"); - pw.println(" 指令:/rtp"); - pw.println(" 权限类型:玩家"); - pw.println(" 图标:ENDER_PEARL"); - pw.println("}"); - } catch (Exception ignored) { - } - } - - // ===== 保存 ===== - public void saveMenu() { - File f = new File( - plugin.getDataFolder(), - "菜单.txt"); - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8))) { - pw.println("# 服务器菜单"); - pw.println(); - for (MenuItem mi : items) { - pw.println("{"); - pw.println(" 标题:" - + mi.title); - pw.println(" 指令:" - + mi.command); - pw.println(" 权限类型:" - + mi.permType); - // ★ 保存实际材质名 ★ - pw.println(" 图标:" - + mi.icon.getType().name()); - pw.println("}"); - pw.println(); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Menu] 保存失败: " - + e.getMessage()); - } - } - - // ===== 聊天编辑 ===== - public boolean isEditing(String name) { - return chatWait.contains(name); - } - - public void editTitle( - Player p, int idx) { - chatIdx.put(p.getName(), idx); - chatWait.add(p.getName()); - chatField.put(p.getName(), "title"); - p.closeInventory(); - p.sendMessage("§e请输入菜单标题:"); - } - - public void editCommand( - Player p, int idx) { - chatIdx.put(p.getName(), idx); - chatWait.add(p.getName()); - chatField.put(p.getName(), "command"); - p.closeInventory(); - p.sendMessage("§e请输入指令" - + "(自动补全/):"); - } - - public void onChat(Player p, String input) { - String name = p.getName(); - if (!chatWait.remove(name)) return; - - String field = chatField.remove(name); - Integer idx = chatIdx.get(name); - if (idx == null || idx >= items.size()) return; - - MenuItem mi = items.get(idx); - - if ("title".equals(field)) { - mi.title = input; - p.sendMessage("§a标题: " + input); - } else if ("command".equals(field)) { - if (!input.startsWith("/")) - input = "/" + input; - mi.command = input; - p.sendMessage("§a指令: " + input); - } - - Bukkit.getScheduler().runTask(plugin, () -> - plugin.gui.openEditor(p, idx)); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/OrderManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/OrderManager.java deleted file mode 100644 index 9dcb625..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/OrderManager.java +++ /dev/null @@ -1,1345 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.logging.Level; - -public class OrderManager implements Listener { - private final Main plugin; - private final List orders = - Collections.synchronizedList(new ArrayList<>()); - private final Map userViewPage = new HashMap<>(); - private final Map adminViewPage = new HashMap<>(); - private final Map adminViewTarget = new HashMap<>(); - private final Set partialRefundListening = new HashSet<>(); - private final Map partialRefundTarget = new HashMap<>(); - private final Map receiptBuilderSection = new HashMap<>(); - private String tplHeader = ""; - private String tplItemLine = "{品名} x{数量} {原价}→{执行价} 小计{小计}枚"; - private String tplDiscountLine = "优惠: -{优惠额}枚 ({优惠类型})"; - private String tplPackLine = "打包: +{打包费}枚 ({打包颜色})"; - private String tplSummary = "原价{原价合计} → 实付{实付}"; - private String tplFooter = "感谢惠顾,欢迎再来!"; - private static final long RETENTION_30D = 30L * 24 * 60 * 60 * 1000; - private static final long SELF_REFUND_WINDOW = 5 * 60 * 1000; - private static final int PAGE_SIZE = 21; - private static final int[] SLOTS = { - 10,11,12,13,14,15,16,19,20,21,22,23,24,25,28,29,30,31,32,33,34}; - // ===== 字段 ===== - private final Set shulkerUsedOrders = - Collections.synchronizedSet(new HashSet<>()); - // ===== 待审批的退货退款 ===== - private final Map pendingReturns = - new HashMap<>(); - // ===== 退货申请确认 ===== - private final Map returnConfirmWaiting = - new HashMap<>(); // uuid -> orderId - private static final int RETURN_CONFIRM_TIMEOUT = 600; // 30秒 - - - private static class PendingReturn { - long orderId; - UUID playerUUID; - String playerName; - int totalPaid; - long createTime; - String packColor; - String packType; - List items; - - PendingReturn(OrderRecord r) { - this.orderId = r.orderId; - this.playerUUID = r.uuid; - this.playerName = r.player; - this.totalPaid = r.totalPaid; - this.createTime = System.currentTimeMillis(); - this.packColor = r.packColor; - this.packType = r.packType; - this.items = new ArrayList<>(r.items); - } - } - - - public static class OrderItem { - public String name; public String mat; - public int originalPrice; public int finalPrice; public int qty; - public OrderItem(String n,String m,int op,int fp,int q){ - name=n;mat=m;originalPrice=op;finalPrice=fp;qty=q;} - public int subtotal(){return finalPrice*qty;} - public int originalSubtotal(){return originalPrice*qty;} - } - public static class OrderRecord { - public long orderId; public UUID uuid; public String player; - public List items; public int totalOriginal; public int totalPaid; - public int discount; public String discountType; public String couponCode; - public int packFee; public String packType; public String packColor; - public long timestamp; public long printTime; public int status; - public int refundedAmount; public String refundType; - } - - public OrderManager(Main plugin) { - this.plugin = plugin; - loadReceiptConfig(); - loadOrders(); - startCleanupTask(); - Bukkit.getPluginManager().registerEvents(this, plugin); - } - public List getOrders() { return orders; } - - // ===== 小票模板 ===== - private void loadReceiptConfig() { - File f = new File(plugin.getDataFolder(), "小票设置.txt"); - if (!f.exists()) createDefaultReceiptConfig(f); - Map m = loadMap(f); - tplHeader = m.getOrDefault("头部", tplHeader); - tplItemLine = m.getOrDefault("商品行", tplItemLine); - tplDiscountLine = m.getOrDefault("优惠行", tplDiscountLine); - tplPackLine = m.getOrDefault("打包行", tplPackLine); - tplSummary = m.getOrDefault("汇总", tplSummary); - tplFooter = m.getOrDefault("页脚", tplFooter); - } - private void saveReceiptConfig() { - File f = new File(plugin.getDataFolder(), "小票设置.txt"); - List L = new ArrayList<>(); - L.add("# 小票模板配置"); - L.add("头部=" + tplHeader); - L.add("商品行=" + tplItemLine); - L.add("优惠行=" + tplDiscountLine); - L.add("打包行=" + tplPackLine); - L.add("汇总=" + tplSummary); - L.add("页脚=" + tplFooter); - writeLines(f, L); - } - private void createDefaultReceiptConfig(File f) { - List L = new ArrayList<>(); - L.add("# 小票模板配置"); - L.add("头部=§6§l{商店名}§r\n购物小票\n订单#{订单号} {订单时间}\n玩家: {玩家}"); - L.add("商品行=§e{品名}§r x{数量} {原价}→{执行价} 小计{小计}枚"); - L.add("优惠行=优惠: §a-{优惠额}枚§r ({优惠类型}{优惠券码})"); - L.add("打包行=打包: §c+{打包费}枚§r ({打包颜色})"); - L.add("汇总=原价{原价合计}枚 → 实付§6{实付}枚§r 余额{余额}枚"); - L.add("页脚=感谢惠顾,欢迎再来!"); - writeLines(f, L); - } - - // ===== 订单记录 ===== - public void recordOrder(Player p, List items, - int totalOriginal, int totalPaid, int discount, - String discountType, String couponCode, int packFee, - String packType, String packColor) { - OrderRecord r = new OrderRecord(); - r.orderId = System.currentTimeMillis(); - r.uuid = p.getUniqueId(); r.player = p.getName(); - r.items = items; r.totalOriginal = totalOriginal; - r.totalPaid = totalPaid; r.discount = discount; - r.discountType = discountType; r.couponCode = couponCode; - r.packFee = packFee; r.packType = packType; - r.packColor = packColor; r.timestamp = r.orderId; - r.printTime = 0; r.status = 0; r.refundedAmount = 0; - r.refundType = null; - orders.add(r); saveOrders(); - } - @EventHandler - public void onBlockPlace(org.bukkit.event.block.BlockPlaceEvent e) { - ItemStack item = e.getItemInHand(); - if (item == null) return; - if (!item.getType().name().contains("SHULKER")) return; - - ItemMeta meta = item.getItemMeta(); - if (meta == null || meta.getLore() == null) return; - - for (String line : meta.getLore()) { - if (line.startsWith("§8#")) { - try { - long orderId = Long.parseLong( - line.replace("§8#", "").trim()); - shulkerUsedOrders.add(orderId); - } catch (NumberFormatException ignored) {} - } - } - } - - // ===== 小票聊天栏 ===== - /** - * 聊天栏打印小票(给定参数 Player + OrderRecord) - * 不要改动方法签名 - */ - public void sendReceiptChat(Player p, OrderRecord r) { - if (p == null || r == null) return; - - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - p.sendMessage("§7§m "); - p.sendMessage("§6§l======== 商店购物小票 ========"); - - // 退款标记 - String statusTag = getStatusTag(r); - if (!statusTag.isEmpty()) { - p.sendMessage(statusTag); - } - - p.sendMessage("§7订单号: §f#" + r.orderId); - p.sendMessage("§7下单时间: §f" - + df.format(new Date(r.timestamp))); - p.sendMessage("§7玩家: §f" + r.player); - p.sendMessage("§7------------------------------"); - - for (OrderItem it : r.items) { - String price; - if (it.originalPrice != it.finalPrice) { - price = "§f" + it.originalPrice - + "§7→§6" + it.finalPrice; - } else { - price = "§6" + it.finalPrice; - } - p.sendMessage("§7 §e" + it.name - + " §7x" + it.qty - + " " + price + "枚" - + " §6小计" + it.subtotal() + "枚"); - } - - p.sendMessage("§7------------------------------"); - p.sendMessage("§7原价: §f" + r.totalOriginal + "枚"); - - if (r.discount > 0) { - int pct = (r.totalOriginal > 0) - ? (r.discount * 100 / r.totalOriginal) - : 0; - String dt = "coupon".equals(r.discountType) - ? "§7(优惠券 -" + pct + "%)" - : "eco".equals(r.discountType) - ? "§7(环保 -" + pct + "%)" - : "§7(-" + pct + "%)"; - p.sendMessage("§7优惠: §a-" + r.discount - + "枚 " + dt); - } - - if (r.packFee > 0) { - String cn = r.packColor != null - ? r.packColor - .replace("_SHULKER_BOX", "") - .replace("SHULKER_BOX", "原色") - : "默认"; - p.sendMessage("§7打包: §c+" + r.packFee - + "枚 §7(" + cn + "潜影盒)"); - } else { - p.sendMessage("§7包装: 直接放入背包"); - } - - p.sendMessage("§7------------------------------"); - p.sendMessage("§7实付: §6" + r.totalPaid + "枚"); - - int bal = 0; - try { - bal = plugin.getBonds().getBonds(r.player); - } catch (Exception ignored) {} - p.sendMessage("§7余额: §a" + bal + "枚"); - - // 退款标记 - if (!statusTag.isEmpty()) { - p.sendMessage(statusTag); - } - - p.sendMessage("§6§l=============================="); - r.printTime = System.currentTimeMillis(); - } - - /** - * 生成书本小票内容 - */ - private String buildReceiptText(OrderRecord r) { - String statusTag = getStatusTag(r); - SimpleDateFormat df = - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - StringBuilder sb = new StringBuilder(); - - sb.append("§6§l===== 商店小票 =====\n"); - - // 退款标记 - String tag = getStatusTag(r); - if (!tag.isEmpty()) sb.append(tag).append("\n"); - - sb.append("订单号: #").append(r.orderId).append("\n"); - sb.append("时间: ").append( - df.format(new Date(r.timestamp))).append("\n"); - sb.append("玩家: ").append(r.player).append("\n"); - sb.append("------------------------------\n"); - - for (OrderItem it : r.items) { - String pr = (it.originalPrice != it.finalPrice) - ? it.originalPrice + "->" + it.finalPrice - : String.valueOf(it.finalPrice); - sb.append(it.name).append(" x").append(it.qty) - .append(" ").append(pr).append("枚") - .append(" 小计").append(it.subtotal()) - .append("枚\n"); - } - - sb.append("------------------------------\n"); - sb.append("原价: ").append(r.totalOriginal) - .append("枚\n"); - - if (r.discount > 0) { - int pct = (r.totalOriginal > 0) - ? (r.discount * 100 / r.totalOriginal) - : 0; - String dt = "coupon".equals(r.discountType) - ? "优惠券(-" + pct + "%)" - : "环保(-" + pct + "%)"; - sb.append("优惠: -").append(r.discount) - .append("枚 ").append(dt).append("\n"); - } - - if (r.packFee > 0) { - sb.append("打包: +").append(r.packFee) - .append("枚\n"); - } - - sb.append("------------------------------\n"); - sb.append("实付: ").append(r.totalPaid).append("枚\n"); - - int bal = 0; - try { - bal = plugin.getBonds().getBonds(r.player); - } catch (Exception ignored) {} - sb.append("余额: ").append(bal).append("枚\n"); - - // 退款标记 - if (!statusTag.isEmpty()) sb.append("\n").append(tag).append("\n"); - - return sb.toString(); - } - - /** - * 退款状态标记文本 - */ - private String getStatusTag(OrderRecord r) { - switch (r.status) { - case 2: - return "§c§l<<<<<<< 已全额退款 >>>>>>>" - + "\n§c退款金额: " - + r.refundedAmount + "枚"; - case 3: - return "§c§l<<<<<<< 已部分退款 >>>>>>>" - + "\n§c退款金额: " - + r.refundedAmount + "枚"; - case 4: - return "§c§l<<<<<<< 退款申请已拒绝 >>>>>>>"; - default: - return ""; - } - } - - /** - * 创建小票书 - */ - public ItemStack createReceiptBook(OrderRecord r) { - String text = buildReceiptText(r); - String[] lines = text.split("\n"); - - ItemStack book = new ItemStack(Material.WRITTEN_BOOK); - BookMeta bm = (BookMeta) book.getItemMeta(); - bm.setTitle("购物小票 #" + r.orderId); - bm.setAuthor("商店"); - - StringBuilder page = new StringBuilder(); - int lineCount = 0; - for (String line : lines) { - // ★ 保留颜色代码 - page.append(line).append("\n"); - lineCount++; - if (lineCount >= 13) { - bm.addPage(page.toString().trim()); - page = new StringBuilder(); - lineCount = 0; - } - } - if (page.length() > 0) { - bm.addPage(page.toString().trim()); - } - - book.setItemMeta(bm); - return book; - } - - // ===== 退款工具 ===== - private boolean withinSelfRefundWindow(OrderRecord r) { return System.currentTimeMillis() - r.timestamp < SELF_REFUND_WINDOW; } - private int countDirectItems(Player p, String matName) { - Material m = Material.matchMaterial(matName); if (m == null) return 0; - int c = 0; for (ItemStack is : p.getInventory().getContents()) if (is != null && is.getType() == m) c += is.getAmount(); - return c; - } - private int countShulkers(Player p, String matName) { - Material m = Material.matchMaterial(matName); if (m == null) return 0; - int c = 0; for (ItemStack is : p.getInventory().getContents()) if (is != null && is.getType() == m) c += is.getAmount(); - return c; - } - private void removeItems(Player p, String matName, int amt) { - Material m = Material.matchMaterial(matName); if (m != null) p.getInventory().removeItem(new ItemStack(m, amt)); - } - private boolean removeOneShulker(Player p, - String materialName) { - // 先尝试精确匹配 - Material mat = Material.matchMaterial(materialName); - if (mat != null) { - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - p.getInventory().clear(i); - return true; - } - } - } - // 匹配不到,移除任意一个潜影盒 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType().name() - .endsWith("_SHULKER_BOX")) { - p.getInventory().clear(i); - return true; - } - } - return false; - } - public String validateRefund(OrderRecord r, Player p) { - if (r.status != 0) - return "订单状态: " + getStatusName(r.status); - - String pt = r.packType != null ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // ★ 检查:潜影盒是否被放置过 - if (shulkerUsedOrders.contains(r.orderId)) { - return "§c潜影盒已被使用过,无法自助退款"; - } - - // ★ 检查:背包里是否有同一订单的潜影盒 - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is == null) continue; - if (!is.getType().name().contains("SHULKER")) - continue; - ItemMeta im = is.getItemMeta(); - if (im == null || im.getLore() == null) - continue; - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - return null; // 找到了,可退 - } - } - } - return "§c对应的潜影盒已不在背包中"; - } else { - // ★ 散装:检测物品数量 - for (OrderItem it : r.items) { - int have = countDirectItems(p, it.mat); - if (have < it.qty) - return "§c" + it.name + "不足(有" - + have + "/" + it.qty + ")"; - } - } - return null; - } - - - - private void notifyPlayer(UUID uuid, String msg) { Player p = Bukkit.getPlayer(uuid); if (p != null && p.isOnline()) p.sendMessage(msg); } - private void notifyAdmins(Player p, OrderRecord r) { - String m = "§e[订单] " + p.getName() + " 申请退款 #" + r.orderId + " §6" + r.totalPaid + "枚"; - for (Player a : Bukkit.getOnlinePlayers()) if (isAdmin(a) && !a.equals(p)) a.sendMessage(m); - } - private String getStatusName(int s) { switch(s){case 0:return "正常";case 1:return "申请中";case 2:return "已退";case 3:return "部分退";case 4:return "已拒绝";default:return "未知";} } - private boolean isAdmin(Player p) { - if (p.isOp()) return true; - try { ConfigManager cm = plugin.getConfigMgr(); if (cm == null) return false; return p.hasPermission("sdf1.admin") || p.hasPermission("sdf1." + cm.adminTag); } catch (Exception e) { return p.hasPermission("sdf1.admin"); } - } - - // ===== 订单中心 ===== - public void openOrderCenter(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l订单中心"); - g.setItem(13, mkItem(Material.BOOK, "§e§l购买记录", "§7查看历史订单", "", "§a点击进入")); - if (isAdmin(p)) { g.setItem(31, mkItem(Material.NETHER_STAR, "§c§l管理员面板", "§7在线玩家购物记录", "§7支持退款操作", "", "§a点击进入")); - g.setItem(33, mkItem(Material.CRAFTING_TABLE, "§e§l小票配置", "§7配置小票模板样式", "", "§a点击进入")); } - g.setItem(49, mkItem(Material.ARROW, "§7返回商店")); - p.openInventory(g); - } - - // ===== 购买记录 ===== - public void openMyOrders(Player p, int page) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l购买记录"); - List list = getPlayerAllOrders(p.getUniqueId()); - int total = Math.max(1, (int) Math.ceil((double) list.size() / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); - userViewPage.put(p.getUniqueId(), page); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, list.size()); - for (int i = start; i < end; i++) { - int si = i - start; if (si >= SLOTS.length) break; - OrderRecord r = list.get(i); - Material icon; String st; String act; - if (r.status != 0) { icon = r.status == 1 ? Material.PAPER : Material.BARRIER; st = "§7状态: " + getStatusName(r.status); act = "§b左键: 补打小票"; } - else if (withinSelfRefundWindow(r)) { icon = Material.EMERALD; st = "§7状态: §a可退款(5分钟内)"; act = "§b左键: 补打小票\n§a右键: 退款退货\n§eShift+右键: 仅退款"; } - else { icon = Material.BOOK; st = "§7状态: §7已过自助退款期"; act = "§b左键: 补打小票\n§e右键: 申请退款退货\n§6Shift+右键: 申请仅退款"; } - ItemStack is = new ItemStack(icon); ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e订单 #" + r.orderId); - List lore = new ArrayList<>(); lore.add("§7" + fmtDate(r.timestamp)); - for (OrderItem it : r.items) lore.add("§7 §e" + it.name + " §7x" + it.qty + " §6" + it.subtotal() + "枚"); - lore.add("§7─────────"); lore.add("§7原价: §f" + r.totalOriginal + "枚"); - if (r.discount > 0) lore.add("§7优惠: §a-" + r.discount + "枚"); - lore.add("§7实付: §6" + r.totalPaid + "枚"); lore.add(st); lore.add(""); - for (String line : act.split("\n")) lore.add(line); - im.setLore(lore); is.setItemMeta(im); - } - g.setItem(SLOTS[si], is); - } - if (list.isEmpty()) g.setItem(31, mkItem(Material.BARRIER, "§7暂无订单记录")); - addPageButtons(g, page, total); - p.openInventory(g); - } - - // ===== 管理员面板 ===== - public void openAdminPanel(Player p, int page) { - if (!isAdmin(p)) { p.sendMessage("§c权限不足"); return; } - Inventory g = Bukkit.createInventory(null, 54, "§c§l管理员订单面板"); - Player[] online = Bukkit.getOnlinePlayers().toArray(new Player[0]); - int total = Math.max(1, (int) Math.ceil((double) online.length / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); adminViewPage.put(p.getUniqueId(), page); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, online.length); - // ★ 替换 openAdminPanel 中的物品创建部分 - for (int i = start; i < end; i++) { - int si = i - start; - if (si >= SLOTS.length) break; - Player t = online[i]; - List tl = getPlayerOrdersWithin( - t.getUniqueId(), RETENTION_30D); - int ts = 0; - for (OrderRecord r : tl) ts += r.totalPaid; - - ItemStack is = new ItemStack(Material.PLAYER_HEAD); - org.bukkit.inventory.meta.SkullMeta sm = - (org.bukkit.inventory.meta.SkullMeta) - is.getItemMeta(); - if (sm != null) { - sm.setOwner(t.getName()); // ★ 设置头颅皮肤 - sm.setDisplayName("§e" + t.getName()); - sm.setLore(Arrays.asList( - "§7订单: §f" + tl.size() + "笔", - "§7消费: §6" + ts + "枚", - "", "§a点击进入")); - is.setItemMeta(sm); // ★ 这行之前缺失了 - } - g.setItem(SLOTS[si], is); - } - - if (online.length == 0) g.setItem(31, mkItem(Material.BARRIER, "§7无在线玩家")); - addPageButtons(g, page, total); p.openInventory(g); - } - - // ===== 管理员订单 ===== - public void openAdminOrders(Player p, UUID targetUUID, int page) { - Inventory g = Bukkit.createInventory(null, 54, "§c§l订单管理"); - String name = Bukkit.getOfflinePlayer(targetUUID).getName(); - List list = getPlayerOrdersWithin(targetUUID, RETENTION_30D); - int total = Math.max(1, (int) Math.ceil((double) list.size() / PAGE_SIZE)); - page = Math.max(0, Math.min(page, total - 1)); - adminViewPage.put(p.getUniqueId(), page); adminViewTarget.put(p.getUniqueId(), targetUUID); - int start = page * PAGE_SIZE; int end = Math.min(start + PAGE_SIZE, list.size()); - for (int i = start; i < end; i++) { - int si = i - start; if (si >= SLOTS.length) break; - OrderRecord r = list.get(i); - Material icon = r.status == 2 || r.status == 4 ? Material.BARRIER : r.status == 1 ? Material.PAPER : Material.BOOK; - ItemStack is = new ItemStack(icon); ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e订单 #" + r.orderId); - List lore = new ArrayList<>(); lore.add("§7" + fmtDate(r.timestamp)); - for (OrderItem it : r.items) lore.add("§7 §e" + it.name + " §7x" + it.qty + " §6" + it.subtotal() + "枚"); - lore.add("§7─────────"); lore.add("§7实付: §6" + r.totalPaid + "枚"); - if (r.status == 1) { lore.add("§7状态: §e申请中(" + nn(r.refundType) + ")"); lore.add(""); lore.add("§a右键: 批准"); lore.add("§c左键: 拒绝"); } - else if (r.status == 2) lore.add("§7已退: §a" + r.refundedAmount + "枚"); - else if (r.status == 3) lore.add("§7部分退: §a" + r.refundedAmount + "枚"); - else if (r.status == 4) lore.add("§7已拒绝"); - else { lore.add(""); lore.add("§a右键: 整单退款"); lore.add("§e左键: 部分退款(输入%)"); } - im.setLore(lore); is.setItemMeta(im); - } - g.setItem(SLOTS[si], is); - } - if (list.isEmpty()) g.setItem(31, mkItem(Material.BARRIER, "§7该玩家暂无订单")); - addPageButtons(g, page, total); - g.setItem(49, mkItem(org.bukkit.Material.PAPER, name + " §e" + (page + 1) + "/" + total)); - p.openInventory(g); - } - private void addPageButtons(Inventory g, int page, int total) { - g.setItem(48, page > 0 ? mkItem(Material.ARROW, "§7上一页") : mkItem(Material.BLACK_STAINED_GLASS_PANE, " ")); - if (g.getItem(49) == null) g.setItem(49, mkItem(Material.PAPER, "§e" + (page + 1) + "/" + total)); - g.setItem(50, page < total - 1 ? mkItem(Material.ARROW, "§7下一页") : mkItem(Material.BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(53, mkItem(Material.ARROW, "§7返回")); - } - - // ===== 小票配置 ===== - public void openReceiptConfig(Player p) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l小票配置"); - g.setItem(10, mkItem(Material.PAPER, "§e§l头部", "§7" + brief(tplHeader), "", "§a点击编辑")); - g.setItem(11, mkItem(Material.PAPER, "§e§l商品行", "§7" + brief(tplItemLine), "", "§a点击编辑")); - g.setItem(12, mkItem(Material.PAPER, "§e§l优惠行", "§7" + brief(tplDiscountLine), "", "§a点击编辑")); - g.setItem(14, mkItem(Material.PAPER, "§e§l打包行", "§7" + brief(tplPackLine), "", "§a点击编辑")); - g.setItem(15, mkItem(Material.PAPER, "§e§l汇总", "§7" + brief(tplSummary), "", "§a点击编辑")); - g.setItem(16, mkItem(Material.PAPER, "§e§l页脚", "§7" + brief(tplFooter), "", "§a点击编辑")); - g.setItem(22, mkItem(Material.BOOK, "§6§l预览(随机价格)", "§7预览小票效果")); - g.setItem(48, mkItem(Material.LIME_WOOL, "§a§l保存")); g.setItem(49, mkItem(Material.ARROW, "§7返回")); - g.setItem(50, mkItem(Material.RED_WOOL, "§c§l重置")); - p.openInventory(g); - } - public void openReceiptBuilder(Player p, String section) { - Inventory g = Bukkit.createInventory(null, 54, "§6§l编辑: " + sectionName(section)); - g.setItem(10, mkItem(Material.EMERALD, "§a{品名}")); g.setItem(11, mkItem(Material.EMERALD, "§a{原价}")); - g.setItem(12, mkItem(Material.EMERALD, "§a{执行价}")); g.setItem(13, mkItem(Material.EMERALD, "§a{数量}")); - g.setItem(14, mkItem(Material.EMERALD, "§a{小计}")); g.setItem(15, mkItem(Material.EMERALD, "§a{商店名}")); - g.setItem(16, mkItem(Material.EMERALD, "§a{玩家}")); g.setItem(19, mkItem(Material.DIAMOND, "§b{订单号}")); - g.setItem(20, mkItem(Material.DIAMOND, "§b{订单时间}")); g.setItem(21, mkItem(Material.DIAMOND, "§b{打印时间}")); - g.setItem(22, mkItem(Material.DIAMOND, "§b{日期}")); g.setItem(23, mkItem(Material.DIAMOND, "§b{时间}")); - g.setItem(24, mkItem(Material.DIAMOND, "§b{原价合计}")); g.setItem(25, mkItem(Material.DIAMOND, "§b{实付}")); - g.setItem(28, mkItem(Material.GOLD_INGOT, "§6{优惠额}")); g.setItem(29, mkItem(Material.GOLD_INGOT, "§6{优惠类型}")); - g.setItem(30, mkItem(Material.GOLD_INGOT, "§6{优惠券码}")); g.setItem(31, mkItem(Material.GOLD_INGOT, "§6{打包费}")); - g.setItem(32, mkItem(Material.GOLD_INGOT, "§6{打包颜色}")); g.setItem(33, mkItem(Material.GOLD_INGOT, "§6{余额}")); - g.setItem(37, mkItem(Material.STONE, "§f═══")); g.setItem(38, mkItem(Material.STONE, "§f───")); - g.setItem(39, mkItem(Material.STONE, "§f═════════════")); g.setItem(40, mkItem(Material.PAPER, "§e换行(\\n)")); - g.setItem(47, mkItem(Material.RED_STAINED_GLASS_PANE, "§c删末尾")); g.setItem(48, mkItem(Material.RED_WOOL, "§c清空")); - g.setItem(49, mkItem(Material.ARROW, "§7返回")); g.setItem(50, mkItem(Material.LIME_WOOL, "§a保存")); - g.setItem(51, mkItem(Material.BLUE_WOOL, "§9重置")); - p.openInventory(g); - } - // ===== 点击处理 ===== - public boolean handleCenterClick(Player p, int raw) { - if (raw == 13) { openMyOrders(p, 0); return true; } - if (raw == 31 && isAdmin(p)) { openAdminPanel(p, 0); return true; } - if (raw == 33 && isAdmin(p)) { openReceiptConfig(p); return true; } - if (raw == 49) { plugin.getShopManager().openShopMain(p); return true; } - return true; - } - public boolean handleMyOrdersClick(Player p, int raw, boolean left, boolean shift) { - List list = getPlayerAllOrders(p.getUniqueId()); - int page = userViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { openMyOrders(p, page - 1); return true; } - if (raw == 50) { openMyOrders(p, page + 1); return true; } - if (raw == 53) { openOrderCenter(p); return true; } - int si = slotIndex(raw); if (si < 0) return true; - int actual = si + page * PAGE_SIZE; if (actual < 0 || actual >= list.size()) return true; - OrderRecord r = list.get(actual); - if (left) { - try { ItemStack book = createReceiptBook(r); - HashMap ov = p.getInventory().addItem(book); - for (ItemStack d : ov.values()) p.getWorld().dropItemNaturally(p.getLocation(), d); - r.printTime = System.currentTimeMillis(); p.sendMessage("§a已补打小票书: #" + r.orderId); - } catch (Exception e) { p.sendMessage("§c补打失败"); } - } else { - if (r.status != 0) { - p.sendMessage("§c状态: " + getStatusName(r.status)); - } else if (withinSelfRefundWindow(r)) { - if (shift) { - // ★ Shift+右键:仅退款申请(需管理员审批) - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请,等待管理员审批"); - notifyAdmins(p, r); - } else { - // ★ 普通右键:5分钟内直接退(自动通过) - String reason = validateRefund(r, p); - if (reason != null) { - p.sendMessage("§c" + reason - + ",Shift+右键可申请仅退款"); - } else { - // 扣货 - String pt = r.packType != null - ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - if (isShulker) { - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory() - .getItem(i); - if (is != null && is.getType() - .name().contains("SHULKER")) { - p.getInventory().clear(i); - break; - } - } - } else { - for (OrderItem it : r.items) - removeItems(p, it.mat, it.qty); - } - // 退钱 - plugin.getBonds().addBonds( - r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - r.refundType = "auto_return"; - saveOrders(); - p.sendMessage("§a退款退货成功: §6" - + r.totalPaid + "枚"); - } - } - - - } else { - if (r.status != 0) { - p.sendMessage("§c状态: " - + getStatusName(r.status)); - } else if (withinSelfRefundWindow(r)) { - if (shift) { - // 仅退款申请 - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请," - + "等待管理员审批"); - notifyAdmins(p, r); - } else { - // 5分钟内退货退款 - String reason = validateRefund(r, p); - if (reason != null) { - // ★ 不满足条件:询问是否申请 - p.sendMessage("§c" + reason); - askReturnConfirm(p, r); - } else { - // 满足条件:自动退 - doAutoReturn(p, r); - } - } - } else { - if (shift) { - // 申请仅退款 - r.status = 1; - r.refundType = "refund_only_apply"; - saveOrders(); - p.sendMessage("§e已提交仅退款申请"); - notifyAdmins(p, r); - } else { - // ★ 申请退货退款:先检查条件 - String reason = validateRefund(r, p); - if (reason != null) { - p.sendMessage("§c" + reason); - } - // ★ 无论是否满足条件都询问 - askReturnConfirm(p, r); - } - } - } - } - if (left) { - // 已退款订单仍然允许补打小票,但小票内会显示退款标记 - try { - ItemStack book = createReceiptBook(r); - HashMap overflow = - p.getInventory().addItem(book); - for (ItemStack drop : overflow.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - r.printTime = System.currentTimeMillis(); - if (r.status != 0) { - p.sendMessage("§e已补打小票(含退款标记): #" - + r.orderId); - } else { - p.sendMessage("§a已补打小票书: #" - + r.orderId); - } - } catch (Exception e) { - p.sendMessage("§c补打失败"); - } - } - - final int fp = page; Bukkit.getScheduler().runTask(plugin, () -> openMyOrders(p, fp)); - return true; - } - public boolean handleAdminClick(Player p, int raw) { - Player[] online = Bukkit.getOnlinePlayers().toArray(new Player[0]); - int page = adminViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { openAdminPanel(p, page - 1); return true; } - if (raw == 50) { openAdminPanel(p, page + 1); return true; } - if (raw == 53) { openOrderCenter(p); return true; } - int si = slotIndex(raw); if (si < 0) return true; - int actual = si + page * PAGE_SIZE; if (actual < 0 || actual >= online.length) return true; - openAdminOrders(p, online[actual].getUniqueId(), 0); return true; - } - public boolean handleAdminOrdersClick(Player p, int raw, boolean left, boolean shift) { - UUID tu = adminViewTarget.get(p.getUniqueId()); - if (tu == null) return false; - List list = getPlayerOrdersWithin(tu, RETENTION_30D); - int page = adminViewPage.getOrDefault(p.getUniqueId(), 0); - if (raw == 48) { - openAdminOrders(p, tu, page - 1); - return true; - } - if (raw == 50) { - openAdminOrders(p, tu, page + 1); - return true; - } - if (raw == 53) { - openAdminPanel(p, 0); - return true; - } - int si = slotIndex(raw); - if (si < 0) return true; - int actual = si + page * PAGE_SIZE; - if (actual < 0 || actual >= list.size()) return true; - OrderRecord r = list.get(actual); - if (r.status == 2 || r.status == 3 || r.status == 4) { - p.sendMessage("§c状态: " + getStatusName(r.status)); - return true; - } - if (left) { - if (r.status == 1) { - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝: #" + r.orderId); - notifyPlayer(r.uuid, "§c退款被拒绝"); - openAdminOrders(p, tu, page); - } else if (r.status == 0) { - partialRefundListening.add(p.getUniqueId()); - partialRefundTarget.put(p.getUniqueId(), r); - p.closeInventory(); - p.sendMessage("§e输入退款百分比(1-100): #" + r.orderId + " 实付" + r.totalPaid + "枚"); - } - } else { - if (r.status == 0 || r.status == 1) { - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a全额退款 §e" + r.player + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, "§a退款 §6" + r.totalPaid + "枚"); - openAdminOrders(p, tu, page); - } - } - // ★ 替换 handleAdminOrdersClick 中的审批逻辑 - if (left) { - // 左键 = 拒绝 - if (r.status == 1) { - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝退款: #" + r.orderId); - notifyPlayer(r.uuid, "§c退款申请已被拒绝"); - openAdminOrders(p, tu, page); - } - } else { - // 右键 = 批准 - if (r.status == 1) { - String rt = r.refundType != null - ? r.refundType : ""; - - // ★ 退款退货申请 → 检查潜影盒 - if (rt.equals("refund_return_apply")) { - String pt = r.packType != null - ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // 检查玩家背包里有没有带订单标签的盒子 - Player target = Bukkit.getPlayer(r.uuid); - if (target == null || !target.isOnline()) { - p.sendMessage("§c玩家不在线,无法执行退货退款"); - openAdminOrders(p, tu, page); - return true; - } - - boolean foundTagged = false; - boolean foundAny = false; - for (int i = 0; i < 36; i++) { - ItemStack is = target.getInventory() - .getItem(i); - if (is == null) continue; - if (!is.getType().name() - .contains("SHULKER")) continue; - foundAny = true; - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - foundTagged = true; - // 收走盒子 - target.getInventory().clear(i); - break; - } - } - } - if (foundTagged) break; - } - - if (foundTagged) { - // ★ 有带标记的盒子 → 收走 + 退钱 - plugin.getBonds().addBonds( - r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" - + r.player + " §6" - + r.totalPaid + "枚(盒子已收走)"); - notifyPlayer(r.uuid, - "§a[订单] 退货退款成功,§6" - + r.totalPaid + "枚已退还"); - openAdminOrders(p, tu, page); - return true; - } else if (foundAny) { - // ★ 有盒子但没标记 → 等管理员决定 - pendingReturns.put(r.orderId, - new PendingReturn(r)); - p.closeInventory(); - p.sendMessage("§e§l[退货审批] 订单 #" - + r.orderId); - p.sendMessage("§7玩家背包有潜影盒但" - + "无订单标记,可能已被替换"); - p.sendMessage("§a输入 §e1 §a= 收走盒子并退款"); - p.sendMessage("§e输入 §e2 §e= 仅退款(不收盒子)"); - p.sendMessage("§c输入 §c3 §c= 拒绝退款"); - p.sendMessage("§72分钟内未操作自动拒绝"); - // 启动2分钟超时 - startReturnTimeout(r.orderId); - return true; - } else { - // ★ 背包里没有任何盒子 - p.sendMessage("§c玩家背包里没有潜影盒"); - p.sendMessage("§7可拒绝并通知玩家" - + "申请仅退款或把货拿回来"); - openAdminOrders(p, tu, page); - return true; - } - } - // ★ 散装退货退款:扣货+退钱 - Player target = Bukkit.getPlayer(r.uuid); - if (target != null && target.isOnline()) { - for (OrderItem it : r.items) - removeItems(target, it.mat, it.qty); - } - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" + r.player - + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, - "§a[订单] 退货退款成功,§6" - + r.totalPaid + "枚"); - openAdminOrders(p, tu, page); - return true; - } - - // ★ 仅退款申请 / 5分钟内自动退款 - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - saveOrders(); - p.sendMessage("§a已批准退款 §e" + r.player - + " §6" + r.totalPaid + "枚"); - notifyPlayer(r.uuid, - "§a[订单] 退款已批准,§6" - + r.totalPaid + "枚已退还"); - openAdminOrders(p, tu, page); - } - } - return true; - } - - /** - * 询问用户是否提交退货退款申请 - */ - private void askReturnConfirm(Player p, OrderRecord r) { - returnConfirmWaiting.put(p.getUniqueId(), r.orderId); - p.sendMessage(""); - p.sendMessage("§e§l[退货退款申请]"); - p.sendMessage("§7订单 #" + r.orderId - + " 实付" + r.totalPaid + "枚"); - p.sendMessage("§a输入 §e1 §a= 提交退货退款申请"); - p.sendMessage("§c输入 §e2 §c= 取消"); - p.sendMessage("§730秒内未操作自动取消"); - p.sendMessage(""); - - // 30秒超时 - final UUID uuid = p.getUniqueId(); - final long oid = r.orderId; - new BukkitRunnable() { - @Override - public void run() { - Long waiting = returnConfirmWaiting.get(uuid); - if (waiting != null && waiting == oid) { - returnConfirmWaiting.remove(uuid); - Player pl = Bukkit.getPlayer(uuid); - if (pl != null && pl.isOnline()) { - pl.sendMessage("§7退货退款申请已超时取消"); - } - } - } - }.runTaskLater(plugin, RETURN_CONFIRM_TIMEOUT); - } - - /** - * 执行5分钟内自动退货退款 - */ - private void doAutoReturn(Player p, OrderRecord r) { - String pt = r.packType != null ? r.packType : "none"; - boolean isShulker = pt.equals("custom") - || pt.equals("default") - || pt.equals("shulker"); - - if (isShulker) { - // 找并收走带标签的盒子 - boolean found = false; - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is == null) continue; - if (!is.getType().name().contains("SHULKER")) - continue; - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + r.orderId)) { - p.getInventory().clear(i); - found = true; - break; - } - } - } - if (found) break; - } - // 没找到标签盒子,收任意潜影盒 - if (!found) { - for (int i = 0; i < 36; i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType().name() - .contains("SHULKER")) { - p.getInventory().clear(i); - break; - } - } - } - } else { - for (OrderItem it : r.items) - removeItems(p, it.mat, it.qty); - } - - plugin.getBonds().addBonds(r.player, r.totalPaid); - r.status = 2; - r.refundedAmount = r.totalPaid; - r.refundType = "auto_return"; - saveOrders(); - p.sendMessage("§a退款退货成功: §6" - + r.totalPaid + "枚"); - } - - - // ===== 2分钟超时 ===== - private void startReturnTimeout(long orderId) { - new BukkitRunnable() { - @Override - public void run() { - PendingReturn pr = pendingReturns - .remove(orderId); - if (pr == null) return; // 已处理 - Player admin = Bukkit.getPlayer(pr.playerUUID); - if (admin != null && admin.isOnline()) { - admin.sendMessage("§c[退货审批] 订单 #" - + orderId + " 超时,自动拒绝"); - } - // 通知玩家 - notifyPlayer(pr.playerUUID, - "§c[订单] 退货退款申请已超时拒绝"); - // 恢复订单状态 - OrderRecord r = findOrder(orderId); - if (r != null) { - r.status = 0; - r.refundType = null; - saveOrders(); - } - } - }.runTaskLater(plugin, 2400L); // 2分钟 - } - - // ===== 查找订单 ===== - private OrderRecord findOrder(long orderId) { - synchronized (orders) { - for (OrderRecord r : orders) { - if (r.orderId == orderId) return r; - } - } - return null; - } - - - public boolean handleReceiptConfigClick(Player p, int raw) { - if (raw == 49) { openOrderCenter(p); return true; } - if (raw == 48) { saveReceiptConfig(); p.sendMessage("§a已保存"); openReceiptConfig(p); return true; } - if (raw == 50) { createDefaultReceiptConfig(new File(plugin.getDataFolder(), "小票设置.txt")); loadReceiptConfig(); p.sendMessage("§e已重置"); openReceiptConfig(p); return true; } - if (raw == 22) { sendRandomPreview(p); return true; } - String sec = null; - if (raw == 10) sec = "header"; else if (raw == 11) sec = "itemLine"; else if (raw == 12) sec = "discountLine"; - else if (raw == 14) sec = "packLine"; else if (raw == 15) sec = "summary"; else if (raw == 16) sec = "footer"; - if (sec != null) { receiptBuilderSection.put(p.getUniqueId(), sec); openReceiptBuilder(p, sec); } - return true; - } - public boolean handleReceiptBuilderClick(Player p, int raw) { - String sec = receiptBuilderSection.get(p.getUniqueId()); if (sec == null) return false; - String token = getTokenBySlot(raw); - if (token != null) { String cur = getSection(sec); setSection(sec, cur.isEmpty() ? token : cur + token); openReceiptBuilder(p, sec); return true; } - if (raw == 47) { String c = getSection(sec); int lb = c.lastIndexOf('}'); if (lb >= 0) { int ls = c.lastIndexOf('{', lb); if (ls >= 0) setSection(sec, c.substring(0, ls)); } else setSection(sec, c.substring(0, Math.max(0, c.length() - 8))); openReceiptBuilder(p, sec); return true; } - if (raw == 48) { setSection(sec, ""); openReceiptBuilder(p, sec); return true; } - if (raw == 49) { receiptBuilderSection.remove(p.getUniqueId()); openReceiptConfig(p); return true; } - if (raw == 50) { saveReceiptConfig(); receiptBuilderSection.remove(p.getUniqueId()); p.sendMessage("§a已保存"); openReceiptConfig(p); return true; } - if (raw == 51) { setSection(sec, getDefaultVal(sec)); openReceiptBuilder(p, sec); return true; } - return true; - } - - // ===== 聊天事件 ===== - @EventHandler - public void onAdminChat(AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); UUID uuid = p.getUniqueId(); - // ★ 在 onAdminChat 方法开头添加 -// ===== 退货审批输入 ===== - for (Map.Entry entry : - new HashMap<>(pendingReturns).entrySet()) { - Long pendingOrderId = entry.getKey(); - PendingReturn pr = entry.getValue(); - if (pr.playerUUID.equals(p.getUniqueId()) - || isAdmin(p)) { - try { - int choice = Integer.parseInt( - e.getMessage().trim()); - pendingReturns.remove(pendingOrderId); - OrderRecord r = findOrder(pendingOrderId); - if (r == null) { - p.sendMessage("§c订单不存在"); - return; - } - - if (choice == 1) { - // ★ 收走盒子并退款 - Player target = Bukkit.getPlayer( - pr.playerUUID); - if (target != null && target.isOnline()) { - for (int i = 0; i < 36; i++) { - ItemStack is = target.getInventory() - .getItem(i); - if (is != null && is.getType().name() - .contains("SHULKER")) { - ItemMeta im = is.getItemMeta(); - if (im != null && im.getLore() != null) { - for (String line : im.getLore()) { - if (line.equals("§8#" + pendingOrderId)) { - target.getInventory().clear(i); - break; - } - } - } - break; - } - } - } - plugin.getBonds().addBonds(pr.playerName, pr.totalPaid); - r.status = 2; - r.refundedAmount = pr.totalPaid; - saveOrders(); - p.sendMessage("§a已退货退款 §e" + pr.playerName - + " §6" + pr.totalPaid + "枚(盒子已收走)"); - notifyPlayer(pr.playerUUID, - "§a[订单] 退货退款成功,§6" - + pr.totalPaid + "枚已退还"); - - } else if (choice == 2) { - // ★ 仅退款(不收盒子) - plugin.getBonds().addBonds(pr.playerName, pr.totalPaid); - r.status = 2; - r.refundedAmount = pr.totalPaid; - saveOrders(); - p.sendMessage("§e已仅退款 §e" + pr.playerName - + " §6" + pr.totalPaid + "枚(未收盒子)"); - notifyPlayer(pr.playerUUID, - "§e[订单] 仅退款成功,§6" - + pr.totalPaid + "枚已退还" - + ",盒子无需退回"); - - } else if (choice == 3) { - // ★ 拒绝 - r.status = 0; - r.refundType = null; - saveOrders(); - p.sendMessage("§c已拒绝退款 #" + pendingOrderId); - notifyPlayer(pr.playerUUID, - "§c[订单] 退货退款已被拒绝" - + ",您可以申请仅退款" - + "或把货拿回来后重新申请"); - } else { - p.sendMessage("§c请输入 1、2 或 3"); - pendingReturns.put(pendingOrderId, pr); - } - return; - - } catch (NumberFormatException ignored) { - // 不是数字,跳过 - } - } - } -// ★ 在 onAdminChat 方法开头,处理用户退货确认 - if (returnConfirmWaiting.containsKey(uuid)) { - String msg = e.getMessage().trim(); - if (msg.equals("1")) { - // ★ 确认提交 - Long orderId = returnConfirmWaiting.remove(uuid); - if (orderId != null) { - OrderRecord r = findOrder(orderId); - if (r != null) { - r.status = 1; - r.refundType = "refund_return_apply"; - saveOrders(); - p.sendMessage("§e已提交退货退款申请," - + "等待管理员审批"); - notifyAdmins(p, r); - } - } - e.setCancelled(true); - return; - } else if (msg.equals("2")) { - // ★ 取消 - returnConfirmWaiting.remove(uuid); - p.sendMessage("§7已取消退货退款申请"); - e.setCancelled(true); - return; - } else { - // 输入其他内容:取消等待 - returnConfirmWaiting.remove(uuid); - // 不 return,让消息正常发送 - } - } - - if (partialRefundListening.contains(uuid)) { - e.setCancelled(true); partialRefundListening.remove(uuid); - OrderRecord t = partialRefundTarget.remove(uuid); if (t == null) return; - try { int pct = Integer.parseInt(e.getMessage().trim()); if (pct < 1 || pct > 100) { p.sendMessage("§c1-100"); return; } - int ref = t.totalPaid * pct / 100; if (ref <= 0) ref = 1; - plugin.getBonds().addBonds(t.player, ref); t.status = 3; t.refundedAmount = ref; saveOrders(); - p.sendMessage("§a部分退款 §e" + t.player + " §6" + ref + "枚(" + pct + "%)"); - notifyPlayer(t.uuid, "§a退款 §6" + ref + "枚"); - final UUID tu = adminViewTarget.get(uuid); final int pg = adminViewPage.getOrDefault(uuid, 0); - Bukkit.getScheduler().runTask(plugin, () -> openAdminOrders(p, tu, pg)); - } catch (NumberFormatException ex) { p.sendMessage("§c请输入数字"); } catch (Exception ex) { plugin.getLogger().log(Level.WARNING, "[Order]退款异常", ex); } - return; - } - if (receiptBuilderSection.containsKey(uuid)) { - e.setCancelled(true); String sec = receiptBuilderSection.remove(uuid); if (sec == null) return; - String msg = e.getMessage().trim(); - if ("cancel".equalsIgnoreCase(msg)) { Bukkit.getScheduler().runTask(plugin, () -> openReceiptConfig(p)); return; } - setSection(sec, msg.replace("\\n", "\n")); p.sendMessage("§a已更新 §e" + sec); - Bukkit.getScheduler().runTask(plugin, () -> openReceiptConfig(p)); - } - } - - // ===== 数据查询 ===== - private List getPlayerAllOrders(UUID uuid) { - List r = new ArrayList<>(); - synchronized (orders) { for (OrderRecord o : orders) if (o.uuid.equals(uuid) && System.currentTimeMillis() - o.timestamp < RETENTION_30D) r.add(o); } - Collections.reverse(r); return r; - } - private List getPlayerOrdersWithin(UUID uuid, long ms) { - List r = new ArrayList<>(); long cut = System.currentTimeMillis() - ms; - synchronized (orders) { for (OrderRecord o : orders) if (o.uuid.equals(uuid) && o.timestamp >= cut) r.add(o); } - Collections.reverse(r); return r; - } - - // ===== 持久化 ===== - private void loadOrders() { - File f = new File(plugin.getDataFolder(), "orders.dat"); if (!f.exists()) return; - try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); - OrderRecord cur = null; String line; - while ((line = br.readLine()) != null) { line = line.trim(); - if (line.equals("==")) { cur = new OrderRecord(); cur.items = new ArrayList<>(); } - else if (cur != null && line.contains("=")) { int eq = line.indexOf('='); String k = line.substring(0, eq); String v = line.substring(eq + 1); - switch (k) { case "id": cur.orderId = Long.parseLong(v); cur.timestamp = cur.orderId; break; case "uuid": cur.uuid = UUID.fromString(v); break; - case "player": cur.player = v; break; case "totalOriginal": cur.totalOriginal = Integer.parseInt(v); break; case "totalPaid": cur.totalPaid = Integer.parseInt(v); break; - case "discount": cur.discount = Integer.parseInt(v); break; case "discountType": cur.discountType = v; break; case "couponCode": cur.couponCode = v; break; - case "packFee": cur.packFee = Integer.parseInt(v); break; case "packType": cur.packType = v; break; case "packColor": cur.packColor = v; break; - case "status": cur.status = Integer.parseInt(v); break; case "refundedAmount": cur.refundedAmount = Integer.parseInt(v); break; - case "printTime": cur.printTime = Long.parseLong(v); break; case "refundType": cur.refundType = v; break; - case "item": String[] pp = v.split("\\|"); if (pp.length >= 5) cur.items.add(new OrderItem(pp[0], pp[1], Integer.parseInt(pp[2]), Integer.parseInt(pp[3]), Integer.parseInt(pp[4]))); break; } - } else if (line.isEmpty() && cur != null && cur.uuid != null) { orders.add(cur); cur = null; } - } if (cur != null && cur.uuid != null) orders.add(cur); br.close(); - } catch (Exception e) { plugin.getLogger().warning("[Order]加载失败: " + e.getMessage()); } - } - private void saveOrders() { - File f = new File(plugin.getDataFolder(), "orders.dat"); - try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); - synchronized (orders) { for (OrderRecord r : orders) { pw.println("=="); - pw.println("id=" + r.orderId); pw.println("uuid=" + r.uuid); pw.println("player=" + r.player); - pw.println("totalOriginal=" + r.totalOriginal); pw.println("totalPaid=" + r.totalPaid); - pw.println("discount=" + r.discount); pw.println("discountType=" + nn(r.discountType)); - pw.println("couponCode=" + nn(r.couponCode)); pw.println("packFee=" + r.packFee); - pw.println("packType=" + nn(r.packType)); pw.println("packColor=" + nn(r.packColor)); - pw.println("status=" + r.status); pw.println("refundedAmount=" + r.refundedAmount); - pw.println("printTime=" + r.printTime); pw.println("refundType=" + nn(r.refundType)); - for (OrderItem it : r.items) pw.println("item=" + it.name + "|" + it.mat + "|" + it.originalPrice + "|" + it.finalPrice + "|" + it.qty); - } } pw.flush(); pw.close(); - } catch (Exception e) { plugin.getLogger().warning("[Order]保存失败: " + e.getMessage()); } - } - private void startCleanupTask() { new BukkitRunnable() { public void run() { synchronized (orders) { if (orders.removeIf(OrderManager.this::isExpired)) saveOrders(); } } }.runTaskTimer(plugin, 36000L, 36000L); } - /** - * 将小票书塞入潜影盒 - */ - public void addBookToShulker(ItemStack shulker, ItemStack book) { - try { - org.bukkit.inventory.meta.BlockStateMeta bm = - (org.bukkit.inventory.meta.BlockStateMeta) - shulker.getItemMeta(); - if (bm == null) return; - org.bukkit.block.BlockState st = bm.getBlockState(); - if (st instanceof org.bukkit.block.Container) { - ((org.bukkit.block.Container) st) - .getInventory().addItem(book); - bm.setBlockState(st); - shulker.setItemMeta(bm); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Order] 塞书失败: " + e.getMessage()); - } - } - - // ===== 工具 ===== - private String nn(String s) { return s != null ? s : ""; } - private boolean isExpired(OrderRecord r) { return System.currentTimeMillis() - r.timestamp > RETENTION_30D; } - private String fmtDate(long ts) { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ts)); } - private int slotIndex(int raw) { for (int i = 0; i < SLOTS.length; i++) if (SLOTS[i] == raw) return i; return -1; } - private String brief(String s) { if (s == null || s.isEmpty()) return "(空)"; String c = sc(s); return c.length() > 30 ? c.substring(0, 30) + "..." : c; } - private String sectionName(String s) { switch(s){case "header":return "头部";case "itemLine":return "商品行";case "discountLine":return "优惠行";case "packLine":return "打包行";case "summary":return "汇总";case "footer":return "页脚";default:return s;} } - private String sc(String s) { - if (s == null) return ""; - return s.replaceAll("§[0-9a-fk-orA-FK-OR]", ""); - } - private String getSection(String s) { switch(s){case "header":return tplHeader;case "itemLine":return tplItemLine;case "discountLine":return tplDiscountLine;case "packLine":return tplPackLine;case "summary":return tplSummary;case "footer":return tplFooter;default:return "";} } - private void setSection(String s, String v) { switch(s){case "header":tplHeader=v;break;case "itemLine":tplItemLine=v;break;case "discountLine":tplDiscountLine=v;break;case "packLine":tplPackLine=v;break;case "summary":tplSummary=v;break;case "footer":tplFooter=v;break;} } - private String getDefaultVal(String s) { switch(s){case "header":return "§6§l{商店名}§r\n购物小票\n订单#{订单号} {订单时间}\n玩家: {玩家}";case "itemLine":return "§e{品名}§r x{数量} {原价}→{执行价} 小计{小计}枚";case "discountLine":return "优惠: §a-{优惠额}枚§r ({优惠类型}{优惠券码})";case "packLine":return "打包: §c+{打包费}枚§r ({打包颜色})";case "summary":return "原价{原价合计}枚 → 实付§6{实付}枚§r 余额{余额}枚";case "footer":return "感谢惠顾,欢迎再来!";default:return "";} } - private String getTokenBySlot(int raw) { switch(raw){case 10:return "{品名}";case 11:return "{原价}";case 12:return "{执行价}";case 13:return "{数量}";case 14:return "{小计}";case 15:return "{商店名}";case 16:return "{玩家}";case 19:return "{订单号}";case 20:return "{订单时间}";case 21:return "{打印时间}";case 22:return "{日期}";case 23:return "{时间}";case 24:return "{原价合计}";case 25:return "{实付}";case 28:return "{优惠额}";case 29:return "{优惠类型}";case 30:return "{优惠券码}";case 31:return "{打包费}";case 32:return "{打包颜色}";case 33:return "{余额}";case 37:return "═══";case 38:return "───";case 39:return "═════════════";case 40:return "\\n";default:return null;} } - private ItemStack mkItem(Material mat, String name, String... lore) { ItemStack it = new ItemStack(mat); ItemMeta im = it.getItemMeta(); if (im != null) { im.setDisplayName(name); if (lore.length > 0) im.setLore(Arrays.asList(lore)); it.setItemMeta(im); } return it; } - private Map loadMap(File f) { Map m = new LinkedHashMap<>(); try { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8)); String line; while ((line = br.readLine()) != null) { line = line.trim(); if (line.isEmpty() || line.startsWith("#")) continue; int eq = line.indexOf('='); if (eq > 0) m.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim()); } br.close(); } catch (Exception ignored) {} return m; } - private void writeLines(File f, List lines) { try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)); for (String l : lines) pw.println(l); pw.flush(); pw.close(); } catch (Exception ignored) {} } - private void sendRandomPreview(Player p) { String[] n = {"钻石剑","钻石镐","铁剑","金苹果"}; String[] m = {"DIAMOND_SWORD","DIAMOND_PICKAXE","IRON_SWORD","GOLDEN_APPLE"}; Random r = new Random(); List items = new ArrayList<>(); for (int i = 0; i < 2 + r.nextInt(3); i++) { int idx = r.nextInt(n.length); items.add(new OrderItem(n[idx], m[idx], 100 + r.nextInt(5000), 100 + r.nextInt(5000), 1 + r.nextInt(10))); } OrderRecord o = new OrderRecord(); o.orderId = System.currentTimeMillis(); o.uuid = p.getUniqueId(); o.player = p.getName(); o.items = items; o.totalOriginal = 0; for (OrderItem it : items) o.totalOriginal += it.originalSubtotal(); o.totalPaid = o.totalOriginal; o.timestamp = o.orderId; o.status = 0; p.sendMessage("§6§l===== 小票预览(随机) ====="); sendReceiptChat(p, o); p.sendMessage("§6§l===== 预览结束 ====="); } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PVPManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/PVPManager.java deleted file mode 100644 index 326c0fb..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PVPManager.java +++ /dev/null @@ -1,1380 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.NamespacedKey; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.scoreboard.*; - - -import java.io.File; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class PVPManager implements Listener { - - private final Main plugin; - private Connection pvpDb; - - private final Map - regions = new ConcurrentHashMap<>(); - private final Map - inRegion = new ConcurrentHashMap<>(); - private final Map - sessionKills = - new ConcurrentHashMap<>(); - private final Map - lastKillTime = - new ConcurrentHashMap<>(); - private final Map - selectState = - new ConcurrentHashMap<>(); - private final Map - killSessions = - new ConcurrentHashMap<>(); - private final Set tripleAnnounced = - ConcurrentHashMap.newKeySet(); - private final Map lastScoreboardRefresh = new ConcurrentHashMap<>(); - - - // 边框显示 - private final Set - permanentBorders = - ConcurrentHashMap.newKeySet(); - private final Set - tempBorders = - ConcurrentHashMap.newKeySet(); - - - public static class KillSession { - public int count = 0; - public long firstKill = 0; - public long lastKill = 0; - public boolean tripleAnnounced = - false; - public String region = ""; - } - - public static class PVPRegion { - public String name; - public String world; - public int x1, y1, z1; - public int x2, y2, z2; - public int minX, minY, minZ; - public int maxX, maxY, maxZ; - - public void calcBounds() { - minX = Math.min(x1, x2); - minY = Math.min(y1, y2); - minZ = Math.min(z1, z2); - maxX = Math.max(x1, x2); - maxY = Math.max(y1, y2); - maxZ = Math.max(z1, z2); - } - - public boolean contains( - Location loc) { - if (loc == null) return false; - if (!loc.getWorld().getName() - .equals(world)) - return false; - int x = loc.getBlockX(); - int y = loc.getBlockY(); - int z = loc.getBlockZ(); - return x >= minX && x <= maxX - && y >= minY && y <= maxY - && z >= minZ && z <= maxZ; - } - } - - public PVPManager(Main plugin) { - this.plugin = plugin; - initDB(); - loadRegions(); - } - - // ===== 独立数据库 ===== - - private void initDB() { - try { - Class.forName( - "org.sqlite.JDBC"); - File dbFile = new File( - plugin.getDataFolder(), - "pvp.db"); - pvpDb = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getAbsolutePath()); - Statement st = - pvpDb.createStatement(); - st.execute( - "PRAGMA journal_mode=WAL"); - st.execute( - "CREATE TABLE IF NOT EXISTS " - + "pvp_regions (" - + "id INTEGER PRIMARY KEY " - + "AUTOINCREMENT," - + "name TEXT NOT NULL " - + "UNIQUE," - + "world TEXT NOT NULL," - + "x1 INTEGER NOT NULL," - + "y1 INTEGER NOT NULL," - + "z1 INTEGER NOT NULL," - + "x2 INTEGER NOT NULL," - + "y2 INTEGER NOT NULL," - + "z2 INTEGER NOT NULL," - + "create_time INTEGER " - + "DEFAULT 0)"); - st.execute( - "CREATE TABLE IF NOT EXISTS " - + "pvp_kills (" - + "player_name TEXT " - + "NOT NULL," - + "region_name TEXT " - + "NOT NULL," - + "kills INTEGER " - + "DEFAULT 0," - + "deaths INTEGER " - + "DEFAULT 0," - + "PRIMARY KEY" - + "(player_name, " - + "region_name))"); - st.close(); - } catch (Exception e) { - throw new RuntimeException( - "[PVP] DB init failed: " - + e.getMessage(), e); - } - } - - // ===== DB 操作 ===== - - private void saveRegion(String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT OR REPLACE " - + "INTO pvp_regions" - + " (name, world," - + " x1,y1,z1," - + " x2,y2,z2," - + " create_time)" - + " VALUES " - + "(?,?,?,?,?,?,?,?,?)"); - ps.setString(1, name); - ps.setString(2, world); - ps.setInt(3, x1); - ps.setInt(4, y1); - ps.setInt(5, z1); - ps.setInt(6, x2); - ps.setInt(7, y2); - ps.setInt(8, z2); - ps.setLong(9, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private Map - getRegion(String name) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT * FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - rs.close(); - ps.close(); - return row; - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - private List> - getAllRegions() { - List> list = - new ArrayList<>(); - try { - Statement st = - pvpDb.createStatement(); - ResultSet rs = - st.executeQuery( - "SELECT * FROM " - + "pvp_regions"); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - for (int i = 1; i <= rs - .getMetaData() - .getColumnCount(); i++) { - row.put(rs.getMetaData() - .getColumnName(i), - rs.getObject(i)); - } - list.add(row); - } - rs.close(); - st.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - private void deleteRegion(String name) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "DELETE FROM " - + "pvp_regions " - + "WHERE name=?"); - ps.setString(1, name); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private int getKills(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT kills FROM " - + "pvp_kills " - + "WHERE " - + "player_name=?" - + " AND " - + "region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = - ps.executeQuery(); - int v = 0; - if (rs.next()) - v = rs.getInt("kills"); - rs.close(); - ps.close(); - return v; - } catch (SQLException e) { - return 0; - } - } - - private int getDeaths(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT deaths FROM " - + "pvp_kills " - + "WHERE " - + "player_name=?" - + " AND " - + "region_name=?"); - ps.setString(1, player); - ps.setString(2, region); - ResultSet rs = - ps.executeQuery(); - int v = 0; - if (rs.next()) - v = rs.getInt("deaths"); - rs.close(); - ps.close(); - return v; - } catch (SQLException e) { - return 0; - } - } - - private void addKill(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT INTO pvp_kills" - + " (player_name," - + " region_name," - + " kills,deaths)" - + " VALUES " - + "(?,?,1,0) " - + "ON CONFLICT" - + "(player_name," - + " region_name)" - + " DO UPDATE " - + "SET kills=" - + "kills+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private void addDeath(String player, - String region) { - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "INSERT INTO pvp_kills" - + " (player_name," - + " region_name," - + " kills,deaths)" - + " VALUES " - + "(?,?,0,1) " - + "ON CONFLICT" - + "(player_name," - + " region_name)" - + " DO UPDATE " - + "SET deaths=" - + "deaths+1"); - ps.setString(1, player); - ps.setString(2, region); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private List> - getTop(String region, int limit) { - List> list = - new ArrayList<>(); - try { - PreparedStatement ps = - pvpDb.prepareStatement( - "SELECT player_name," - + " kills,deaths" - + " FROM " - + "pvp_kills " - + "WHERE " - + "region_name=?" - + " AND kills>0" - + " ORDER BY kills" - + " DESC LIMIT ?"); - ps.setString(1, region); - ps.setInt(2, limit); - ResultSet rs = - ps.executeQuery(); - while (rs.next()) { - Map row = - new LinkedHashMap<>(); - row.put("player_name", - rs.getString( - "player_name")); - row.put("kills", - rs.getInt("kills")); - row.put("deaths", - rs.getInt("deaths")); - list.add(row); - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public Set getRegionNames() { - return regions.keySet(); - } - - // ===== 加载 ===== - - private void loadRegions() { - regions.clear(); - List> list = - getAllRegions(); - for (Map row : list) { - PVPRegion r = new PVPRegion(); - r.name = (String) row - .get("name"); - r.world = (String) row - .get("world"); - r.x1 = ((Number) row - .get("x1")).intValue(); - r.y1 = ((Number) row - .get("y1")).intValue(); - r.z1 = ((Number) row - .get("z1")).intValue(); - r.x2 = ((Number) row - .get("x2")).intValue(); - r.y2 = ((Number) row - .get("y2")).intValue(); - r.z2 = ((Number) row - .get("z2")).intValue(); - r.calcBounds(); - regions.put(r.name, r); - } - } - - // ===== 工具 ===== - - public ItemStack createTool() { - ItemStack tool = new ItemStack( - Material.BLAZE_ROD); - ItemMeta m = tool.getItemMeta(); - m.setDisplayName( - "§6§l[PVP] 区域选择工具"); - m.setLore(Arrays.asList( - "§7右键:设置第一个角", - "§7左键:设置第二个角")); - m.getPersistentDataContainer() - .set(new NamespacedKey( - plugin, "pvp_tool"), - PersistentDataType.STRING, - "tool"); - tool.setItemMeta(m); - return tool; - } - - private boolean isPVPTool( - ItemStack item) { - if (item == null) return false; - ItemMeta m = item.getItemMeta(); - if (m == null) return false; - return m.getPersistentDataContainer() - .has(new NamespacedKey( - plugin, "pvp_tool"), - PersistentDataType.STRING); - } - - public void giveTool(Player p) { - boolean has = false; - for (ItemStack it : p.getInventory() - .getContents()) { - if (isPVPTool(it)) { - has = true; - break; - } - } - if (!has) { - p.getInventory() - .addItem(createTool()); - } - } - - // ===== 边框粒子 ===== - - private void drawBorder(Player target, - PVPRegion r) { - if (r == null) return; - World w = Bukkit.getWorld(r.world); - if (w == null) return; - int step = 2; - drawLine(target, w, - r.minX, r.minY, r.minZ, - r.maxX, r.minY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.minZ, - r.maxX, r.minY, r.maxZ, step); - drawLine(target, w, - r.maxX, r.minY, r.maxZ, - r.minX, r.minY, r.maxZ, step); - drawLine(target, w, - r.minX, r.minY, r.maxZ, - r.minX, r.minY, r.minZ, step); - drawLine(target, w, - r.minX, r.maxY, r.minZ, - r.maxX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.maxY, r.minZ, - r.maxX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.maxX, r.maxY, r.maxZ, - r.minX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.minX, r.maxY, r.maxZ, - r.minX, r.maxY, r.minZ, step); - drawLine(target, w, - r.minX, r.minY, r.minZ, - r.minX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.minZ, - r.maxX, r.maxY, r.minZ, step); - drawLine(target, w, - r.maxX, r.minY, r.maxZ, - r.maxX, r.maxY, r.maxZ, step); - drawLine(target, w, - r.minX, r.minY, r.maxZ, - r.minX, r.maxY, r.maxZ, step); - } - - private void drawLine(Player target, - World w, - int x1, int y1, int z1, - int x2, int y2, int z2, - int step) { - double dx = x2 - x1; - double dy = y2 - y1; - double dz = z2 - z1; - double dist = Math.sqrt( - dx * dx + dy * dy + dz * dz); - int steps = (int) (dist / step); - if (steps < 1) steps = 1; - for (int i = 0; i <= steps; i++) { - double t = (double) i / steps; - Location loc = new Location(w, - x1 + dx * t, - y1 + dy * t + 0.1, - z1 + dz * t); - w.spawnParticle( - Particle.FLAME, loc, 1, - 0.0, 0.0, 0.0, 0.0); - } - } - - // ===== 每秒 tick ===== - - public void tickRegions() { - long now = - System.currentTimeMillis(); - - // 玩家区域检测 - for (Player p : - Bukkit.getOnlinePlayers()) { - String pn = p.getName(); - Location loc = p.getLocation(); - String inside = null; - for (PVPRegion r : - regions.values()) { - if (r.contains(loc)) { - inside = r.name; - break; - } - } - String was = inRegion.get(pn); - - if (inside != null && !inside.equals(was)) { - inRegion.put(pn, inside); - if (was == null) { - String reg = inside; - final Player pp = p; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> - updateScoreboard( - pp, reg), - 1L); - } - - } else if (inside == null - && was != null) { - inRegion.remove(pn); - // 不删 sessionKills,等倒计时结束再清 - removeScoreboard(p); - final Player pp = p; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> plugin - .giveMenuSnowball(pp), - 5L); - } - - } - - // 5分钟超时重置连杀session - for (String pname : - new ArrayList<>( - killSessions - .keySet())) { - KillSession ks = - killSessions.get(pname); - if (ks == null) continue; - if (now - ks.lastKill - > 5 * 60 * 1000) { - killSessions.remove(pname); - } - } - - // 5分钟无击杀 → 计数榜切总榜 - // 5分钟无击杀 → 归档并清零 - for (String rName : - new ArrayList<>( - lastKillTime - .keySet())) { - long last = lastKillTime - .getOrDefault(rName, 0L); - if (now - last > 5 * 60 * 1000) { - // 清零该区域所有sessionKills - sessionKills.keySet() - .removeIf(k -> k - .startsWith( - rName + ":")); - // 切换到总榜 - refreshScoreboard(rName); - lastKillTime.remove(rName); - lastScoreboardRefresh.remove(rName); - - } - } - - - // 边框渲染 - for (Player p : - Bukkit.getOnlinePlayers()) { - String pr = - inRegion.get(p.getName()); - if (pr == null) continue; - PVPRegion r = regions.get(pr); - if (r == null) continue; - if (permanentBorders - .contains(pr)) { - drawBorder(p, r); - } - if (tempBorders - .contains(pr)) { - drawBorder(p, r); - } - // 每秒刷新活跃区域的计分板倒计时 - for (String rName : new ArrayList<>(lastKillTime.keySet())) { - long last = lastScoreboardRefresh.getOrDefault(rName, 0L); - if (now - last >= 1000) { - lastScoreboardRefresh.put(rName, now); - for (Player tp : Bukkit.getOnlinePlayers()) { - String inR = inRegion.get(tp.getName()); - if (rName.equals(inR)) { - showCounter(tp, rName); - } - } - } - } - - - - } - } - - private void broadcastWithSound(String msg, Sound sound) { - Bukkit.broadcastMessage(msg); - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), sound, 1.0f, 1.0f); - } - } - - - private void initSession(String player, - String region) { - killSessions.remove(player); - tripleAnnounced.remove( - region + ":" + player); - KillSession s = new KillSession(); - s.region = region; - s.firstKill = - System.currentTimeMillis(); - s.lastKill = - System.currentTimeMillis(); - killSessions.put(player, s); - } - - // ===== 击杀事件 ===== - - @EventHandler - public void onDeath( - EntityDeathEvent event) { - if (!(event.getEntity() - instanceof Player)) return; - Player victim = - (Player) event.getEntity(); - Player killer = - victim.getKiller(); - if (killer == null) return; - if (killer.equals(victim)) return; - - String region = - inRegion.get( - victim.getName()); - if (region == null) return; - - String vName = victim.getName(); - String kName = killer.getName(); - - // 总榜 - addKill(kName, region); - addDeath(vName, region); - - // 计数榜 - String kKey = - region + ":" + kName; - sessionKills.merge(kKey, 1, - Integer::sum); - lastKillTime.put(region, - System.currentTimeMillis()); - - // 终结检测 - // 终结检测 - KillSession victimSession = - killSessions.get(vName); - if (victimSession != null - && victimSession.count >= 3) { - String endMsg = - "§b§l" + kName - + " §e终结了 " - + vName + " §e的 " - + victimSession.count - + "连杀!§a恭喜终结者 " - + kName - + "§e!§7快来挑战!"; - killSessions.remove(vName); - broadcastWithSound(endMsg, Sound.ENTITY_ENDER_DRAGON_GROWL); - - killSessions.remove(vName); - } - - - // 击杀者连杀 - KillSession ks = - killSessions.get(kName); - if (ks == null - || !ks.region.equals(region) - || System.currentTimeMillis() - - ks.lastKill > 5 * 60 * 1000) { - tripleAnnounced.remove( - region + ":" + kName); - KillSession ns = - new KillSession(); - ns.region = region; - ns.count = 1; - ns.firstKill = - System.currentTimeMillis(); - ns.lastKill = - System.currentTimeMillis(); - killSessions.put(kName, ns); - } else { - ks.count++; - ks.lastKill = - System.currentTimeMillis(); - } - - // 多杀通报 - KillSession killerSession = - killSessions.get(kName); - if (killerSession != null) { - plugin.getLogger().info( - "[PVP] checkMultiKill: " - + kName + " count=" - + killerSession.count - + " region=" + region); - checkMultiKill(killer, - region, killerSession); - } - - // 刷新计分板 - for (Player p : - Bukkit.getOnlinePlayers()) { - if (region.equals( - inRegion.get( - p.getName()))) { - updateScoreboard(p, region); - } - } - - victim.sendMessage("§c你在「" - + region + "」被 " - + kName + " 击杀"); - killer.sendMessage("§a你在「" - + region + "」击杀了 " - + vName); - } - - private void checkMultiKill( - Player killer, String region, - KillSession session) { - int count = session.count; - String kName = killer.getName(); - String key = region + ":" + kName; - String msg = null; - - if (count >= 5) { - msg = "§c§l" + kName - + " §e在「" + region - + "」达成 " + count - + "连杀!§7§l快来挑战!"; - } else if (count == 3) { - if (tripleAnnounced.add(key)) { - msg = "§6§l三杀!" - + kName - + " 势不可挡!" - + "§7快来挑战!"; - } - } - - if (msg != null) { - if (count >= 5) { - broadcastWithSound(msg, Sound.ENTITY_ENDER_DRAGON_GROWL); - } else { - broadcastWithSound(msg, Sound.BLOCK_END_PORTAL_SPAWN); - } - } - - } - - - - // ===== 计分板(英文ID) ===== - - private void ensureScoreboard( - Player p, String region) { - // 活跃击杀:每秒强制刷新(更新倒计时) - if (lastKillTime.containsKey(region)) { - showCounter(p, region); - return; - } - // 无活跃击杀:只在没有scoreboard时创建总榜 - Scoreboard sb = p.getScoreboard(); - if (sb == null - || sb.getObjective( - "sdf1pvp") == null) { - showTotal(p, region); - } - } - - - private void updateScoreboard( - Player p, String region) { - if (lastKillTime - .containsKey(region)) { - showCounter(p, region); - } else { - showTotal(p, region); - } - } - - private void showTotal(Player p, - String region) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - Scoreboard sb = - mgr.getNewScoreboard(); - Objective obj = - sb.registerNewObjective( - "sdf1pvp", - Criteria.DUMMY, - "§6§l击杀总榜 [" - + region + "]"); - obj.setDisplaySlot( - DisplaySlot.SIDEBAR); - - List> top = - getTop(region, 10); - if (top.isEmpty()) { - obj.getScore("§7暂无记录") - .setScore(0); - } else { - int rank = 10; - for (Map row : - top) { - String name = (String) row - .get("player_name"); - int kills = ((Number) row - .get("kills")) - .intValue(); - int deaths = ((Number) row - .get("deaths")) - .intValue(); - String line = - "§e#" + (11 - rank) - + " §f" + name - + " §a" + kills - + "§7/§c" + deaths; - obj.getScore(line) - .setScore(rank); - rank--; - } - } - p.setScoreboard(sb); - } - private void showCounter(Player p, - String region) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - Scoreboard sb = mgr.getNewScoreboard(); - Objective obj = - sb.registerNewObjective( - "sdf1pvp", - Criteria.DUMMY, - "§c§l实时战况 [" - + region + "]"); - obj.setDisplaySlot( - DisplaySlot.SIDEBAR); - - List entries = - new ArrayList<>(); - for (Map.Entry e : - sessionKills.entrySet()) { - if (!e.getKey().startsWith( - region + ":")) - continue; - if (e.getValue() <= 0) continue; - String pName = e.getKey() - .substring( - region.length() + 1); - entries.add(new String[]{ - pName, - String.valueOf( - e.getValue())}); - } - - if (entries.isEmpty()) { - obj.getScore("§7等待击杀...") - .setScore(1); - } else { - entries.sort((a, b) -> - Integer.compare( - Integer.parseInt(b[1]), - Integer.parseInt(a[1]))); - int rank = 10; - for (String[] entry : entries) { - obj.getScore( - "§e" + entry[0] - + " §a" + entry[1]) - .setScore(rank); - rank--; - if (rank < 1) break; - } - } - - Long last = - lastKillTime.get(region); - if (last != null) { - long elapsed = - System.currentTimeMillis() - - last; - long remaining = - Math.max(0, - 5 * 60 * 1000 - elapsed); - long secs = remaining / 1000; - obj.getScore( - "§7冷却: " + secs + "s") - .setScore(-1); - } - - // 关键:强制赋值新scoreboard - p.setScoreboard(sb); - } - - private void removeScoreboard( - Player p) { - ScoreboardManager mgr = - Bukkit.getScoreboardManager(); - p.setScoreboard( - mgr.getNewScoreboard()); - } - - private void refreshScoreboard( - String regionName) { - for (Player p : - Bukkit.getOnlinePlayers()) { - String pr = - inRegion.get(p.getName()); - if (regionName.equals(pr)) { - showTotal(p, regionName); - } - } - } - - // ===== 工具选择事件 ===== - - @EventHandler - public void onInteract( - PlayerInteractEvent event) { - Player p = event.getPlayer(); - ItemStack hand = - p.getInventory() - .getItemInMainHand(); - if (!isPVPTool(hand)) return; - - if (!event.getAction() - .isRightClick() - && !event.getAction() - .isLeftClick()) return; - - Location loc = p.getLocation(); - String[] state = - selectState.get(p.getName()); - if (state == null) { - state = new String[]{ - loc.getWorld() - .getName(), - "", ""}; - selectState.put( - p.getName(), state); - } - - String coord = - loc.getBlockX() + "," - + loc.getBlockY() + "," - + loc.getBlockZ(); - - if (event.getAction() - .isRightClick()) { - state[0] = loc.getWorld() - .getName(); - state[1] = coord; - p.sendMessage( - "§a第一个角: " + coord); - } else { - state[2] = coord; - p.sendMessage( - "§a第二个角: " + coord); - } - event.setCancelled(true); - } - - // ===== 退出清理 ===== - - @EventHandler - public void onQuit( - PlayerQuitEvent event) { - String name = - event.getPlayer().getName(); - inRegion.remove(name); - selectState.remove(name); - } - - // ===== 指令 ===== - - public boolean onCommand(Player p, - String[] args) { - if (args.length == 0) { - return showStats(p, - p.getName()); - } - switch (args[0].toLowerCase()) { - case "create": - return handleCreate( - p, args); - case "stats": - if (args.length >= 2) - return showStats(p, - args[1]); - return showStats(p, - p.getName()); - case "list": - return handleList(p); - case "delete": - if (args.length >= 2) - return handleDelete(p, - args[1]); - p.sendMessage( - "§c用法: " - + "/pvp delete <名字>"); - return true; - case "tool": - giveTool(p); - p.sendMessage( - "§a已发放" - + "PVP区域选择工具"); - return true; - case "on": - return handleBorderOn( - p, args); - case "off": - return handleBorderOff( - p, args); - case "tempban": - return handleTempBan( - p, args); - default: - return showStats(p, - p.getName()); - } - } - - private boolean handleBorderOn( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp on <区域名>"); - return true; - } - if (!regions - .containsKey(name)) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - permanentBorders.add(name); - p.sendMessage("§a已开启「" - + name + "」永久边框显示"); - return true; - } - - private boolean handleBorderOff( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp off <区域名>"); - return true; - } - permanentBorders.remove(name); - tempBorders.remove(name); - p.sendMessage("§a已关闭「" - + name + "」边框显示"); - return true; - } - - private boolean handleTempBan( - Player p, String[] args) { - String name = args.length >= 2 - ? args[1] : null; - if (name == null) { - p.sendMessage("§c用法: " - + "/pvp tempban <区域名>"); - return true; - } - if (!regions - .containsKey(name)) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - tempBorders.add(name); - p.sendMessage("§a「" + name - + "」临时边框显示15秒"); - final String rName = name; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> - tempBorders - .remove(rName), - 300L); - return true; - } - - private boolean handleCreate( - Player p, String[] args) { - if (args.length >= 8) { - try { - int x1 = Integer.parseInt( - args[1]); - int y1 = Integer.parseInt( - args[2]); - int z1 = Integer.parseInt( - args[3]); - int x2 = Integer.parseInt( - args[4]); - int y2 = Integer.parseInt( - args[5]); - int z2 = Integer.parseInt( - args[6]); - String rName = args[7]; - return createRegion(p, - rName, - p.getWorld() - .getName(), - x1, y1, z1, - x2, y2, z2); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c坐标格式错误"); - return true; - } - } - - if (args.length >= 2) { - String[] state = - selectState - .get(p.getName()); - if (state == null - || state[1].isEmpty() - || state[2].isEmpty()) { - p.sendMessage( - "§c请先用工具选择两个角"); - return true; - } - String[] p1 = - state[1].split(","); - String[] p2 = - state[2].split(","); - return createRegion(p, - args[1], state[0], - Integer.parseInt(p1[0]), - Integer.parseInt(p1[1]), - Integer.parseInt(p1[2]), - Integer.parseInt(p2[0]), - Integer.parseInt(p2[1]), - Integer.parseInt(p2[2])); - } - - p.sendMessage( - "§c用法: /pvp create " - + " " - + " <名字>"); - p.sendMessage( - "§c或: /pvp create " - + "<名字> (先用工具选择)"); - return true; - } - - private boolean createRegion( - Player p, String name, - String world, - int x1, int y1, int z1, - int x2, int y2, int z2) { - if (getRegion(name) != null) { - p.sendMessage( - "§c区域已存在: " + name); - return true; - } - PVPRegion r = new PVPRegion(); - r.name = name; - r.world = world; - r.x1 = x1; - r.y1 = y1; - r.z1 = z1; - r.x2 = x2; - r.y2 = y2; - r.z2 = z2; - r.calcBounds(); - saveRegion(name, world, - x1, y1, z1, - x2, y2, z2); - regions.put(name, r); - p.sendMessage( - "§a§lPVP区域创建成功!"); - p.sendMessage( - "§7名称: §f" + name); - p.sendMessage("§7范围: §f" - + r.minX + "," + r.minY - + "," + r.minZ + " → " - + r.maxX + "," + r.maxY - + "," + r.maxZ); - selectState.remove(p.getName()); - return true; - } - - private boolean handleList( - Player p) { - List> all = - getAllRegions(); - if (all.isEmpty()) { - p.sendMessage( - "§7暂无PVP区域"); - return true; - } - p.sendMessage( - "§6§lPVP区域列表:"); - for (Map row : - all) { - p.sendMessage("§e- §f" - + row.get("name") - + " §7(" + row.get("world") - + ")"); - } - return true; - } - - private boolean handleDelete( - Player p, String name) { - if (getRegion(name) == null) { - p.sendMessage( - "§c区域不存在: " + name); - return true; - } - deleteRegion(name); - regions.remove(name); - permanentBorders.remove(name); - tempBorders.remove(name); - sessionKills.keySet() - .removeIf(k -> k - .startsWith( - name + ":")); - lastKillTime.remove(name); - lastScoreboardRefresh.remove(name); - p.sendMessage( - "§a已删除: " + name); - return true; - } - - private boolean showStats(Player p, - String target) { - String region = - inRegion.get(target); - if (region == null) { - p.sendMessage("§6§l" + target - + " 的PVP战绩:"); - List> all = - getAllRegions(); - boolean has = false; - for (Map row : - all) { - String rn = (String) row - .get("name"); - int kills = - getKills(target, rn); - int deaths = - getDeaths(target, rn); - if (kills > 0 - || deaths > 0) { - p.sendMessage( - "§e- §f" + rn - + " §a击杀:" + kills - + " §c死亡:" + deaths); - has = true; - } - } - if (!has) - p.sendMessage( - "§7暂无记录"); - } else { - int kills = - getKills(target, region); - int deaths = - getDeaths(target, region); - double kd = deaths > 0 - ? (double) kills / deaths - : kills; - p.sendMessage("§6§l" + target - + " @ " + region); - p.sendMessage( - "§a击杀: " + kills - + " §c死亡: " + deaths - + " §eKD: " - + String.format( - "%.2f", kd)); - } - return true; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PasswordUtils.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/PasswordUtils.java deleted file mode 100644 index 4029ed4..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PasswordUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -package Sdf1_login; - -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; - -public class PasswordUtils { - - private static final SecureRandom RANDOM = - new SecureRandom(); - - public static String generateSalt() { - byte[] salt = new byte[16]; - RANDOM.nextBytes(salt); - return Base64.getEncoder().encodeToString(salt); - } - - public static String hash(String password, String salt) { - try { - MessageDigest md = - MessageDigest.getInstance("SHA-256"); - md.update(Base64.getDecoder().decode(salt)); - byte[] hash = - md.digest(password.getBytes("UTF-8")); - return Base64.getEncoder().encodeToString(hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static boolean validate(String password) { - if (password == null) return false; - if (password.length() < 6 || password.length() > 20) - return false; - boolean hasUpper = false; - boolean hasLower = false; - boolean hasDigit = false; - for (char c : password.toCharArray()) { - if (Character.isUpperCase(c)) hasUpper = true; - if (Character.isLowerCase(c)) hasLower = true; - if (Character.isDigit(c)) hasDigit = true; - } - return hasUpper && hasLower && hasDigit; - } - - public static String generateTempPassword() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(8); - for (int i = 0; i < 8; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateInviteCode() { - String chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - StringBuilder sb = new StringBuilder(6); - for (int i = 0; i < 6; i++) { - sb.append(chars.charAt( - RANDOM.nextInt(chars.length()))); - } - return sb.toString(); - } - - public static String generateVerifyCode() { - int code = 100000 + RANDOM.nextInt(900000); - return String.valueOf(code); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PlayerStorage.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/PlayerStorage.java deleted file mode 100644 index f93caa3..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PlayerStorage.java +++ /dev/null @@ -1,130 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.*; -import java.util.logging.Logger; - -public class PlayerStorage { - private Connection conn; - private final Logger logger; - - public PlayerStorage(File dataFolder, - Logger logger) { - this.logger = logger; - File dbFile = new File( - dataFolder, - "quest_data.db"); - try { - Class.forName( - "org.sqlite.JDBC"); - conn = DriverManager - .getConnection( - "jdbc:sqlite:" - + dbFile - .getAbsolutePath()); - conn.setAutoCommit(true); - Statement s = - conn.createStatement(); - s.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_kv (" - + "player TEXT NOT NULL," - + "key TEXT NOT NULL," - + "value TEXT," - + "PRIMARY KEY" - + "(player, key))"); - s.close(); - logger.info( - "[QuestStorage] 初始化: " - + dbFile.getAbsolutePath()); - } catch (Exception e) { - logger.severe( - "[QuestStorage] 失败: " - + e.getMessage()); - } - } - - public Object getCondition( - String player, String key) { - if (conn == null) return null; - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value " - + "FROM quest_kv " - + "WHERE player=? " - + "AND key=?"); - ps.setString(1, - player.toLowerCase()); - ps.setString(2, key); - ResultSet rs = - ps.executeQuery(); - String val = rs.next() - ? rs.getString("value") - : null; - rs.close(); - ps.close(); - return parseValue(val); - } catch (SQLException e) { - return null; - } - } - - public void setCondition( - String player, String key) { - setConditionRaw( - player, key, "true"); - } - public void debugPlayer(String player) { - - } - public void setConditionRaw( - String player, String key, - String value) { - if (conn == null) return; - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO quest_kv" - + "(player,key,value)" - + " VALUES(?,?,?)"); - ps.setString(1, - player.toLowerCase()); - ps.setString(2, key); - ps.setString(3, value); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - logger.warning( - "[QuestStorage] 写入失败: " - + key + "=" + value - + " | " + e.getMessage()); - } - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) - conn.close(); - } catch (Exception e) {} - } - - private Object parseValue(String v) { - if (v == null) return null; - try { - return Long.parseLong(v); - } catch (Exception e1) { - try { - return Double.parseDouble(v); - } catch (Exception e2) { - if ("true".equalsIgnoreCase(v)) - return true; - if ("false".equalsIgnoreCase(v)) - return false; - return v; - } - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PointsManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/PointsManager.java deleted file mode 100644 index 77fc195..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/PointsManager.java +++ /dev/null @@ -1,374 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - - -import java.lang.reflect.Method; -import java.util.*; - -public class PointsManager { - - private final Main plugin; - private Object cyInstance = null; - private Method cyActivationMethod = null; - private Method cyGetShopItemsMethod = null; - - private final List> cyItems = - new ArrayList<>(); - - public PointsManager(Main plugin) { - this.plugin = plugin; - discoverCY(); - } - - private void discoverCY() { - try { - org.bukkit.plugin.Plugin p = Bukkit - .getPluginManager() - .getPlugin("CY_beibao"); - if (p != null && p.isEnabled()) { - cyInstance = p; - cyGetShopItemsMethod = p.getClass() - .getMethod("onSdf1GetShopItems"); - cyActivationMethod = p.getClass() - .getMethod("onSdf1Activation", - String.class, - int.class, int.class); - plugin.getLogger().info( - "[Sdf1_login] CY_beibao 联控已连接"); - } - } catch (Exception ignored) { - } - } - - private void refreshCYItems() { - cyItems.clear(); - if (cyInstance == null - || cyGetShopItemsMethod == null) return; - try { - Object result = - cyGetShopItemsMethod.invoke(cyInstance); - if (result == null) return; - String data = result.toString(); - if (data.isEmpty()) return; - String[] items = data.split(";"); - for (String itemStr : items) { - String[] p = itemStr.split("\\|"); - if (p.length < 8) continue; - Map item = - new LinkedHashMap<>(); - item.put("id", p[0]); - item.put("name", p[1]); - item.put("days", - Integer.parseInt(p[2])); - item.put("slots", - Integer.parseInt(p[3])); - item.put("price", - Integer.parseInt(p[4])); - item.put("stock", - Integer.parseInt(p[5])); - item.put("icon", p[6]); - item.put("lifetime", - "1".equals(p[7])); - cyItems.add(item); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 拉取CY商品失败: " - + e.getMessage()); - } - } - - private void activateCY(String name, - int slots, int days) { - try { - if (cyInstance == null - || cyActivationMethod == null) return; - cyActivationMethod.invoke( - cyInstance, name, slots, days); - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] CY激活失败: " - + e.getMessage()); - } - } - - public int getPoints(String name) { - return ((Number) plugin.getDb() - .getField(name, "points")).intValue(); - } - - public Inventory createShopGUI(Player p) { - int points = getPoints(p.getName()); - Inventory inv = Bukkit.createInventory( - null, 54, - "§d§l积分商城 | 当前积分: " + points); - fillBg(inv); - inv.setItem(10, mkItem(Material.EMERALD, - "§a游戏币充值", - "§7消耗积分兑换游戏币", - "§7100积分 = $1000", - "", - "§e点击兑换")); - inv.setItem(12, mkItem(Material.ENDER_CHEST, - "§b背包空间", - "§7消耗积分兑换背包格子", - "§750积分 = 9格永久空间", - "", - "§e点击兑换")); - inv.setItem(14, mkItem(Material.EXPERIENCE_BOTTLE, - "§e经验瓶", - "§7消耗积分兑换经验", - "§720积分 = 10级经验", - "", - "§e点击兑换")); - if (cyInstance != null) { - inv.setItem(31, mkItem(Material.CHEST, - "§6背包商城", - "§7来自CY_beibao的商品", - "", - "§e点击查看")); - } - inv.setItem(49, mkItem(Material.ARROW, "§7返回")); - return inv; - } - - public Inventory createCYShopGUI(Player p) { - refreshCYItems(); - int points = getPoints(p.getName()); - int size = Math.max(54, - ((cyItems.size() / 9) + 1) * 9); - if (size > 54) size = 54; - Inventory inv = Bukkit.createInventory( - null, size, - "§d§lCY背包商城 | 当前积分: " + points); - fillBg(inv); - for (int i = 0; i < cyItems.size() - && i < 45; i++) { - Map item = cyItems.get(i); - String name = (String) item.get("name"); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - List lore = new ArrayList<>(); - lore.add("§7价格: §e" + price + " 积分"); - if (lifetime) { - lore.add("§7格子: §e终身 +" + slots); - } else { - lore.add("§7格子: §e+" + slots); - lore.add("§7时间: §e" + days + "天"); - } - if (stock == 0) { - lore.add("§7库存: §a无限"); - } else if (stock == -1) { - lore.add("§7库存: §c已下架"); - } else { - lore.add("§7库存: §e" + stock); - } - lore.add(""); - if (points >= price && stock != -1) { - lore.add("§e点击购买"); - } else if (stock == -1) { - lore.add("§c已下架"); - } else { - lore.add("§c积分不足"); - } - inv.setItem(i, mkItem(Material.CHEST, - "§a" + name, - lore.toArray(new String[0]))); - } - inv.setItem(size - 1, - mkItem(Material.ARROW, "§7返回")); - return inv; - } - - public void addItem(String name, - int price, String cmd2) { - // 追加到商品配置文件 - File file = new File( - plugin.getDataFolder(), - "ShopItems.txt"); - try { - java.io.FileWriter fw = - new java.io.FileWriter(file, true); - fw.write(name + ":" + price - + ":" + cmd2 + "\n"); - fw.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void removeItem(int index) { - File file = new File( - plugin.getDataFolder(), - "ShopItems.txt"); - try { - java.util.List lines = - new java.util.ArrayList<>(); - java.io.BufferedReader br = - new java.io.BufferedReader( - new java.io.FileReader( - file)); - String line; - int i = 0; - while ((line = br.readLine()) != null) { - if (line.trim().isEmpty() - || line.startsWith("#")) { - lines.add(line); - continue; - } - if (i != index) { - lines.add(line); - } - i++; - } - br.close(); - java.io.PrintWriter pw = - new java.io.PrintWriter(file); - for (String l : lines) pw.println(l); - pw.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - public void handleClick(Player p, int slot) { - int points = getPoints(p.getName()); - switch (slot) { - case 10: - if (points >= 100) { - plugin.getDb().deductPoints( - p.getName(), 100); - if (plugin.getEconomy() != null) { - plugin.getEconomy() - .depositPlayer(p, 1000); - } - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "100")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 12: - if (points >= 50) { - plugin.getDb().deductPoints( - p.getName(), 50); - activateCY(p.getName(), 9, 0); - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "50")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 14: - if (points >= 20) { - plugin.getDb().deductPoints( - p.getName(), 20); - p.setLevel(p.getLevel() + 10); - p.sendMessage(plugin.getConfig2() - .msg("points_purchase_success") - .replace("{count}", "20")); - } else { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - } - break; - case 31: - if (cyInstance != null) { - p.openInventory( - createCYShopGUI(p)); - } - break; - } - } - - public void handleCYClick(Player p, int slot) { - if (slot < 0 || slot >= cyItems.size() - || slot >= 45) return; - Map item = cyItems.get(slot); - int price = ((Number) item.get("price")) - .intValue(); - int stock = ((Number) item.get("stock")) - .intValue(); - int days = ((Number) item.get("days")) - .intValue(); - int slots = ((Number) item.get("slots")) - .intValue(); - boolean lifetime = (boolean) item - .get("lifetime"); - - if (stock == -1) { - p.sendMessage("§c该商品已下架"); - return; - } - int points = getPoints(p.getName()); - if (points < price) { - p.sendMessage(plugin.getConfig2() - .msg("points_insufficient") - .replace("{points}", - String.valueOf(points))); - return; - } - - plugin.getDb().deductPoints(p.getName(), price); - int actDays = lifetime ? 0 : days; - activateCY(p.getName(), slots, actDays); - - String tl = lifetime - ? "终身" : (days + "天"); - p.sendMessage("§a购买成功!获得 " - + tl + " +" + slots + "格 背包空间"); - } - - private void fillBg(Inventory inv) { - ItemStack gl = mkItem( - Material.GRAY_STAINED_GLASS_PANE, " "); - for (int i = 0; i < inv.getSize(); i++) - inv.setItem(i, gl); - } - - private ItemStack mkItem(Material mat, String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/QuestStorage.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/QuestStorage.java deleted file mode 100644 index 45dbabf..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/QuestStorage.java +++ /dev/null @@ -1,138 +0,0 @@ -package Sdf1_login; - -import java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -public class QuestStorage { - - private Connection conn; - - public QuestStorage(File dataFolder) { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File( - dataFolder, - "quest_data.db"); - conn = DriverManager.getConnection( - "jdbc:sqlite:" - + dbFile.getPath()); - initTable(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void initTable() - throws SQLException { - Statement stmt = - conn.createStatement(); - stmt.executeUpdate( - "CREATE TABLE IF NOT EXISTS " - + "quest_conditions (" - + "player TEXT NOT NULL, " - + "condition_key TEXT NOT NULL, " - + "value TEXT DEFAULT '1', " - + "timestamp INTEGER, " - + "PRIMARY KEY " - + "(player, condition_key))"); - stmt.close(); - } - - /** - * 标记条件已完成(value = "1") - */ - public void setCondition( - String player, - String condition) { - setConditionRaw( - player, condition, "1"); - } - - /** - * 写入任意值(计数器等) - */ - public void setConditionRaw( - String player, - String condition, - String value) { - try { - PreparedStatement ps = - conn.prepareStatement( - "INSERT OR REPLACE " - + "INTO " - + "quest_conditions " - + "(player, " - + "condition_key, " - + "value, timestamp) " - + "VALUES " - + "(?, ?, ?, ?)"); - ps.setString(1, player); - ps.setString(2, condition); - ps.setString(3, value); - ps.setLong(4, - System.currentTimeMillis()); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - /** - * 查询条件值 - */ - public Object getCondition( - String player, - String condition) { - try { - PreparedStatement ps = - conn.prepareStatement( - "SELECT value FROM " - + "quest_conditions " - + "WHERE player = ? " - + "AND " - + "condition_key = ?"); - ps.setString(1, player); - ps.setString(2, condition); - ResultSet rs = - ps.executeQuery(); - if (rs.next()) { - String val = - rs.getString("value"); - rs.close(); - ps.close(); - // 尝试解析为数字 - try { - if (val.contains(".")) { - return Double.parseDouble( - val); - } - return Long.parseLong(val); - } catch (NumberFormatException e) { - return val; - } - } - rs.close(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - public void close() { - try { - if (conn != null - && !conn.isClosed()) { - conn.close(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/QuestTracker.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/QuestTracker.java deleted file mode 100644 index c147723..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/QuestTracker.java +++ /dev/null @@ -1,3537 +0,0 @@ -package Sdf1_login; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.generator.structure.Structure; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.Plugin; -// 如果 Bonds 在 Sdf1_login 包下: -import Sdf1_login.BondManager; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - - -public class QuestTracker { - - private final Plugin plugin; - private final QuestStorage storage; - private final Map - playerBiome = new HashMap<>(); - private final Map> - questCache = new HashMap<>(); - private final List - checkInRecords = new ArrayList<>(); - private final Map - lastCheckInPos = new HashMap<>(); - - public void recordCheckIn( - String player, - String biomeKey, - String category, - String worldName, - double x, double y, double z) { - String record = player - + "|" + worldName - + "|" + biomeKey - + "|" + category - + "|" + String.format( - "%.1f", x) - + "|" + String.format( - "%.1f", y) - + "|" + String.format( - "%.1f", z) - + "|" + System.currentTimeMillis(); - checkInRecords.add(record); - plugin.getLogger().info( - "[QuestTracker] 打卡记录: " - + record); - } - - public List getCheckInRecords() { - return new ArrayList<>(checkInRecords); - } - - public List getPlayerRecords( - String player) { - List list = new ArrayList<>(); - for (String r : checkInRecords) { - if (r.startsWith( - player.toLowerCase() - + "|")) { - list.add(r); - } - } - return list; - } - - - public QuestTracker(Plugin plugin) { - this.plugin = plugin; - this.storage = new QuestStorage( - plugin.getDataFolder()); - - loadAllQuests(); - } - - public void debugPlayer(String player) { - plugin.getLogger().info( - "[QuestTracker] === " - + player + " 任务数据 ==="); - } - public void shutdown() { - if (storage != null) { - storage.close(); - } - } - - /** - * 玩家移动时记录当前群系 - */ - public void onPlayerMove( - String player, - String biomeName) { - playerBiome.put(player, biomeName); - } - /** - * 标记玩家已打卡该群系 - */ - // ========== 群系分类映射表 ========== - private static final Map - BIOME_CATEGORY = new HashMap<>(); - static { - // 海洋群系 - BIOME_CATEGORY.put("minecraft:ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_warm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_lukewarm_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_cold_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:frozen_ocean", "海洋"); - BIOME_CATEGORY.put("minecraft:deep_frozen_ocean", "海洋"); - // 平原群系 - BIOME_CATEGORY.put("minecraft:plains", "平原"); - BIOME_CATEGORY.put("minecraft:sunflower_plains", "平原"); - BIOME_CATEGORY.put("minecraft:meadow", "平原"); - // 森林群系 - BIOME_CATEGORY.put("minecraft:forest", "森林"); - BIOME_CATEGORY.put("minecraft:flower_forest", "森林"); - BIOME_CATEGORY.put("minecraft:birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_birch_forest", "森林"); - BIOME_CATEGORY.put("minecraft:dark_forest", "森林"); - BIOME_CATEGORY.put("minecraft:taiga", "森林"); - BIOME_CATEGORY.put("minecraft:snowy_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_pine_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:old_growth_spruce_taiga", "森林"); - BIOME_CATEGORY.put("minecraft:cherry_grove", "森林"); - BIOME_CATEGORY.put("minecraft:jungle", "森林"); - BIOME_CATEGORY.put("minecraft:sparse_jungle", "森林"); - BIOME_CATEGORY.put("minecraft:bamboo_jungle", "森林"); - // 沙漠群系 - BIOME_CATEGORY.put("minecraft:desert", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna", "沙漠"); - BIOME_CATEGORY.put("minecraft:savanna_plateau", "沙漠"); - BIOME_CATEGORY.put("minecraft:beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:snowy_beach", "沙漠"); - BIOME_CATEGORY.put("minecraft:windswept_savanna", "沙漠"); - // 恶地群系 - BIOME_CATEGORY.put("minecraft:badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:wooded_badlands", "恶地"); - BIOME_CATEGORY.put("minecraft:eroded_badlands", "恶地"); - // 雪原群系 - BIOME_CATEGORY.put("minecraft:snowy_plains", "雪原"); - BIOME_CATEGORY.put("minecraft:grove", "雪原"); - BIOME_CATEGORY.put("minecraft:ice_spikes", "雪原"); - BIOME_CATEGORY.put("minecraft:jagged_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_river", "雪原"); - BIOME_CATEGORY.put("minecraft:frozen_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:stony_peaks", "雪原"); - BIOME_CATEGORY.put("minecraft:snowy_slopes", "雪原"); - // 沙砾群系 - BIOME_CATEGORY.put("minecraft:windswept_hills", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_forest", "沙砾"); - BIOME_CATEGORY.put("minecraft:windswept_gravelly_hills", "沙砾"); - // 沼泽群系 - BIOME_CATEGORY.put("minecraft:swamp", "沼泽"); - BIOME_CATEGORY.put("minecraft:mangrove_swamp", "沼泽"); - // 河流 - BIOME_CATEGORY.put("minecraft:river", "河流"); - // 蘑菇岛 - BIOME_CATEGORY.put("minecraft:mushroom_fields", "蘑菇岛"); - // 地下群系 - BIOME_CATEGORY.put("minecraft:dripstone_caves", "地下"); - BIOME_CATEGORY.put("minecraft:lush_caves", "地下"); - BIOME_CATEGORY.put("minecraft:deep_dark", "地下"); - // 下界群系 - BIOME_CATEGORY.put("minecraft:nether_wastes", "下界"); - BIOME_CATEGORY.put("minecraft:crimson_forest", "下界"); - BIOME_CATEGORY.put("minecraft:warped_forest", "下界"); - BIOME_CATEGORY.put("minecraft:soul_sand_valley", "下界"); - BIOME_CATEGORY.put("minecraft:basalt_deltas", "下界"); - // 岩岸/沙滩 - BIOME_CATEGORY.put("minecraft:stony_shore", - "石岸"); -// 海岸 - BIOME_CATEGORY.put("minecraft:mushroom_fields", - "蘑菇岛"); -// 苍白之园 - BIOME_CATEGORY.put( - "minecraft:pale_garden", "森林"); -// 末地小型岛屿 - BIOME_CATEGORY.put( - "minecraft:small_end_islands", "末地"); -// 主世界结构归类 - BIOME_CATEGORY.put("minecraft:village", "平原"); - BIOME_CATEGORY.put( - "minecraft:desert_pyramid", "沙漠"); - BIOME_CATEGORY.put( - "minecraft:jungle_pyramid", "森林"); - BIOME_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - - // 末地群系 - BIOME_CATEGORY.put("minecraft:the_end", "末地"); - BIOME_CATEGORY.put("minecraft:the_void", "末地"); - BIOME_CATEGORY.put("minecraft:end_highlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_midlands", "末地"); - BIOME_CATEGORY.put("minecraft:end_barrens", "末地"); - - - } - - // 所有群系大类列表 - private static final String[] - ALL_CATEGORIES = { - "海洋", "平原", "森林", "沙漠", - "恶地", "雪原", "沙砾", "沼泽", - "河流", "石岸", "蘑菇岛", "地下", - "下界", "末地" - }; - - // ===== 结构映射(PaperMC 1.21.4 实际结构 key)===== - private static final Map - STRUCTURE_CATEGORY = - new HashMap<>(); - static { - // 主世界结构 - STRUCTURE_CATEGORY.put( - "minecraft:village_plains", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_desert", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_savanna", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_snowy", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:village_taiga", - "村庄"); - STRUCTURE_CATEGORY.put( - "minecraft:desert_pyramid", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:jungle_pyramid", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:swamp_hut", "沼泽"); - STRUCTURE_CATEGORY.put( - "minecraft:igloo", "雪原"); - STRUCTURE_CATEGORY.put( - "minecraft:mansion", "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:pillager_outpost", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:mineshaft_mesa", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:stronghold", "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:ancient_city", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:trail_ruins", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:trial_chambers", - "地下"); - STRUCTURE_CATEGORY.put( - "minecraft:monument", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_cold", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ocean_ruin_warm", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck", "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:shipwreck_beached", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:buried_treasure", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal", - "平原"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_desert", - "沙漠"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_jungle", - "森林"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_mountain", - "山地"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_ocean", - "海洋"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_swamp", - "沼泽"); - // 下界结构 - STRUCTURE_CATEGORY.put( - "minecraft:fortress", "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:bastion_remnant", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:nether_fossil", - "下界"); - STRUCTURE_CATEGORY.put( - "minecraft:ruined_portal_nether", - "下界"); - // 末地结构 - STRUCTURE_CATEGORY.put( - "minecraft:end_city", "末地"); - } - - // 所有群系分类的条件关键词映射 - private static final Map - CATEGORY_KEYWORDS = new HashMap<>(); - static { - CATEGORY_KEYWORDS.put("海洋", "海洋"); - CATEGORY_KEYWORDS.put("平原", "平原"); - CATEGORY_KEYWORDS.put("森林", "森林"); - CATEGORY_KEYWORDS.put("沙漠", "沙漠"); - CATEGORY_KEYWORDS.put("恶地", "恶地"); - CATEGORY_KEYWORDS.put("雪原", "雪原"); - CATEGORY_KEYWORDS.put("沙砾", "沙砾"); - CATEGORY_KEYWORDS.put("沼泽", "沼泽"); - CATEGORY_KEYWORDS.put("河流", "河流"); - CATEGORY_KEYWORDS.put("蘑菇岛", "蘑菇岛"); - CATEGORY_KEYWORDS.put("地下", "地下"); - CATEGORY_KEYWORDS.put("下界", "下界"); - CATEGORY_KEYWORDS.put("末地", "末地"); - // 条件文本中的别名映射 - CATEGORY_KEYWORDS.put("地狱", "下界"); - CATEGORY_KEYWORDS.put("下届", "下界"); - CATEGORY_KEYWORDS.put("主岛", "末地"); - CATEGORY_KEYWORDS.put("石岸", "石岸"); - CATEGORY_KEYWORDS.put("沙滩", "石岸"); - CATEGORY_KEYWORDS.put("海岸", "石岸"); - - // ===== 结构名称关键词映射 ===== - // 主世界结构 - CATEGORY_KEYWORDS.put("村庄", "村庄"); - CATEGORY_KEYWORDS.put("沙漠神殿", "沙漠"); - CATEGORY_KEYWORDS.put("丛林神庙", "森林"); - CATEGORY_KEYWORDS.put("沼泽小屋", "沼泽"); - CATEGORY_KEYWORDS.put("雪屋", "雪原"); - CATEGORY_KEYWORDS.put("林地府邸", "森林"); - CATEGORY_KEYWORDS.put("掠夺者前哨站", "平原"); - CATEGORY_KEYWORDS.put("废弃矿井", "地下"); - CATEGORY_KEYWORDS.put("要塞", "地下"); - CATEGORY_KEYWORDS.put("远古城市", "地下"); - CATEGORY_KEYWORDS.put("古迹废墟", "地下"); - CATEGORY_KEYWORDS.put("试炼密室", "地下"); - CATEGORY_KEYWORDS.put("海底废墟", "海洋"); - CATEGORY_KEYWORDS.put("沉船", "海洋"); - CATEGORY_KEYWORDS.put("埋藏的宝箱", "海洋"); - CATEGORY_KEYWORDS.put("废弃传送门", "主世界"); - CATEGORY_KEYWORDS.put("化石", "主世界"); - - // 下界结构 - CATEGORY_KEYWORDS.put("下界要塞", "下界"); - CATEGORY_KEYWORDS.put("猪灵堡垒", "下界"); - CATEGORY_KEYWORDS.put("玄武岩结构", "下界"); - - // 末地结构 - CATEGORY_KEYWORDS.put("末地城", "末地"); - CATEGORY_KEYWORDS.put("末地船", "末地"); - - // 下界相关别名 - CATEGORY_KEYWORDS.put("地狱要塞", "下界"); - CATEGORY_KEYWORDS.put("下界堡垒", "下界"); - - } - - /** - * 用世界维度名分类(最可靠) - * 世界名含 the_nether → 下界 - * 世界名含 the_end → 末地 - * 其余 → 主世界 - */ - public String mapBiomeCategory( - String rawKey, String worldName) { - // 优先用世界名判断维度 - if (worldName != null) { - String w = - worldName.toLowerCase(); - if (w.contains("nether")) - return "下界"; - if (w.contains("end")) - return "末地"; - } - - // 用群系映射表 - if (rawKey != null) { - String lower = - rawKey.toLowerCase(); - // 带 minecraft: 前缀的直接查 - String cat = - BIOME_CATEGORY.get(lower); - if (cat != null) - return cat; - - // 不带前缀的尝试补全 - String prefixed = - "minecraft:" + lower; - cat = BIOME_CATEGORY.get(prefixed); - if (cat != null) - return cat; - - // 模糊匹配群系名 - for (Map.Entry entry - : BIOME_CATEGORY.entrySet()) { - if (entry.getKey() - .contains(lower) - || lower.contains( - entry.getKey() - .replace( - "minecraft:", - ""))) { - return entry.getValue(); - } - } - } - - return "主世界"; - } - - // ========== 结构检测 ========== - - /** - * 检测指定位置是否存在自然生成的结构 - * 返回结构分类(如"村庄"、"沙漠"等),未找到返回null - * 使用 PaperMC World.hasStructureAt() + Registry.STRUCTURE API - */ - private String detectStructureAt( - String worldName, double x, - double y, double z) { - try { - World w = Bukkit.getWorld(worldName); - if (w == null) return null; - - // 使用 BlockPosition 提高性能 - io.papermc.paper.math.Position pos = - io.papermc.paper.math.Position.block( - (int) Math.floor(x), - (int) Math.floor(y), - (int) Math.floor(z)); - - // 遍历所有自然生成的结构 - for (Structure structure : Registry.STRUCTURE) { - if (w.hasStructureAt(pos, structure)) { - // 获取结构 NamespacedKey(如 minecraft:village) - NamespacedKey structureKey = Registry.STRUCTURE.getKey(structure); - String keyStr = (structureKey != null) ? structureKey.toString() : "unknown"; - String cat = - STRUCTURE_CATEGORY.get(keyStr); - if (cat != null) { - plugin.getLogger().info( - "[QuestTracker] 检测到结构: " - + keyStr - + " -> " + cat); - return cat; - } - } - } - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 结构检测失败: " - + ex.getMessage()); - } - return null; - } - - /** - * 将结构中文名映射到对应的大类 - * 用于匹配任务条件中的结构名 - */ - private String mapStructureCategory( - String structureName) { - if (structureName == null) return ""; - - // 主世界结构 - if (structureName.contains("村庄")) - return "村庄"; - if (structureName.contains("沙漠神殿")) - return "沙漠"; - if (structureName.contains("丛林神庙")) - return "森林"; - if (structureName.contains("沼泽小屋")) - return "沼泽"; - if (structureName.contains("雪屋")) - return "雪原"; - if (structureName.contains("林地府邸")) - return "森林"; - if (structureName.contains("掠夺者前哨站")) - return "平原"; - if (structureName.contains("废弃矿井")) - return "地下"; - if (structureName.contains("要塞")) - return "地下"; - if (structureName.contains("远古城市")) - return "地下"; - if (structureName.contains("古迹废墟")) - return "地下"; - if (structureName.contains("试炼密室")) - return "地下"; - if (structureName.contains("海底废墟")) - return "海洋"; - if (structureName.contains("沉船")) - return "海洋"; - if (structureName.contains("埋藏的宝箱")) - return "海洋"; - if (structureName.contains("废弃传送门")) - return "主世界"; - if (structureName.contains("化石")) - return "主世界"; - - // 下界结构 - if (structureName.contains("下界要塞")) - return "下界"; - if (structureName.contains("猪灵堡垒")) - return "下界"; - if (structureName.contains("玄武岩结构")) - return "下界"; - - // 末地结构 - if (structureName.contains("末地城")) - return "末地"; - if (structureName.contains("末地船")) - return "末地"; - - return ""; - } - - -// ========== 解析为区块 ========== - - private List
parseIntoSections( - List lines) { - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - return parseHashSections(lines); - } else if (hasDash) { - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - continue; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseHashSections(converted); - } - return new ArrayList<>(); - } - - private List
parseHashSections( - List lines) { - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add(new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } else if (t.isEmpty()) { - // skip - } else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty() - && curTitle != null) { - curLines.add(cond); - } - } - } - if (curTitle != null) { - sections.add(new Section( - curTitle, curLines)); - } - return sections; - } - - private List
buildFlatSections( - List lines, - String category) { - List conditions = - new ArrayList<>(); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - conditions.add(cond); - } - } - if (conditions.isEmpty()) - return new ArrayList<>(); - List
list = - new ArrayList<>(); - list.add(new Section( - "第一阶段", conditions)); - return list; - } - - // ========== 文件加载入口 ========== - - private void loadAllQuests() { - loadCategory("新人任务"); - loadCategory("主线任务"); - loadCategory("支线任务"); - } - - private void loadCategory(String name) { - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 扫描中..."); - File folder = new File( - plugin.getDataFolder(), - name); - String path = "plugins/Sdf1_login/" - + name + "/"; - - if (!folder.exists()) { - folder.mkdirs(); - plugin.getLogger().warning( - "[QuestTracker] 已创建目录: " - + path); - questCache.put(name, - new ArrayList<>()); - return; - } - - File[] mdFiles = folder.listFiles( - (d, n) -> n.endsWith(".md")); - - if (mdFiles == null - || mdFiles.length == 0) { - plugin.getLogger().warning( - "[QuestTracker] " + path - + " 下无 .md 文件"); - questCache.put(name, - new ArrayList<>()); - return; - } - Arrays.sort(mdFiles); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 发现 " + mdFiles.length - + " 个文件,开始扫描..."); - - // 合并所有文件的阶段 - TreeMap merged = - new TreeMap<>(); - int fileCount = 0; - - for (int fi = 0; - fi < mdFiles.length; fi++) { - File file = mdFiles[fi]; - List lines = - readFileLines(file); - - if (lines.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 空文件,跳过"); - continue; - } - - List
sections = - parseIntoSections(lines); - - if (sections.isEmpty()) { - sections = buildFlatSections( - lines, name); - } - - if (sections.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " - + file.getName() - + ": 无法识别,跳过"); - continue; - } - - // 每个文件独立处理阶段号 - int defaultNum = fi + 1; - buildSingleFileStages( - sections, name, - merged, defaultNum); - - fileCount++; - } - - if (merged.isEmpty()) { - plugin.getLogger().warning( - "[QuestTracker] " + name - + ": 无有效任务数据"); - questCache.put(name, - new ArrayList<>()); - return; - } - - // 日志 - for (Map.Entry en - : merged.entrySet()) { - int num = en.getKey() >= 1000 - ? en.getKey() - 999 - : en.getKey(); - plugin.getLogger().info( - "[QuestTracker] " + name - + " 阶段" + num - + " \"" - + en.getValue().displayName - + "\" → 条件" - + en.getValue() - .conditions.size() - + "项, 详情" - + en.getValue() - .details.size() - + "项, 奖励" - + en.getValue() - .rewards.size() - + "项"); - } - - List list = - new ArrayList<>( - merged.values()); - questCache.put(name, list); - - plugin.getLogger().info( - "[QuestTracker] " + name - + ": 解析完成 → " - + list.size() + " 个阶段" - + " (来自 " + fileCount - + " 个文件)"); - } - - private Integer extractStageNumber( - String title) { - if (title == null) return null; - String t = title.toLowerCase().trim(); - - // 含阶段/stage/关/chapter 关键词 - boolean isStage = - t.contains("阶段") - || t.contains("stage") - || t.contains("关卡") - || t.contains("章节") - || t.contains("chapter"); - - if (isStage) { - String digits = title - .replaceAll("[^0-9]", ""); - if (!digits.isEmpty()) { - try { - return Integer.parseInt(digits); - } catch (NumberFormatException e) { - // fall through - } - } - return parseChineseNumber(title); - } - - // 无关键词但以数字开头(如 "1. 挖掘") - String trimmed = t - .replaceAll("^[.、\\s]+", "") - .trim(); - if (trimmed.length() > 0 - && Character.isDigit( - trimmed.charAt(0))) { - String num = trimmed - .replaceAll("[^0-9]", ""); - if (!num.isEmpty()) { - try { - return Integer.parseInt(num); - } catch (NumberFormatException e) { - // fall through - } - } - } - - return null; - } -// ========== 领取记录 ========== - - /** - * 标记已领取某阶段奖励 - */ - public void markClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return; - // 用阶段编号代替显示名,保证稳定 - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - storage.setCondition(player, key); - } - - public boolean hasClaimed( - String player, QuestFile qf) { - if (player == null || qf == null) - return false; - int num = getStageNumber(qf); - String key = "claimed_" - + qf.fileName + "_" + num; - return toBool( - storage.getCondition( - player, key)); - } - - private int parseChineseNumber( - String title) { - String[] cn = {"一", "二", "三", - "四", "五", "六", "七", - "八", "九", "十"}; - if (title.contains("十")) { - for (int j = 0; j < 9; j++) { - if (title.contains(cn[j])) { - return 10 + j + 1; - } - } - return 10; - } - for (int i = 0; i < 9; i++) { - if (title.contains(cn[i])) { - return i + 1; - } - } - return 0; - } - - // ========== md 解析核心 ========== - - /** - * 读取文件所有行 - */ - private List readFileLines( - File file) { - List lines = - new ArrayList<>(); - try { - BufferedReader br = - new BufferedReader( - new FileReader(file)); - String line; - while ((line = br.readLine()) - != null) { - lines.add(line); - } - br.close(); - } catch (Exception e) { - plugin.getLogger().warning( - "[QuestTracker] 读取 " - + file.getName() - + " 失败: " - + e.getMessage()); - } - return lines; - } - - /** - * 自动检测格式并解析单个文件 - */ - private List parseMdFile( - File file, String category) { - List lines = - readFileLines(file); - if (lines.isEmpty()) - return new ArrayList<>(); - - // 检测分隔符 - boolean hasHash = false; - boolean hasDash = false; - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("#")) - hasHash = true; - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) - hasDash = true; - } - - if (hasHash) { - // # 分隔格式 - return parseSections( - lines, category); - } else if (hasDash) { - // ---- 分隔格式:转换为 # 后复用 - List converted = - new ArrayList<>(); - for (String l : lines) { - String t = l.trim(); - if (t.startsWith("----") - || t.startsWith("====") - || t.startsWith("****")) { - String title = t - .replaceAll( - "^[-=*]+\\s*", - "") - .replaceAll( - "\\s*[-=*]+$", - "") - .trim(); - if (title.isEmpty()) - title = "separator"; - converted.add("# " + title); - } else { - converted.add(l); - } - } - return parseSections( - converted, category); - } else { - // 无分隔符:整文件一个阶段 - return parseFlat(lines, category); - } - } - - /** - * # 分隔格式 → 区块 → 阶段 - */ - private List parseSections( - List lines, - String category) { - // 拆分为区块 - List
sections = - new ArrayList<>(); - String curTitle = null; - List curLines = - new ArrayList<>(); - - for (String raw : lines) { - String t = raw.trim(); - - // # 开头 → 新区块 - if (t.startsWith("#")) { - if (curTitle != null) { - sections.add( - new Section( - curTitle, - new ArrayList<>( - curLines))); - } - curTitle = t.replaceFirst( - "^#+\\s*", "").trim(); - curLines = new ArrayList<>(); - } - // 空行/分隔线 → 跳过 - else if (t.isEmpty() - || t.startsWith("----") - || t.startsWith("====") - || t.startsWith("***")) { - // skip - } - // 普通行 → 区块内容 - else { - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - curLines.add(cond); - } - } - } - // 保存最后一个区块 - if (curTitle != null) { - sections.add( - new Section( - curTitle, curLines)); - } - - // 直接转为 QuestFile 列表 - List result = - new ArrayList<>(); - int num = 1; - for (Section sec : sections) { - QuestFile qf = new QuestFile(); - qf.displayName = - extractDisplayName( - sec.title); - qf.fileName = category; - qf.conditions = - new ArrayList<>(sec.lines); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - result.add(qf); - num++; - } - return result; - } - - /** - * 无分隔符:整文件 = 一个阶段 - */ - private List parseFlat( - List lines, - String category) { - List list = - new ArrayList<>(); - QuestFile qf = makeDefaultStage( - category); - for (String raw : lines) { - String t = raw.trim(); - if (t.isEmpty()) continue; - String cond = t.replaceFirst( - "^-\\s+", "").trim(); - if (!cond.isEmpty()) { - qf.conditions.add(cond); - } - } - if (!qf.conditions.isEmpty()) { - list.add(qf); - } - return list; - } -// ========== Vault 经济奖励发放 ========== - - /** - * 发放阶段奖励 - * 含经济内容的走 Vault API或债券 - */ - public void claimRewards( - Player player, - QuestFile qf) { - if (qf == null - || qf.rewards == null - || qf.rewards.isEmpty()) { - player.sendMessage("§c无可领取的奖励"); - return; - } - - Main main = (Main) plugin; - // ★ 读取配置:economy = Vault,bonds = 债券(默认) - boolean useEconomy = "economy".equalsIgnoreCase( - main.getConfigMgr().rewardChannel); - - // Vault 经济引用(仅 useEconomy 时使用) - net.milkbowl.vault.economy.Economy econ = null; - if (useEconomy) { - econ = main.getEconomy(); - } - - int sentCount = 0; - - for (String reward : qf.rewards) { - String rt = reward.trim(); - if (rt.isEmpty()) continue; - - // 检查可选奖励标记 - if (hasOptionalMarker(rt)) { - if (!areAllConditionsMet(player.getName(), qf)) { - player.sendMessage( - "§7[可选奖励] 需完成所有可选条件: " + rt); - continue; - } - } - - // 按 + 分割子项 - String[] parts = rt.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 尝试识别经济金额 - double amt = extractEconomyAmount(part); - if (amt > 0) { - // ★ 根据配置分发 - if (useEconomy && econ != null) { - // —— Vault 经济 —— - double before = - econ.getBalance(player); - econ.depositPlayer(player, amt); - double after = - econ.getBalance(player); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(余额: §e" - + String.format("%.2f", before) - + " §7→ §a" - + String.format("%.2f", after) - + "§7)"); - if (plugin instanceof Main) { - ((Main) plugin).getCommission() - .payCommission( - player.getName(), amt); - } - } else { - // —— 债券(默认) —— - int bondAmt = - (int) Math.round(amt); - BondManager bonds = main.getBonds(); - if (bonds != null) { - int before = bonds.getBonds( - player.getName()); - bonds.addBonds( - player.getName(), - bondAmt, - "quest_reward", - qf.displayName, - "任务系统", - "任务奖励:" + qf.displayName); - int after = bonds.getBonds( - player.getName()); - player.sendMessage( - "§a§l[任务奖励] §f" + part - + " §7(债券: §e" - + before - + " §7→ §a" - + after + "§7)"); - } else { - player.sendMessage( - "§c债券系统不可用"); - } - } - sentCount++; - continue; - } - - // 尝试发物品 - java.util.List items = - parseRewardItems(part); - if (!items.isEmpty()) { - for (ItemStack is : items) { - player.getInventory().addItem(is); - } - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - continue; - } - - // 非经济非物品 → 仅消息 - player.sendMessage( - "§a§l[任务奖励] §f" + part); - sentCount++; - } - } - - plugin.getLogger().info( - "[QuestTracker] " + player.getName() - + " 预取了 \"" + qf.displayName - + "\" 共 " + sentCount + "项奖励"); - } - - /** - * 检查奖励文本是否含可选标记 - */ - private boolean hasOptionalMarker( - String text) { - return text.contains("(可选)") - || text.contains("(进阶)") - || text.contains("【可选】") - || text.contains("【进阶】"); - } - - /** - * 检查阶段所有条件(含可选)是否全部满足 - * 用于决定可选奖励是否发放 - */ - private boolean areAllConditionsMet( - String player, QuestFile qf) { - if (qf.conditions == null - || qf.conditions.isEmpty()) - return true; - for (String cond : qf.conditions) { - if (!isConditionMet(player, cond)) - return false; - } - return true; - } - - /** - * 从奖励文本中提取经济金额 - * 支持: "500游戏币" "500" "金币500" - * "$500" "1000-5000"(随机) "1000~5000" - */ - private double extractEconomyAmount( - String text) { - String t = text.replaceAll(" ", ""); - - // 必须包含经济关键词才算经济奖励 - boolean hasEcon = - t.contains("游戏币") - || t.contains("金币") - || t.contains("积分") - || t.contains("块钱") - || t.contains("元") - || t.contains("$") - || t.contains("经济") - || t.contains("余额") - || t.contains("发钱") - || t.contains("债券"); // ★ 新增 - - - if (!hasEcon) return 0; - - // 范围: "1000-5000" 或 "1000~5000" - Matcher rm = Pattern - .compile("(\\d+)[-~](\\d+)") - .matcher(t); - if (rm.find()) { - double min = Double.parseDouble( - rm.group(1)); - double max = Double.parseDouble( - rm.group(2)); - return min + Math.random() - * (max - min); - } - - // 纯数字(含 $ 前缀) - Matcher nm = Pattern - .compile("\\$?(\\d+(\\.\\d+)?)") - .matcher(t); - if (nm.find()) { - return Double.parseDouble( - nm.group(1)); - } - - return 0; - } - /** - * 解析奖励物品文本 - * "皮革套装*1" → 4件皮革装备 - * "木剑*1" → 1把木剑 - * "钻石*64" → 64颗钻石 - */ - private List parseRewardItems( - String text) { - List result = - new ArrayList<>(); - - // 去掉条件前缀 - // 如 "完成注册登录:皮革套装*1" - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - if (text.contains(":")) { - text = text.substring( - text.indexOf(":") + 1); - } - - // 按 + 分割多个物品 - String[] parts = - text.split("[+,]"); - for (String part : parts) { - part = part.trim(); - if (part.isEmpty()) continue; - - // 跳过经济文本 - if (extractEconomyAmount(part) > 0) - continue; - - int amount = 1; - String itemName = part; - - // 解析 *N 数量 - if (part.contains("*")) { - String[] sp = - part.split("\\*"); - itemName = sp[0].trim(); - try { - amount = Integer.parseInt( - sp[1].trim()); - } catch (Exception e) { - amount = 1; - } - } - - List mats = - matchMaterials(itemName); - for (Material m : mats) { - int give = m.name() - .contains("HELMET") - || m.name() - .contains("CHESTPLATE") - || m.name() - .contains("LEGGINGS") - || m.name() - .contains("BOOTS") - ? 1 : amount; - result.add( - new ItemStack(m, give)); - } - } - return result; - } - - /** - * 中文物品名 → Material 列表 - * 套装返回多个部件 - */ - private List matchMaterials( - String name) { - List list = - new ArrayList<>(); - String n = name.toLowerCase().trim(); - - // 套装 - if (n.contains("皮革套装") - || n.contains("皮套装")) { - list.add(Material.LEATHER_HELMET); - list.add(Material.LEATHER_CHESTPLATE); - list.add(Material.LEATHER_LEGGINGS); - list.add(Material.LEATHER_BOOTS); - return list; - } - if (n.contains("铁套装") - || n.contains("铁甲")) { - list.add(Material.IRON_HELMET); - list.add(Material.IRON_CHESTPLATE); - list.add(Material.IRON_LEGGINGS); - list.add(Material.IRON_BOOTS); - return list; - } - if (n.contains("钻石套装") - || n.contains("钻甲")) { - list.add(Material.DIAMOND_HELMET); - list.add(Material.DIAMOND_CHESTPLATE); - list.add(Material.DIAMOND_LEGGINGS); - list.add(Material.DIAMOND_BOOTS); - return list; - } - // 木制工具套装 - if (n.contains("木制工具") - || n.contains("木工具")) { - list.add(Material.WOODEN_SWORD); - list.add(Material.WOODEN_PICKAXE); - list.add(Material.WOODEN_AXE); - list.add(Material.WOODEN_SHOVEL); - return list; - } - if (n.contains("石制工具") - || n.contains("石工具")) { - list.add(Material.STONE_SWORD); - list.add(Material.STONE_PICKAXE); - list.add(Material.STONE_AXE); - list.add(Material.STONE_SHOVEL); - return list; - } - if (n.contains("铁工具")) { - list.add(Material.IRON_SWORD); - list.add(Material.IRON_PICKAXE); - list.add(Material.IRON_AXE); - list.add(Material.IRON_SHOVEL); - return list; - } - - // 单个物品 - if (n.contains("木剑")) - list.add(Material.WOODEN_SWORD); - else if (n.contains("木镐")) - list.add(Material.WOODEN_PICKAXE); - else if (n.contains("铁剑")) - list.add(Material.IRON_SWORD); - else if (n.contains("铁镐")) - list.add(Material.IRON_PICKAXE); - else if (n.contains("钻石剑")) - list.add(Material.DIAMOND_SWORD); - else if (n.contains("钻石镐")) - list.add(Material.DIAMOND_PICKAXE); - else if (n.contains("钻石")) - list.add(Material.DIAMOND); - else if (n.contains("铁锭")) - list.add(Material.IRON_INGOT); - else if (n.contains("金锭") - || n.contains("金块")) - list.add(Material.GOLD_INGOT); - else if (n.contains("煤炭")) - list.add(Material.COAL); - else if (n.contains("木棍")) - list.add(Material.STICK); - else if (n.contains("弓")) - list.add(Material.BOW); - else if (n.contains("箭")) - list.add(Material.ARROW); - else if (n.contains("床")) - list.add(Material.RED_BED); - else { - // 尝试直接匹配 Material 名 - try { - list.add(Material.valueOf( - n.toUpperCase() - .replaceAll("[^A-Z_]", - ""))); - } catch (Exception e) { - // 未知物品 - } - } - return list; - } - - private QuestFile getOrCreateStage( - TreeMap map, - int key, - String category) { - if (!map.containsKey(key)) { - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + key; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.details = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - } - return map.get(key); - } - - /** - * 单个文件的区块 → 阶段 - * defaultStageNum: 文件无阶段标题时的默认编号 - */ - private void buildSingleFileStages( - List
sections, - String category, - TreeMap stageMap, - int defaultStageNum) { - Integer curKey = null; - - for (Section sec : sections) { - Integer stageNum = - extractStageNumber(sec.title); - - if (stageNum != null) { - curKey = stageNum; - } else if (curKey == null) { - curKey = defaultStageNum; - } - - QuestFile qf = getOrCreateStage( - stageMap, curKey, category); - - String displayName = - extractDisplayName(sec.title); - - // 设置显示名 - if (!displayName.isEmpty() - && qf.displayName - .startsWith("阶段 ")) { - qf.displayName = displayName; - } - - // 路由 - if (stageNum != null - && sec.lines.isEmpty()) { - if (!displayName.isEmpty()) { - qf.displayName = displayName; - } - } - else if (isRewardTitle(sec.title)) { - // 奖励 → 只进 rewards - qf.rewards.addAll(sec.lines); - } - else if (isDetailsTitle(sec.title)) { - // 详情 → 进 details + conditions - qf.details.addAll(sec.lines); - for (String line : sec.lines) { - if (!qf.conditions - .contains(line)) { - qf.conditions.add(line); - } - } - } - else if (isConditionsTitle( - sec.title)) { - // 条件 → 只进 conditions - qf.conditions.addAll(sec.lines); - } - else { - // 其他 → 条件 - qf.conditions.addAll(sec.lines); - } - } - } - - /** - * "主线任务-斗志昂扬" → "斗志昂扬" - * "支线-钓鱼" → "钓鱼" - * "阶段 1" → "阶段 1" - * "第一阶段" → "第一阶段" - */ - private String extractDisplayName( - String title) { - if (title == null) return ""; - // 按 - 或 — 或 – 分割 - String[] parts = title.split( - "\\s*[-—–]\\s*", 2); - if (parts.length >= 2) { - String right = parts[1].trim(); - if (!right.isEmpty()) { - return right; - } - } - return title; - } - private DatabaseManager getDb() { - return ((Main) plugin).getDb(); - } - - private boolean isDetailsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("任务详情") - || t.contains("详情") - || t.contains("任务说明") - || t.contains("任务目标") - || t.contains("description") - || t.contains("任务要求"); - } - - private boolean isConditionsTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("解锁条件") - || t.contains("条件") - || t.contains("前置条件") - || t.contains("前置要求") - || t.contains("解锁") - || t.contains("condition"); - } - - - private void ensureStage( - TreeMap map, - String category, - QuestFile current, - int counter) { - if (current != null) return; - // 用大数字确保排在有编号阶段后面 - int key = 1000 + counter; - QuestFile qf = new QuestFile(); - qf.displayName = "阶段 " + (counter + 1); - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - map.put(key, qf); - // 更新调用方的引用(通过返回值) - // 这里用 map 操作,current 无法直接更新 - // 所以改用返回值方式 - } - /** - * 自动将条件中的奖励内容移到 rewards - */ - private void separateRewards(QuestFile qf) { - List remaining = - new ArrayList<>(); - for (String line : qf.conditions) { - if (isRewardContent(line)) { - if (!qf.rewards.contains(line)) { - qf.rewards.add(line); - } - } else { - remaining.add(line); - } - } - qf.conditions = remaining; - } - private boolean checkStageDependency( - String player, - String condition) { - Matcher m = Pattern.compile( - "第([一二三四五六七八九十\\d]+)" - + "阶段") - .matcher(condition); - - while (m.find()) { - String numStr = m.group(1); - int stageNum = - parseChineseNumber(numStr); - if (stageNum == 0) { - try { - stageNum = - Integer.parseInt(numStr); - } catch (Exception e) { - return false; - } - } - - boolean stageDone = false; - for (List quests - : questCache.values()) { - for (QuestFile qf : quests) { - Integer qfNum = - extractStageNumber( - qf.displayName); - if (qfNum != null - && qfNum == stageNum) { - if (isStageComplete(player, - qf.conditions)) { - stageDone = true; - } - break; - } - } - if (stageDone) break; - } - - if (!stageDone) return false; - } - return true; - } - - /** - * 判断一行内容是否为奖励 - */ - private boolean isRewardContent( - String line) { - String l = line.toLowerCase(); - // 含货币关键词 - if (l.contains("游戏币") - || l.contains("金币") - || l.contains("积分") - || l.contains("钻石") - || l.contains("发钱") - || l.contains("发放") - || l.contains("经验") - || l.contains("等级") - || l.contains("经济") - || l.contains("余额") - || l.contains("块钱")) - return true; - // 纯数字+单位(如 "500游戏币") - if (l.matches( - ".*\\d+\\s*(游戏币|金币|元|积分|" - + "钻石|块钱|经验|级|经济|余额).*")) - return true; - // $前缀(如 "$500") - if (l.matches(".*\\$\\d+.*")) - return true; - // 范围格式(如 "1000-5000" 或 "1000~5000") - if (l.matches(".*\\d+[-~]\\d+.*") - && !l.contains("阶段") - && !l.contains("stage")) - return true; - return false; - } - - // ========== 标题分类 ========== - - private boolean isStageTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("阶段") - || t.contains("stage") - || t.contains("第一") - || t.contains("第二") - || t.contains("第三") - || t.contains("第四") - || t.contains("第五"); - } - - private boolean isRewardTitle( - String title) { - String t = title.toLowerCase(); - return t.contains("奖励") - || t.contains("奖品") - || t.contains("报酬") - || t.contains("reward") - || t.contains("奖赏") - || t.contains("礼包内容") - || t.contains("礼包") - || t.contains("物品奖励"); - } - - - private QuestFile makeDefaultStage( - String category) { - QuestFile qf = new QuestFile(); - qf.displayName = "第一阶段"; - qf.fileName = category; - qf.conditions = new ArrayList<>(); - qf.rewards = new ArrayList<>(); - return qf; - } - - private static class Section { - final String title; - final List lines; - Section(String title, - List lines) { - this.title = title; - this.lines = lines; - } - } - - // ========== 公开查询 ========== - - public List getQuests( - String category) { - List list = - questCache.get(category); - return list != null ? list - : new ArrayList<>(); - } - - public int getCompletedStageCount( - String player, - List quests) { - int count = 0; - if (quests == null) return 0; - for (QuestFile qf : quests) { - if (isStageComplete(player, - qf.conditions)) { - count++; - } - } - return count; - } - - public QuestFile getCurrentStage( - String player, - List quests) { - if (quests == null) return null; - for (QuestFile qf : quests) { - if (!isStageComplete( - player, qf.conditions)) { - return qf; - } - } - return null; - } - - public int getStageNumber( - List quests, - QuestFile qf) { - if (quests == null || qf == null) - return 0; - return quests.indexOf(qf) + 1; - } - - public int getStageNumber( - QuestFile qf) { - if (qf == null) return 0; - List quests = - getQuests(qf.fileName); - return getStageNumber(quests, qf); - } - - public boolean isStageCompleted( - String player, - QuestFile qf) { - if (qf == null) return false; - return isStageComplete( - player, qf.conditions); - } - - public List getUnmetConditions( - String player, - List conditions) { - List unmet = - new ArrayList<>(); - if (conditions == null) return unmet; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) { - unmet.add( - getProgress( - player, cond)); - } - } - return unmet; - } - public String getProgress( - String player, String rawCond) { - if (player == null || rawCond == null) - return "§7未知"; - String key = stripOptional(rawCond); - - if (key.contains("打卡") - && key.contains("群系")) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return "§7打卡 " + biome - + "群系: §e" - + catCount + "/1"; - } - int total = getCounter( - player, "biome_ALL"); - return "§7群系打卡: §e" - + total + "/1"; - } - - if (key.contains("完成") - && key.contains("阶段")) { - boolean done = checkStageDependency( - player, key); - return done - ? "§a✓ " + key - : "§c✗ " + key; - } - - if ("完成注册登录".equals(key)) { - boolean done = getDb() != null - && getDb().userExists( - player); - return done - ? "§a✓ 完成注册登录" - : "§c✗ 完成注册登录"; - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - boolean done = val != null - && !val.toString().isEmpty(); - return done - ? "§a✓ 绑定Email" - : "§c✗ 绑定Email"; - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + needed + "小时"; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - int cur = getCounter( - player, "CHAT"); - return "§7聊天: §e" - + cur + "/1"; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return "§7方块操作: §e" - + total + "/" + needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return "§7连续登录: §e" - + streak + "/" + needed + "天"; - } - - if (key.startsWith("MINE:")) { - String[] parts = key.split(":"); - if (parts.length >= 3) { - int needed = - parseInt(parts[2]); - int cur = getCounter( - player, - "MINE:" + parts[1]); - return "§7挖 " + parts[1] - + ": §e" + cur - + "/" + needed; - } - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "VILLAGER_TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - - } - - // ===== 击杀泛解析(支持中文列表) ===== -// 匹配 "击杀10只骷髅、僵尸、苦力怕" 等 - if (key.contains("击杀") && key.contains("只")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - - String mobPart = key; - java.util.regex.Matcher killM = - java.util.regex.Pattern - .compile("击杀\\d+只(.+)") - .matcher(key); - if (killM.find()) { - mobPart = killM.group(1).trim(); - } - - String[] mobNames = mobPart - .split("[、,,,]+"); - int totalKills = 0; - StringBuilder names = new StringBuilder(); - for (String name : mobNames) { - String trimmed = name.trim(); - if (trimmed.isEmpty()) continue; - String entityType = - chineseMobToEntity(trimmed); - if (!entityType.isEmpty()) { - totalKills += getCounter( - player, - "KILL:" + entityType); - if (names.length() > 0) - names.append("/"); - names.append(trimmed); - } - } - return "§7击杀" + names + ": §e" - + totalKills + "/" + needed; - } - -// 单一KILL:xxx:N 格式(原有逻辑保留) - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - int needed = parseInt(p[2]); - int cur = getCounter(player, - "KILL:" + p[1]); - return "§7击杀" + p[1] + ": §e" - + cur + "/" + needed; - } - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "KILL_PLAYER"); - return "§7击杀玩家: §e" - + cur + "/" + needed; - } - - if (key.startsWith("CRAFT")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "CRAFT"); - return "§7合成: §e" - + cur + "/" + needed; - } - - if (key.startsWith("FISH")) { - String[] parts = key.split(":"); - int needed = parts.length >= 2 - ? parseInt(parts[1]) : 1; - int cur = getCounter( - player, "FISH"); - return "§7钓鱼: §e" - + cur + "/" + needed; - } - - if (key.startsWith("PLAYTIME")) { - String[] parts = key.split(":"); - long needed = parts.length >= 2 - ? Long.parseLong(parts[1]) - : 0; - long cur = getCounterLong( - player, "PLAYTIME"); - return "§7在线: §e" - + formatMs(cur) + "/" - + formatMs(needed); - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7上报村庄: §e" - + cur + "/" + needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - int cur = getCounter( - player, "REPORT_VILLAGE"); - return "§7探索村庄: §e" - + cur + "/1"; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return "§7近7天登录: §e" - + streak + "/" - + needed + "天"; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7新手任务: §e" - + done + "/" - + quests.size(); - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - int done = 0; - for (QuestFile qf : quests) { - if (isStageComplete( - player, - qf.conditions)) - done++; - } - return "§7主线任务: §e" - + done + "/" - + quests.size(); - } - // ===== 泛解析:击杀类进度 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < mobTypes.size(); i++) { - total += getCounter(player, - "KILL:" + mobTypes.get(i)); - if (i > 0) names.append("/"); - names.append(reverseMobName( - mobTypes.get(i))); - } - return "§7击杀" + names + ": §e" - + total + "/" + needed; - } - } - - // ===== 泛解析:村民交易进度 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "TRADE"); - return "§7村民交易: §e" - + cur + "/" + needed; - } - // ===== 泛解析:采集/挖矿进度 ===== - if (key.contains("采集") - || key.contains("挖")) { - int needed = - parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - StringBuilder names = - new StringBuilder(); - for (int i = 0; - i < matTypes.size(); i++) { - total += getCounter( - player, - "MINE:" + matTypes - .get(i)); - if (i > 0) - names.append("/"); - names.append( - translateMaterialName( - matTypes - .get(i))); - } - return "§7采集" + names - + ": §e" + total - + "/" + needed; - } - } - - // ===== 泛解析:合成进度 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "CRAFT"); - return "§7合成: §e" + cur + "/" + needed; - } - - // ===== 泛解析:钓鱼进度 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int cur = getCounter(player, "FISH"); - return "§7钓鱼: §e" + cur + "/" + needed; - } - - return "§7" + key; - } - private String translateMaterialName( - String name) { - switch (name) { - case "COAL_ORE": - return "煤矿石"; - case "DEEPSLATE_COAL_ORE": - return "深层煤矿石"; - case "IRON_ORE": - return "铁矿石"; - case "DEEPSLATE_IRON_ORE": - return "深层铁矿石"; - case "COPPER_ORE": - return "铜矿石"; - case "DEEPSLATE_COPPER_ORE": - return "深层铜矿石"; - case "GOLD_ORE": - return "金矿石"; - case "DEEPSLATE_GOLD_ORE": - return "深层金矿石"; - case "DIAMOND_ORE": - return "钻石矿石"; - case "DEEPSLATE_DIAMOND_ORE": - return "深层钻石矿石"; - case "EMERALD_ORE": - return "绿宝石矿石"; - case "DEEPSLATE_EMERALD_ORE": - return "深层绿宝石矿石"; - case "LAPIS_ORE": - return "青金石矿石"; - case "DEEPSLATE_LAPIS_ORE": - return "深层青金石矿石"; - case "REDSTONE_ORE": - return "红石矿石"; - case "DEEPSLATE_REDSTONE_ORE": - return "深层红石矿石"; - case "NETHER_GOLD_ORE": - return "下界金矿石"; - case "NETHER_QUARTZ_ORE": - return "下界石英矿石"; - case "ANCIENT_DEBRIS": - return "远古残骸"; - case "COAL": - return "煤炭"; - case "RAW_IRON": - return "粗铁"; - case "RAW_GOLD": - return "粗金"; - case "RAW_COPPER": - return "粗铜"; - case "DIAMOND": - return "钻石"; - case "EMERALD": - return "绿宝石"; - case "LAPIS_LAZULI": - return "青金石"; - case "REDSTONE": - return "红石"; - case "QUARTZ": - return "石英"; - default: - return name; - } - } - - /** - * 中文物种名 → Bukkit EntityType 名 - * 支持击杀泛解析的模糊匹配 - */ - private String chineseMobToEntity(String cn) { - String n = cn.toLowerCase().trim(); - - // 直接别名表 - Map mobMap = new HashMap<>(); - mobMap.put("骷髅", "SKELETON"); - mobMap.put("僵尸", "ZOMBIE"); - mobMap.put("苦力怕", "CREEPER"); - mobMap.put("蜘蛛", "SPIDER"); - mobMap.put("洞穴蜘蛛", "CAVE_SPIDER"); - mobMap.put("小白", "SKELETON"); - mobMap.put("末影人", "ENDERMAN"); - mobMap.put("enderman", "ENDERMAN"); - mobMap.put("猪灵", "PIGLIN"); - mobMap.put("猪灵蛮兵", "PIGLIN_BRUTE"); - mobMap.put("恶魂", "GHAST"); - mobMap.put("凋灵骷髅", "WITHER_SKELETON"); - mobMap.put("烈焰人", "BLAZE"); - mobMap.put("史莱姆", "SLIME"); - mobMap.put("岩浆怪", "MAGMA_CUBE"); - mobMap.put("女巫", "WITCH"); - mobMap.put("蠹虫", "SILVERFISH"); - mobMap.put("潜影贝", "SHULKER"); - mobMap.put("守卫者", "GUARDIAN"); - mobMap.put("远古守卫者", "ELDER_GUARDIAN"); - mobMap.put("溺尸", "DROWNED"); - mobMap.put("僵尸村民", "ZOMBIE_VILLAGER"); - mobMap.put("尸壳", "HUSK"); - mobMap.put("流浪者", "STRAY"); - mobMap.put("幻翼", "PHANTOM"); - mobMap.put("掠夺者", "PILLAGER"); - mobMap.put("卫道士", "VINDICATOR"); - mobMap.put("唤魔者", "EVOKER"); - mobMap.put("恼鬼", "VEX"); - mobMap.put("劫掠兽", "RAVAGER"); - mobMap.put("蜘蛛骑士", "SPIDER_JOCKEY"); - mobMap.put("凋灵", "WITHER"); - mobMap.put("末影龙", "ENDER_DRAGON"); - - // 精确匹配 - if (mobMap.containsKey(n)) { - return mobMap.get(n); - } - - // 模糊匹配(包含关系) - for (Map.Entry en - : mobMap.entrySet()) { - if (n.contains(en.getKey()) - || en.getKey().contains(n)) { - return en.getValue(); - } - } - - // 尝试直接用英文名 - try { - return org.bukkit.entity.EntityType - .valueOf(n.toUpperCase() - .replace(" ", "_")) - .name(); - } catch (Exception e) { - return ""; - } - } - - public List checkInBiome( - String player, - String rawBiomeKey, - String worldName, - double x, double y, double z) { - List matched = - new ArrayList<>(); - - // 32格去重 - String posKey = - player + "_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - String lastPos = - lastCheckInPos.get(posKey); - if (curPos.equals(lastPos)) { - plugin.getLogger().info( - "[QuestTracker] 打卡忽略: " - + player - + " 32格内重复"); - return matched; - } - lastCheckInPos.put(posKey, curPos); - - String category = - mapBiomeCategory( - rawBiomeKey, worldName); - // 结构检测(覆盖纯生物群系分类) - // 地下检测:Y低于表面则归类为地下 -// 覆盖废弃矿井/试炼密室/要塞/远古城市等 - try { - org.bukkit.World uw = - Bukkit.getWorld(worldName); - if (uw != null) { - int surfaceY = uw - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - } - } catch (Exception ignored) {} - - // ===== 结构检测(覆盖纯生物群系分类)===== - // 使用 PaperMC API 检测玩家所在位置的结构 - String structureCat = - detectStructureAt( - worldName, x, y, z); - if (structureCat != null - && !structureCat.isEmpty()) { - // 结构检测成功,使用结构分类 - // 但保留"地下"分类(如果Y低于表面) - if (!"地下".equals(category)) { - category = structureCat; - } - plugin.getLogger().info( - "[QuestTracker] 结构覆盖: " - + category - + " (结构: " - + structureCat + ")"); - } - - - // 记录总打卡 - int allCur = getCounter( - player, "biome_ALL"); - setCounter(player, "biome_ALL", - allCur + 1); - - // 记录分类打卡 - if (!category.isEmpty() - && !"主世界" - .equals(category)) { - String catKey = - "biomecat_" + category; - int catCur = getCounter( - player, catKey); - setCounter(player, catKey, - catCur + 1); - } - - // 记录具体群系 - String bioKey = - "biome_" + rawBiomeKey; - int bioCur = getCounter( - player, bioKey); - setCounter(player, bioKey, - bioCur + 1); - - // 检查所有任务条件是否匹配 - List categories = - Arrays.asList( - "新人任务", - "主线任务", - "支线任务"); - - for (String cat : categories) { - List quests = - getQuests(cat); - for (QuestFile qf : quests) { - if (isStageCompleted( - player, qf)) - continue; - for (String cond - : qf.conditions) { - if (isOptional(cond)) - continue; - String condKey = - stripOptional(cond); - if (!condKey - .contains("打卡")) - continue; - // 支持群系和结构两种打卡条件 - boolean isBiomeCond = - condKey.contains("群系") - || condKey - .contains("风景点"); - boolean isStructureCond = - condKey.contains("结构") - || condKey - .contains("村庄") - || condKey - .contains("神殿") - || condKey - .contains("神庙") - || condKey - .contains("小屋") - || condKey - .contains("雪屋") - || condKey - .contains("府邸") - || condKey - .contains("前哨站") - || condKey - .contains("矿井") - || condKey - .contains("要塞") - || condKey - .contains("城市") - || condKey - .contains("废墟") - || condKey - .contains("密室") - || condKey - .contains("沉船") - || condKey - .contains("宝箱") - || condKey - .contains("传送门") - || condKey - .contains("化石") - || condKey - .contains("堡垒"); - if (!isBiomeCond - && !isStructureCond) - continue; - String target = - extractBiome(condKey); - boolean hit = false; - if ("ALL".equals(target) - || "风景点" - .equals(target)) { - hit = true; - } else if (!target.isEmpty() - && target.equals( - category)) { - hit = true; - } - if (hit && !matched - .contains(cond)) { - matched.add(cond); - } - } - } - } - - return matched; - } - - /* - * 打卡入口:报告坐标 → 32格去重 → 匹配条件 → 记录 - */ - public String checkInAtPosition( - String player, String worldName, - double x, double y, double z) { - - List history = - loadCheckInPositions(player); - for (double[] pos : history) { - double dist = Math.sqrt( - Math.pow(x - pos[0], 2) - + Math.pow(y - pos[1], 2) - + Math.pow(z - pos[2], 2)); - if (dist <= 32) { - return "§c距离上次打卡点太近(" - + String.format( - "%.0f", dist) - + "格)"; - } - } - - // ===== 获取群系和分类 ===== - String biome = "minecraft:plains"; - String category = "平原"; - String structureCat = null; - try { - org.bukkit.World w = - Bukkit.getWorld(worldName); - if (w != null) { - org.bukkit.Location loc = - new org.bukkit.Location( - w, - (int) Math.floor(x), - (int) Math.floor(y), - (int) Math.floor(z)); - biome = loc.getBlock() - .getBiome().getKey() - .toString(); - category = mapBiomeCategory( - biome, worldName); - - int surfaceY = w - .getHighestBlockYAt( - (int) Math.floor(x), - (int) Math.floor(z)); - if (y < surfaceY - 8) { - category = "地下"; - } - - // ===== 结构检测(覆盖纯生物群系分类)===== - structureCat = - detectStructureAt( - worldName, x, y, z); - if (structureCat != null - && !structureCat.isEmpty() - && !"地下".equals(category)) { - category = structureCat; - } - - if ("下界".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.NETHER) { - category = "下界"; - } else if ("末地".equals(category) - || w.getEnvironment() - == org.bukkit.World - .Environment.THE_END) { - category = "末地"; - } - } - } catch (Exception ex) { - plugin.getLogger().warning( - "[QuestTracker] 群系读取失败: " - + ex.getMessage()); - } - - plugin.getLogger().info( - "[QuestTracker] 打卡: " - + player - + " world=" + worldName - + " biome=" + biome - + " cat=" + category - + " pos=" - + String.format( - "%.0f,%.0f,%.0f", - x, y, z)); - - saveCheckInPosition( - player, x, y, z); - recordCheckIn( - player, biome, category, - worldName, x, y, z); - List matched = - checkInBiome( - player, biome, - worldName, x, y, z); - - StringBuilder sb = new StringBuilder(); - sb.append("§a打卡成功!群系: §e") - .append(category); - if (structureCat != null && !structureCat.isEmpty()) { - sb.append("\n§a结构: §e").append(structureCat); - } - if (!matched.isEmpty()) { - sb.append("\n§a完成任务条件:"); - for (String m : matched) { - String qn = - findQuestName(m); - sb.append("\n§7• "); - if (qn != null) { - sb.append("§e[") - .append(qn) - .append("§7] §f"); - } - sb.append(m); - } - } - return sb.toString(); - } - - - /** - * 根据条件文本查找所属任务名 - */ - private String findQuestName( - String condition) { - for (String cat : Arrays.asList( - "新人任务", - "主线任务", - "支线任务")) { - for (QuestFile qf : - getQuests(cat)) { - if (qf.conditions.contains( - condition)) { - return qf.displayName; - } - } - } - return null; - } - - - /** - * 加载玩家所有历史打卡坐标 - * 格式: x1,y1,z1;x2,y2,z2;... - */ - private List loadCheckInPositions( - String player) { - List list = new ArrayList<>(); - Object val = storage.getCondition( - player, "checkin_all_pos"); - if (val == null) return list; - String data = val.toString(); - if (data.isEmpty()) return list; - String[] records = data.split(";"); - for (String r : records) { - String[] parts = r.split(","); - if (parts.length >= 3) { - try { - list.add(new double[]{ - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2])}); - } catch (NumberFormatException e) { - // 跳过损坏数据 - } - } - } - return list; - } - - /** - * 保存打卡坐标(追加到已有记录) - */ - private void saveCheckInPosition( - String player, double x, - double y, double z) { - List list = - loadCheckInPositions(player); - list.add(new double[]{x, y, z}); - StringBuilder sb = new StringBuilder(); - for (double[] pos : list) { - if (sb.length() > 0) sb.append(";"); - sb.append(pos[0]).append(",") - .append(pos[1]).append(",") - .append(pos[2]); - } - storage.setConditionRaw( - player, - "checkin_all_pos", - sb.toString()); - } - // ========== 中文生物名 → EntityType 映射 ========== - private static final Map - MOB_NAME_MAP = new HashMap<>(); - static { - MOB_NAME_MAP.put("洞穴蜘蛛", "CAVE_SPIDER"); - MOB_NAME_MAP.put("骷髅", "SKELETON"); - MOB_NAME_MAP.put("僵尸", "ZOMBIE"); - MOB_NAME_MAP.put("苦力怕", "CREEPER"); - MOB_NAME_MAP.put("蜘蛛", "SPIDER"); - MOB_NAME_MAP.put("末影人", "ENDERMAN"); - MOB_NAME_MAP.put("女巫", "WITCH"); - MOB_NAME_MAP.put("烈焰人", "BLAZE"); - MOB_NAME_MAP.put("僵尸猪灵", - "ZOMBIFIED_PIGLIN"); - MOB_NAME_MAP.put("猪灵", "PIGLIN"); - MOB_NAME_MAP.put("恶魂", "GHAST"); - MOB_NAME_MAP.put("凋灵骷髅", - "WITHER_SKELETON"); - MOB_NAME_MAP.put("史莱姆", "SLIME"); - MOB_NAME_MAP.put("岩浆怪", "MAGMA_CUBE"); - MOB_NAME_MAP.put("溺尸", "DROWNED"); - MOB_NAME_MAP.put("尸壳", "HUSK"); - MOB_NAME_MAP.put("流髑", "STRAY"); - MOB_NAME_MAP.put("幻翼", "PHANTOM"); - MOB_NAME_MAP.put("守卫者", "GUARDIAN"); - MOB_NAME_MAP.put("远古守卫者", - "ELDER_GUARDIAN"); - MOB_NAME_MAP.put("掠夺者", "PILLAGER"); - MOB_NAME_MAP.put("卫道士", "VINDICATOR"); - MOB_NAME_MAP.put("唤魔者", "EVOKER"); - MOB_NAME_MAP.put("恼鬼", "VEX"); - MOB_NAME_MAP.put("劫掠兽", "RAVAGER"); - MOB_NAME_MAP.put("潜影贝", "SHULKER"); - MOB_NAME_MAP.put("末影螨", "ENDERMITE"); - MOB_NAME_MAP.put("凋灵", "WITHER"); - MOB_NAME_MAP.put("末影龙", "ENDER_DRAGON"); - MOB_NAME_MAP.put("监守者", "WARDEN"); - MOB_NAME_MAP.put("嗅探兽", "SNIFFER"); - MOB_NAME_MAP.put("僵尸村民", - "ZOMBIE_VILLAGER"); - } - - /** - * 从条件文本中提取所有匹配的生物类型 - * 按名称长度降序匹配,避免短名误匹配 - */ - private List extractMobTypes( - String text) { - List types = new ArrayList<>(); - List> sorted = - new ArrayList<>( - MOB_NAME_MAP.entrySet()); - sorted.sort((a, b) -> - b.getKey().length() - - a.getKey().length()); - String remaining = text; - for (Map.Entry entry - : sorted) { - if (remaining.contains( - entry.getKey())) { - if (!types.contains( - entry.getValue())) { - types.add(entry.getValue()); - } - remaining = remaining.replace( - entry.getKey(), ""); - } - } - return types; - } - - /** - * EntityType → 中文名 - */ - private String reverseMobName( - String entityType) { - for (Map.Entry entry - : MOB_NAME_MAP.entrySet()) { - if (entry.getValue() - .equals(entityType)) { - return entry.getKey(); - } - } - return entityType; - } - - /** - * 从条件文本中提取矿物名 - * "采集任意矿物(煤炭除外)1组" - * → [COAL_ORE, IRON_ORE, GOLD_ORE, ...] - */ - private List extractMaterialTypes( - String text) { - List types = new ArrayList<>(); - // 排除列表 - List excludes = - new ArrayList<>(); - if (text.contains("煤炭除外")) { - excludes.add("COAL"); - excludes.add("COAL_ORE"); - excludes.add("DEEPSLATE_COAL_ORE"); - } - // 通配符:"矿物" "矿石" - if (text.contains("矿物") - || text.contains("矿石")) { - String[] ores = { - "COAL_ORE", - "DEEPSLATE_COAL_ORE", - "IRON_ORE", - "DEEPSLATE_IRON_ORE", - "COPPER_ORE", - "DEEPSLATE_COPPER_ORE", - "GOLD_ORE", - "DEEPSLATE_GOLD_ORE", - "DIAMOND_ORE", - "DEEPSLATE_DIAMOND_ORE", - "EMERALD_ORE", - "DEEPSLATE_EMERALD_ORE", - "LAPIS_ORE", - "DEEPSLATE_LAPIS_ORE", - "REDSTONE_ORE", - "DEEPSLATE_REDSTONE_ORE", - "NETHER_GOLD_ORE", - "NETHER_QUARTZ_ORE", - "ANCIENT_DEBRIS" - }; - for (String ore : ores) { - if (!excludes.contains(ore)) { - types.add(ore); - } - } - return types; - } - // 单个矿物名 - if (text.contains("煤炭")) - types.add("COAL"); - if (text.contains("铁矿") - || text.contains("铁锭")) - types.add("RAW_IRON"); - if (text.contains("金矿") - || text.contains("金锭")) - types.add("RAW_GOLD"); - if (text.contains("钻石")) - types.add("DIAMOND"); - if (text.contains("绿宝石")) - types.add("EMERALD"); - if (text.contains("青金石")) - types.add("LAPIS_LAZULI"); - if (text.contains("红石")) - types.add("REDSTONE"); - if (text.contains("铜")) - types.add("RAW_COPPER"); - if (text.contains("下界石英") - || text.contains("石英")) - types.add("QUARTZ"); - return types; - } - - - /** - * 从条件文本中提取目标群系分类 - * "打卡上报地狱任意群系" → "下界" - * "打卡任意海洋群系" → "海洋" - * "打卡任意群系" → "ALL" - * "打卡任意风景点" → "" - * "打卡村庄" → "村庄" - * "打卡沙漠神殿" → "沙漠" - */ - private String extractBiome(String text) { - if (text == null) return ""; - - // 优先匹配结构名称(更具体) - String structureResult = - mapStructureCategory(text); - if (!structureResult.isEmpty()) { - return structureResult; - } - - // 逐个匹配分类关键词 - for (Map.Entry en - : CATEGORY_KEYWORDS.entrySet()) { - if (text.contains(en.getKey())) { - return en.getValue(); - } - } - if (text.contains("风景点") - || text.contains("风景")) { - return "ALL"; - } - - // 有"群系"但无具体分类 → ALL - if (text.contains("群系")) { - return "ALL"; - } - return ""; - } - - - // ========== 标记条件 ========== - - public void markConditionComplete( - String player, - String conditionName) { - if (player == null - || conditionName == null) - return; - storage.setCondition( - player, conditionName); - } - - // ========== 事件回调 ========== - - public void onBlockMined( - String player, String material) { - String key = "MINE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - // 总计数 - setCounter(player, "MINE:ALL", - getCounter(player, "MINE:ALL") - + 1); - } - public boolean onReportVillage( - String player, - String worldName, - double x, double y, double z) { - String dbKey = "village_lastPos"; - String curPos = worldName - + "|" + String.format( - "%.0f", x / 32) - + "|" + String.format( - "%.0f", y / 32) - + "|" + String.format( - "%.0f", z / 32); - Object lastObj = - storage.getCondition( - player, dbKey); - String lastPos = lastObj != null - ? lastObj.toString() : ""; - if (curPos.equals(lastPos)) { - return false; - } - storage.setConditionRaw( - player, dbKey, curPos); - - String key = "REPORT_VILLAGE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - return true; - } - - public void onMobKill( - String player, String mobType) { - String key = "KILL:" + mobType; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayerKill( - String player) { - String key = "KILL_PLAYER"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onItemCrafted( - String player) { - String key = "CRAFT"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onFishCaught( - String player) { - String key = "FISH"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - public void onPlayTime( - String player, long ms) { - String key = "PLAYTIME"; - long cur = getCounterLong( - player, key); - setCounter(player, key, cur + ms); - } - - // ========== 内部:条件判定 ========== - - private boolean isStageComplete( - String player, - List conditions) { - if (conditions == null - || conditions.isEmpty()) - return true; - for (String cond : conditions) { - if (isOptional(cond)) continue; - if (!isConditionMet( - player, cond)) - return false; - } - return true; - } - - public boolean isConditionMet( - String player, - String rawCond) { - if (player == null - || rawCond == null) - return false; - - String key = stripOptional(rawCond); - - // 无条件类文本直接通过 - if (key.equals("无前级条件") - || key.equals("无条件") - || key.equals("无") - || key.equals("none") - || key.equals("无需条件") - || key.equals("无解锁条件")) { - return true; - } - - if (key.contains("打卡") - && (key.contains("群系") - || key.contains("风景点") - || key.contains("结构") - || key.contains("村庄") - || key.contains("神殿") - || key.contains("神庙") - || key.contains("小屋") - || key.contains("雪屋") - || key.contains("府邸") - || key.contains("前哨站") - || key.contains("矿井") - || key.contains("要塞") - || key.contains("城市") - || key.contains("废墟") - || key.contains("密室") - || key.contains("废墟") - || key.contains("沉船") - || key.contains("宝箱") - || key.contains("传送门") - || key.contains("化石") - || key.contains("堡垒"))) { - String biome = - extractBiome(key); - if ("ALL".equals(biome)) { - int total = getCounter( - player, "biome_ALL"); - return total >= 1; - } - if (!biome.isEmpty()) { - int catCount = getCounter( - player, - "biomecat_" + biome); - return catCount >= 1; - } - return getCounter( - player, "biome_ALL") >= 1; - } - - if (key.contains("完成") - && key.contains("阶段")) { - return checkStageDependency( - player, key); - } - - if ("完成注册登录".equals(key)) { - return getDb() != null - && getDb().userExists( - player); - } - - if (key.contains("绑定Email") - || key.contains("绑定邮箱")) { - Object val = - getDb().getField( - player, "email"); - return val != null - && !val.toString().isEmpty(); - } - - if (key.contains("在线") - && key.contains("小时")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - long cur = getCounterLong( - player, "PLAYTIME"); - return cur >= needed * 3600000L; - } - - if (key.contains("打招呼") - || key.contains("打个招呼") - || key.contains("发消息") - || key.contains("聊天")) { - return getCounter( - player, "CHAT") >= 1; - } - - if ((key.contains("破坏") - || key.contains("放置")) - && key.contains("方块")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int total = getCounter( - player, "MINE:ALL") - + getCounter( - player, "PLACE:ALL"); - return total >= needed; - } - - if (key.contains("连续") - && key.contains("登录")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - Object val = - getDb().getField( - player, - "checkin_streak"); - int streak = val instanceof Number - ? ((Number) val).intValue() - : 0; - return streak >= needed; - } - - if (key.startsWith("MINE:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "MINE:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.startsWith("KILL:")) { - String[] p = key.split(":"); - if (p.length >= 3) { - return getCounter(player, - "KILL:" + p[1]) - >= parseInt(p[2]); - } - } - - if (key.contains("村民") - && key.contains("交易")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "VILLAGER_TRADE") - >= needed; - } - - - if (key.startsWith("KILL_PLAYER")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter(player, - "KILL_PLAYER") >= n; - } - - if (key.startsWith("CRAFT")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "CRAFT") >= n; - } - - if (key.startsWith("FISH")) { - String[] p = key.split(":"); - int n = p.length >= 2 - ? parseInt(p[1]) : 1; - return getCounter( - player, "FISH") >= n; - } - - if (key.startsWith("PLAYTIME")) { - String[] p = key.split(":"); - long n = p.length >= 2 - ? Long.parseLong(p[1]) : 0; - return getCounterLong(player, - "PLAYTIME") >= n; - } - - if (key.contains("上报") - && key.contains("村庄")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter( - player, "REPORT_VILLAGE") - >= needed; - } - - if (key.contains("探索") - && key.contains("村庄")) { - return getCounter( - player, "REPORT_VILLAGE") - >= 1; - } - - if (key.contains("7天") - || key.contains("七天")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - int streak = getIntField( - player, - "checkin_streak"); - if (streak == 0) { - streak = getIntField( - player, "streak"); - } - if (streak == 0) { - streak = getIntField( - player, - "total_checkin_days"); - } - return streak >= needed; - } - - if (key.contains("新手任务") - && key.contains("全部")) { - List quests = - getQuests("新人任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - - if (key.contains("主线任务") - && key.contains("全部")) { - List quests = - getQuests("主线任务"); - for (QuestFile qf : quests) { - if (!isStageComplete( - player, - qf.conditions)) - return false; - } - return true; - } - // ===== 泛解析:击杀类中文条件 ===== - if (key.contains("击杀") || key.contains("猎杀") - || key.contains("消灭")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List mobTypes = extractMobTypes(key); - if (!mobTypes.isEmpty()) { - int total = 0; - for (String mobType : mobTypes) { - total += getCounter(player, - "KILL:" + mobType); - } - return total >= needed; - } - } - - // ===== 泛解析:村民交易条件 ===== - if (key.contains("交易") - && (key.contains("村民") - || key.contains("与村民"))) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "TRADE") - >= needed; - } - - // ===== 泛解析:采集/挖矿类条件 ===== - if (key.contains("采集") || key.contains("挖")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - List matTypes = - extractMaterialTypes(key); - if (!matTypes.isEmpty()) { - int total = 0; - for (String mat : matTypes) { - total += getCounter(player, - "MINE:" + mat); - } - return total >= needed; - } - } - - // ===== 泛解析:合成类条件 ===== - if (key.contains("合成") || key.contains("制作")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "CRAFT") - >= needed; - } - - // ===== 泛解析:钓鱼类条件 ===== - if (key.contains("钓鱼") || key.contains("钓")) { - int needed = parseNumberFromText(key); - if (needed <= 0) needed = 1; - return getCounter(player, "FISH") - >= needed; - } - - return false; - } - - public void debugLogin(String player) { - try { - var user = getDb().getUser(player); - if (user != null) { - plugin.getLogger().info( - "[QuestTracker] " - + "=== " + player - + " 数据库全部字段 ==="); - for (var entry - : user.entrySet()) { - plugin.getLogger().info( - "[QuestTracker] " - + entry.getKey() - + " = " - + entry.getValue() - + " (" - + (entry.getValue() - != null - ? entry.getValue() - .getClass() - .getSimpleName() - : "null") - + ")"); - } - } else { - plugin.getLogger().info( - "[QuestTracker] " - + "getUser返回null: " - + player); - } - } catch (Exception e) { - plugin.getLogger().info( - "[QuestTracker] " - + "debug异常: " - + e.getMessage()); - } - } - - // ========== 内部:计数器 ========== - - private int getCounter( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - return 0; - } - private int getIntField( - String player, String field) { - try { - Object val = getDb().getField( - player, field); - if (val instanceof Number) { - return ((Number) val).intValue(); - } - if (val != null) { - return Integer.parseInt( - val.toString().trim()); - } - } catch (Exception e) { - // 忽略 - } - return 0; - } - - public void onVillagerTrade(String player) { - String key = "VILLAGER_TRADE"; - int cur = getCounter(player, key); - setCounter(player, key, cur + 1); - } - - private long getCounterLong( - String player, String key) { - Object val = storage.getCondition( - player, "cnt_" + key); - if (val instanceof Number) { - return ((Number) val).longValue(); - } - return 0; - } - - private void setCounter( - String player, String key, - int value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - private void setCounter( - String player, String key, - long value) { - storage.setConditionRaw( - player, "cnt_" + key, - String.valueOf(value)); - } - - // ========== 工具 ========== - - private boolean toBool(Object val) { - if (val == null) return false; - if (val instanceof Boolean) - return (Boolean) val; - if (val instanceof Number) - return ((Number) val) - .intValue() != 0; - String s = String.valueOf(val); - return "1".equals(s) - || "true" - .equalsIgnoreCase(s); - } - - public boolean isOptional(String cond) { - if (cond == null) return false; - String c = cond; - return c.contains("(进阶)") - || c.contains("(可选)") - || c.contains(",可选)") - || c.contains(",可选)") - || c.contains("【可选】") - || c.contains("【进阶】"); - } - - - private String stripOptional(String cond) { - if (cond == null) return ""; - return cond.replace("(进阶)", "") - .replace("(可选)", "") - .replace(",可选)", "") - .replace(",可选)", "") - .replace("【可选】", "") - .replace("【进阶】", "") - .trim(); - } - - private int parseInt(String s) { - - try { - return Integer.parseInt(s.trim()); - } catch (Exception e) { - return 0; - } - } - private Object getDbField( - String player, String field) { - Main main = (Main) plugin; - return main.getDb().getField( - player, field); - } - - /** - * 从文本中提取第一个数字 - * "在线时间≥1小时" → 1 - * "破坏/放置方块100个" → 100 - */ - private int parseNumberFromText( - String text) { - java.util.regex.Matcher m = - java.util.regex.Pattern - .compile("(\\d+)") - .matcher(text); - if (m.find()) { - try { - return Integer.parseInt( - m.group(1)); - } catch (Exception e) { - return 0; - } - } - return 0; - } - public void onBlockPlaced( - String player, String material) { - String key = "PLACE:" + material; - setCounter(player, key, - getCounter(player, key) + 1); - setCounter(player, "PLACE:ALL", - getCounter(player, "PLACE:ALL") - + 1); - } - - public void onPlayerChat( - String player) { - String key = "CHAT"; - setCounter(player, key, - getCounter(player, key) + 1); - } - - private String formatMs(long ms) { - long sec = ms / 1000; - long min = sec / 60; - long hr = min / 60; - if (hr > 0) - return hr + "小时" - + (min % 60) + "分"; - if (min > 0) - return min + "分" - + (sec % 60) + "秒"; - return sec + "秒"; - } - - // ========== 数据结构 ========== - - public static class QuestFile { - public String displayName = ""; - public String fileName = ""; - public List details = - new ArrayList<>(); - public List rewards = - new ArrayList<>(); - public List conditions = - new ArrayList<>(); - - public boolean hasConditions() { - return conditions != null - && !conditions.isEmpty(); - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RadioDownloadListener.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/RadioDownloadListener.java deleted file mode 100644 index d3e744f..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RadioDownloadListener.java +++ /dev/null @@ -1,144 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player - .PlayerResourcePackStatusEvent; - -import java.util.UUID; -import java.util.concurrent - .ConcurrentHashMap; - -public class RadioDownloadListener - implements Listener { - - private final Main plugin; - private final java.util.logging.Logger log; - - private final ConcurrentHashMap - failCount = new ConcurrentHashMap<>(); - private final ConcurrentHashMap - blocked = new ConcurrentHashMap<>(); - - private static final int MAX_FAILED = 3; - - public RadioDownloadListener(Main plugin) { - this.plugin = plugin; - this.log = plugin.getLogger(); - } - - public void onPlayerQuit(UUID uuid) { - failCount.remove(uuid); - blocked.remove(uuid); - } - - @EventHandler - public void onStatus( - PlayerResourcePackStatusEvent e) { - Player p = e.getPlayer(); - UUID uuid = p.getUniqueId(); - String status = - e.getStatus().name(); - - log.info("[Radio] === 资源包状态 ==="); - log.info("[Radio] 玩家: " - + p.getName()); - log.info("[Radio] UUID: " + uuid); - log.info("[Radio] 状态: " - + status); - log.info("[Radio] 原始状态枚举: " - + e.getStatus().getClass() - .getSimpleName() - + "." - + e.getStatus().name()); - - if (blocked.containsKey(uuid)) { - log.info("[Radio] 已熔断,跳过: " - + p.getName()); - return; - } - - switch (status) { - case "SUCCESS": - log.info("[Radio] " - + p.getName() - + " ✓ 资源包加载成功"); - failCount.remove(uuid); - blocked.remove(uuid); - break; - - case "ACCEPTED": - log.info("[Radio] " - + p.getName() - + " 客户端已接受," - + "等待下载..."); - break; - - case "DECLINED": - log.warning("[Radio] " - + p.getName() - + " ✗ 拒绝了资源包"); - log.warning("[Radio] " - + "可能原因: " - + "1.客户端设置禁用资源包" - + " 2.已缓存拒绝记录" - + " 3.SSL证书不匹配"); - handleFailure(p, uuid); - break; - - case "FAILED_DOWNLOAD": - log.warning("[Radio] " - + p.getName() - + " ✗ 下载失败"); - log.warning("[Radio] " - + "可能原因: " - + "1.URL无法访问" - + " 2.SSL证书错误" - + " 3.网络超时" - + " 4.文件不存在(404)"); - handleFailure(p, uuid); - break; - - default: - log.info("[Radio] " - + p.getName() - + " 未知状态: " - + status); - break; - } - } - - private void handleFailure(Player p, UUID uuid) { - int failNum = failCount.getOrDefault(uuid, 0) + 1; - failCount.put(uuid, failNum); - - log.warning("[Radio] " + p.getName() - + " 失败次数: " + failNum + "/" + MAX_FAILED); - - if (failNum >= MAX_FAILED) { - blocked.put(uuid, true); - log.severe("[Radio] " + p.getName() - + " 已达最大失败次数,停止重试," - + "玩家继续留在服务器"); - // 不踢出,只记录熔断,玩家正常游玩 - p.sendMessage("§e§l[Radio] 资源包加载失败" - + ",已取消下载资源包,部分画面音频可能出现问题" - + "\n§d§l若想继续下载,请退出重进。若持续失败,请上报管理员"); - - return; - } -// 打了包没测试 - p.sendMessage("§e§l[Radio] 下载失败," - + "重试... (" + failNum + "/" + MAX_FAILED + ")"); - - // 5秒后重试 - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline() && plugin.radio != null) { - plugin.radio.sendResourcePack(p); - } - }, 100L); - } -} \ No newline at end of file diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RadioManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/RadioManager.java deleted file mode 100644 index 6fe5eda..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RadioManager.java +++ /dev/null @@ -1,1040 +0,0 @@ -package Sdf1_login; - -import org.bukkit.*; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; - -import java.io.*; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.zip.*; -import java.util.Collections; -import org.bukkit.NamespacedKey; -import java.util.Set; - - -public class RadioManager { - - private final Main plugin; - private File radioDir; - private File oggDir; - private File packDir; - private BukkitTask mainRadioTask; - private final Map loginPlayers = - new ConcurrentHashMap<>(); - private String externalUrl = ""; - private int httpPort = 443; - private final Set pendingPack = - Collections.newSetFromMap( - new ConcurrentHashMap<>()); - private String lastPlayedSound = ""; - - private int radioIndex = 0; - - // ========== 登录阶段推送(管理员手动触发) ========== - public void startLoginRadio(Player p) { - loginPlayers.put(p.getName(), true); - } - - public void stopLoginRadio(Player p) { - loginPlayers.remove(p.getName()); - } - - public void stopAllLoginRadio() { - loginPlayers.clear(); - } - - private final List mainFiles = new ArrayList<>(); - private final List loginFiles = new ArrayList<>(); - - public RadioManager(Main plugin) { - this.plugin = plugin; - this.radioDir = new File( - plugin.getDataFolder(), "radio"); - this.oggDir = new File( - plugin.getDataFolder(), "radio/ogg"); - this.packDir = new File( - plugin.getDataFolder(), "radiopack"); - radioDir.mkdirs(); - oggDir.mkdirs(); - packDir.mkdirs(); - loadRadioConfig(); - } - // ========== 读取 radio_config.txt ========== - private void loadRadioConfig() { - File cfg = new File( - plugin.getDataFolder(), - "radio_config.txt"); - - // 不存在则创建默认配置 - if (!cfg.exists()) { - createRadioConfig(cfg); - plugin.getLogger().info( - "[Radio] 已生成 radio_config.txt"); - } - - // 读取 - try (BufferedReader r = new BufferedReader( - new InputStreamReader( - new FileInputStream(cfg), - java.nio.charset - .StandardCharsets - .UTF_8))) { - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("#") - || line.trim().isEmpty()) - continue; - int eq = line.indexOf('='); - if (eq < 0) continue; - String key = line.substring(0, eq) - .trim(); - String val = line.substring(eq + 1) - .trim(); - if (key.equals("resource-pack-url")) { - externalUrl = val; - } else if (key.equals("http-port")) { - try { - httpPort = Integer.parseInt( - val); - } catch (NumberFormatException ignored) { - } - } - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 读取radio_config.txt失败: " - + e.getMessage()); - } - - // 443是HTTPS端口,内嵌HTTP不能用 - if (httpPort == 443) httpPort = 8080; - - plugin.getLogger().info( - "[Radio] 配置: externalUrl=[" - + externalUrl + "] httpPort=" - + httpPort); - } - - private void createRadioConfig(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - java.nio.charset - .StandardCharsets - .UTF_8))) { - pw.println("# ============ Sdf1 Radio Config ============"); - pw.println("#"); - pw.println("# resource-pack-url"); - pw.println("# 填外部地址(HTTP或HTTPS均可)"); - pw.println("# 例: https://example.com/radiopack.zip"); - pw.println("# 留空 = 使用内置HTTP服务"); - pw.println("#"); - pw.println("# http-port"); - pw.println("# 内置HTTP端口(仅resource-pack-url为空时生效)"); - pw.println("#"); - pw.println("# ============ Settings ==============="); - pw.println(); - pw.println("resource-pack-url="); - pw.println("http-port=8080"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 创建radio_config.txt失败: " - + e.getMessage()); - } - } - - // ========== 初始化 ========== - public void init() { - loadRadioConfig(); - convertNonOgg(); - loadAllOggs(); - buildResourcePack(); - startHttpServer(); // 启动HTTP - plugin.getLogger().info( - "[Radio] 初始化完成: 全服=" - + mainFiles.size() - + " 登录=" + loginFiles.size()); - } - - - // ========== 转换非ogg文件 ========== - private void convertNonOgg() { - convertDir(radioDir); - convertDir(new File(radioDir, "login")); - convertDir(new File(radioDir, "reg")); - } - - private void convertDir(File src) { - if (!src.exists() || !src.isDirectory()) return; - File[] files = src.listFiles((d, n) -> { - String l = n.toLowerCase(); - return l.endsWith(".mp3") - || l.endsWith(".wav") - || l.endsWith(".flac"); - }); - if (files == null) return; - - for (File f : files) { - String oggName = f.getName() - .replaceAll("\\.(mp3|wav|flac)$", - ".ogg"); - File oggFile = new File(src, oggName); - if (oggFile.exists() - && oggFile.lastModified() - >= f.lastModified()) { - continue; - } - // 调用ffmpeg转换 - if (convertWithFfmpeg(f, oggFile)) { - plugin.getLogger().info( - "[Radio] 转换成功: " - + f.getName()); - } else { - plugin.getLogger().warning( - "[Radio] 转换失败: " - + f.getName() - + " (需要安装ffmpeg)"); - } - } - } - - private boolean convertWithFfmpeg( - File input, File output) { - try { - ProcessBuilder pb = new ProcessBuilder( - "ffmpeg", "-y", - "-i", input.getAbsolutePath(), - "-c:a", "libvorbis", - "-q:a", "5", - output.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - boolean finished = - p.waitFor(30, TimeUnit.SECONDS); - if (!finished) { - p.destroyForcibly(); - return false; - } - return output.exists() - && output.length() > 0; - } catch (Exception e) { - return false; - } - } - - // ========== 加载ogg文件列表 ========== - private void loadAllOggs() { - mainFiles.clear(); - loginFiles.clear(); - loadOggsFlat(radioDir, mainFiles); - loadOggsFlat( - new File(radioDir, "login"), - loginFiles); - loadOggsFlat( - new File(radioDir, "reg"), - loginFiles); - } - - private void loadOggsFlat( - File dir, List list) { - if (!dir.exists() || !dir.isDirectory()) - return; - File[] files = dir.listFiles( - (d, n) -> n.toLowerCase() - .endsWith(".ogg")); - if (files == null) return; - Arrays.sort(files); - for (File f : files) list.add(f); - } - /** - * 解析 pack.png:优先读取插件目录, - * 没有则搜索图片并用ffmpeg转码 - */ - private File resolvePackIcon() { - File dataDir = plugin.getDataFolder(); - File packPng = new File(dataDir, "pack.png"); - - if (packPng.exists() - && packPng.length() > 0) { - plugin.getLogger().info( - "[Radio] 找到 pack.png (" - + packPng.length() - + " bytes)"); - return packPng; - } - - // 搜索可转换的图片文件 - String[] exts = {"png", "jpg", "jpeg", - "bmp", "webp", "tiff", "gif"}; - File[] found = dataDir.listFiles( - (dir, name) -> { - String low = name.toLowerCase(); - for (String ext : exts) { - if (low.endsWith("." + ext) - && !name.equals( - "pack.png")) { - return true; - } - } - return false; - }); - - if (found != null && found.length > 0) { - File src = found[0]; - plugin.getLogger().info( - "[Radio] 找到图片: " - + src.getName() - + " → 转码为 pack.png"); - if (convertToPackPng(src, packPng)) { - return packPng; - } - plugin.getLogger().warning( - "[Radio] 转码失败"); - return null; - } - - plugin.getLogger().warning( - "[Radio] 未找到 pack.png 或可转换图片"); - return null; - } - - /** - * ffmpeg 将任意图片转为 256x256 PNG - */ - private boolean convertToPackPng( - File input, File output) { - try { - ProcessBuilder pb = new ProcessBuilder( - "ffmpeg", "-y", - "-i", input.getAbsolutePath(), - "-vf", - "scale='min(256,iw)'" - + ":'min(256,ih)':" - + "force_original_aspect_ratio" - + "=decrease," - + "pad=256:256:" - + "(ow-iw)/2:" - + "(oh-ih)/2:" - + "color=0x00000000", - "-c:v", "png", - output.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - boolean ok = p.waitFor( - 30, TimeUnit.SECONDS); - if (!ok) { - p.destroyForcibly(); - return false; - } - return output.exists() - && output.length() > 0; - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] ffmpeg转码失败: " - + e.getMessage()); - return false; - } - } - - // ========== 资源包构建 ========== - private void buildResourcePack() { - deleteDir(packDir); - packDir.mkdirs(); - - List allOggs = new ArrayList<>(); - collectOggs(radioDir, allOggs); - - if (allOggs.isEmpty()) { - plugin.getLogger().warning( - "[Radio] radio/ 目录下" - + "无 .ogg 文件,跳过资源包"); - return; - } - - // 正确路径:assets/minecraft/sounds/custom/ - File soundsDir = new File(packDir, - "assets/minecraft/sounds/custom"); - soundsDir.mkdirs(); - - // 生成 sounds.json - StringBuilder json = - new StringBuilder("{\n"); - for (int i = 0; i < allOggs.size(); i++) { - File ogg = allOggs.get(i); - String simpleName = - ogg.getName() - .replace(".ogg", ""); - File dest = new File(soundsDir, - simpleName + ".ogg"); - try { - Files.copy(ogg.toPath(), - dest.toPath(), - StandardCopyOption - .REPLACE_EXISTING); - } catch (Exception e) { - // ignore - } - // 音频事件名: custom.xxx - // 文件路径: custom/xxx - json.append(" \"custom.") - .append(simpleName) - .append("\": {\n"); - json.append(" \"sounds\": [{\n"); - json.append(" \"name\":" - + " \"custom/") - .append(simpleName) - .append("\",\n"); - json.append( - " \"stream\": true\n"); - json.append(" }]\n"); - json.append(" }"); - if (i < allOggs.size() - 1) - json.append(","); - json.append("\n"); - } - json.append("}"); - - // sounds.json 放在 assets/minecraft/ 下 - try { - Files.write(new File(packDir, - "assets/minecraft/sounds.json") - .toPath(), - json.toString() - .getBytes("UTF-8")); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] sounds.json写入失败"); - } -// sounds.json 写入后... - -// ===== 新增:打包 pack.png 作为资源包图标 ===== - File iconPng = resolvePackIcon(); - if (iconPng != null && iconPng.exists()) { - try { - Files.copy(iconPng.toPath(), - new File(packDir, "pack.png") - .toPath(), - StandardCopyOption - .REPLACE_EXISTING); - plugin.getLogger().info( - "[Radio] pack.png 已加入资源包"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] pack.png复制失败: " - + e.getMessage()); - } - } - - // pack.mcmeta(pack_format=54) - try { - Files.write(new File(packDir, - "pack.mcmeta").toPath(), - ("{\n \"pack\": {\n" - + " \"pack_format\": 54,\n" - + " \"description\":" - + " \"§d§l草原探险服务器资源包,请勿拒绝资源包\"\n }\n}") - .getBytes("UTF-8")); - } catch (Exception e) { - // ignore - } - - try { - File zipFile = new File( - plugin.getDataFolder(), - "radiopack.zip"); - packZip(packDir, zipFile); - plugin.getLogger().info( - "[Radio] 资源包生成: " - + allOggs.size() + " 个音频"); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] zip打包失败: " - + e.getMessage()); - } - } - - private void deleteDir(File dir) { - if (!dir.exists()) return; - File[] files = dir.listFiles(); - if (files != null) { - for (File f : files) { - if (f.isDirectory()) deleteDir(f); - else f.delete(); - } - } - dir.delete(); - } - - private void collectOggs( - File dir, List list) { - File[] files = dir.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) - collectOggs(f, list); - else if (f.getName().endsWith(".ogg")) - list.add(f); - } - } - - private void packZip(File root, - File zipFile) throws Exception { - try (ZipOutputStream zos = - new ZipOutputStream( - new FileOutputStream( - zipFile))) { - zipDir(root, root, zos); - } - } - - private void zipDir(File root, - File current, - ZipOutputStream zos) - throws Exception { - File[] files = current.listFiles(); - if (files == null) return; - for (File f : files) { - if (f.isDirectory()) { - zipDir(root, f, zos); - } else { - String path = root.toURI() - .relativize(f.toURI()) - .getPath(); - zos.putNextEntry(new ZipEntry(path)); - zos.write(Files.readAllBytes( - f.toPath())); - zos.closeEntry(); - } - } - } - - // ========== 资源包发送 ========== - - /** - * 发送资源包(带限流,不禁重试) - */ - private final Map lastSendTime = - new ConcurrentHashMap<>(); - - public void sendResourcePack(Player p) { - UUID uuid = p.getUniqueId(); - - // 最短间隔 5秒(不是10秒的全锁死) - Long last = lastSendTime.get(uuid); - if (last != null - && System.currentTimeMillis() - last - < 5000L) { - plugin.getLogger().info( - "[Radio] 跳过(5秒内): " - + p.getName()); - return; - } - lastSendTime.put(uuid, - System.currentTimeMillis()); - - String url = getResourcePackUrl(); - if (url == null || url.isEmpty()) { - plugin.getLogger().warning( - "[Radio] URL为空,跳过: " - + p.getName()); - return; - } - - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) { - plugin.getLogger().warning( - "[Radio] radiopack.zip不存在," - + "跳过: " + p.getName()); - return; - } - - try { - byte[] data = - Files.readAllBytes(zip.toPath()); - String hash = sha1(data); - long fileSize = zip.length(); - - plugin.getLogger().info( - "[Radio] 发送: " + p.getName() - + " url=" + url - + " size=" + fileSize - + " hash=" + hash); - - sendPackWithHash(p, url, hash); - - } catch (Exception e) { - plugin.getLogger().severe( - "[Radio] 发送异常: " - + p.getName()); - e.printStackTrace(); - } - } - - - - private void sendPackWithHash( - Player p, - String url, - String hash) { - - UUID uuid = UUID.fromString( - "a1b2c3d4-e5f6-7890-abcd-" - + "ef1234567890"); - - // 尝试5参 + Component - try { - Object component = - net.kyori.adventure.text - .Component.text( - "§d§l请下载自定义资源包"); - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class, - net.kyori.adventure - .text.Component - .class, - boolean.class) - .invoke( - p, uuid, url, - hash, component, - true); - plugin.getLogger().info( - "[Radio] 成功(5参): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // 尝试5参 + String描述 - try { - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class, - String.class, - boolean.class) - .invoke( - p, uuid, url, - hash, - "Sdf1 Radio", - true); - plugin.getLogger().info( - "[Radio] 成功(5参String): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // 尝试3参 - try { - p.getClass().getMethod( - "setResourcePack", - UUID.class, - String.class, - String.class) - .invoke( - p, uuid, url, - hash); - plugin.getLogger().info( - "[Radio] 成功(3参): " - + p.getName()); - return; - } catch (Exception ignored) { - } - - // ★ 全部失败 → 通知玩家 + 熔断 - plugin.getLogger().severe( - "[Radio] 所有签名失败: " - + p.getName() - + " 客户端版本: " - + p.getProtocolVersion()); - p.sendMessage( - "§e§l[Radio] §f资源包发送失败," - + "§7部分功能可能异常"); - p.sendMessage( - "§7可尝试: §e/rejoin §7或重启客户端"); - } - - - - private void sendPackFromLocal( - Player p, String url) { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (!zip.exists()) return; - try { - byte[] data = - Files.readAllBytes( - zip.toPath()); - String hash = sha1(data); - sendPackWithHash(p, url, hash); - } catch (Exception e) { - plugin.getLogger().warning( - "[Radio] 降级发送失败"); - } - } - - // 从URL下载字节 - private byte[] downloadBytes(String urlStr) - throws Exception { - java.net.URL url = - new java.net.URL(urlStr); - java.net.HttpURLConnection conn = - (java.net.HttpURLConnection) - url.openConnection(); - conn.setConnectTimeout(10000); - conn.setReadTimeout(30000); - try (java.io.InputStream is = - conn.getInputStream()) { - return is.readAllBytes(); - } - } - - // SHA-1计算 - private String sha1(byte[] data) - throws Exception { - java.security.MessageDigest md = - java.security.MessageDigest - .getInstance("SHA-1"); - md.update(data); - byte[] hash = md.digest(); - StringBuilder sb = - new StringBuilder(); - for (byte b : hash) - sb.append(String.format( - "%02x", b)); - return sb.toString(); - } - - public void clearPending(UUID uuid) { - lastSendTime.remove(uuid); - } - - - - // ========== 内嵌HTTP服务 ========== - private com.sun.net.httpserver.HttpServer - httpServer; - - private void startHttpServer() { - // 如果配置了外部地址则不启动 - if (plugin.getConfig2() != null - && !plugin.getConfig2() - .broadcastServerUrl - .isEmpty()) { - return; - } - try { - int port = 8080; - if (plugin.getConfig2() != null) { - port = plugin.getConfig2().httpPort; - } - httpServer = - com.sun.net.httpserver - .HttpServer.create( - new java.net - .InetSocketAddress( - port), 0); - httpServer.createContext( - "/radiopack.zip", - exchange -> { - File zip = new File( - plugin.getDataFolder(), - "radiopack.zip"); - if (zip.exists()) { - byte[] data = - Files.readAllBytes( - zip.toPath()); - exchange.getResponseHeaders() - .set("Content-Type", - "application/zip"); - exchange.getResponseHeaders() - .set("Access-Control" - + "-Allow-Origin", - "*"); - exchange.sendResponseHeaders( - 200, data.length); - exchange.getResponseBody() - .write(data); - exchange.getResponseBody() - .close(); - } else { - byte[] empty = - "not found".getBytes(); - exchange.sendResponseHeaders( - 404, empty.length); - exchange.getResponseBody() - .write(empty); - exchange.getResponseBody() - .close(); - } - }); - httpServer.setExecutor(null); - httpServer.start(); - plugin.getLogger().info( - "[Radio] HTTP服务: http://0.0.0.0:" - + port + "/radiopack.zip"); - } catch (Exception e) { - plugin.getLogger().severe( - "[Radio] HTTP启动失败: " - + e.getMessage()); - } - } - - private void stopHttpServer() { - if (httpServer != null) { - httpServer.stop(0); - httpServer = null; - } - } - - public String getResourcePackUrl() { - // 有外部配置 → 随机生成URL - if (externalUrl != null - && !externalUrl.trim().isEmpty()) { - return generateRandomUrl(externalUrl); - } - // 无配置 → 用内置HTTP - int port = 8080; - if (plugin.getConfig2() != null) { - port = plugin.getConfig2().httpPort; - } - return "http://127.0.0.1:" - + port + "/radiopack.zip"; - } - - - /** - * 从配置的域名列表中随机选一个 - * 生成随机、带时间戳的完整资源包URL - * - * 规则: - * *.example.com - * → 随机子域+时间戳 - * 例: a3f7k2.example.com/pack.zip?t=xxx&v=b9c1e4 - * - * cdn.example.com (无*) - * → 直接使用+时间戳 - * 例: cdn.example.com/pack.zip?t=xxx - */ - private String generateRandomUrl( - String raw) { - String[] parts = raw.split("\\|"); - String pick = - parts[(int)(Math.random() - * parts.length)].trim(); - - // 去掉协议 https:// http:// - String cleaned = pick - .replaceFirst("^https?://", ""); - // 去掉路径 /path/a?v=1 - if (cleaned.contains("/")) { - cleaned = cleaned.substring( - 0, cleaned.indexOf('/')); - } - - // 随机子域 - String hex = Long.toHexString( - Double.doubleToLongBits( - Math.random())); - String rand = hex.length() > 6 - ? hex.substring(2, 8) : hex; - rand = rand.replaceAll( - "[^a-z0-9]", "x"); - - String host; - if (cleaned.startsWith("*")) { - host = rand - + cleaned.substring(1); - } else { - host = cleaned; - } - - String ts = String.valueOf( - System.currentTimeMillis()); - String token = Integer.toHexString( - (int)(Math.random() - * 0xFFFFFF)); - - String url = "https://" + host - + "/radiopack.zip" - + "?t=" + ts - + "&v=" + token; - - plugin.getLogger().info( - "[Radio] 生成URL: " - + pick + " → " + url); - return url; - } - - - - // ========== 全服广播 ========== - public void startMainRadio() { - stopMainRadio(); - if (mainFiles.isEmpty()) { - plugin.getLogger().warning( - "[Radio] 无 .ogg 文件"); - return; - } - plugin.getLogger().info( - "[Radio] 启动全服广播 (" - + mainFiles.size() + " 个文件)"); - mainRadioTask = new BukkitRunnable() { - @Override - public void run() { - playNext(); - } - }.runTaskTimer(plugin, 100L, 2400L); - } - - private void playNext() { - if (mainFiles.isEmpty()) return; - File file = mainFiles.get( - radioIndex % mainFiles.size()); - String name = file.getName() - .replace(".ogg", ""); - lastPlayedSound = "custom." + name; - String key = "minecraft:" + lastPlayedSound; - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" + name); - radioIndex++; - } - - // ========== 按名称播放 ========== - public void playToAll(String name) { - String key = findSongKey(name); - if (key == null) return; - lastPlayedSound = "custom." + name; - for (Player p : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" + name); - } - - - public void playToPlayer( - Player p, String name) { - String key = findSongKey(name); - if (key == null) { - p.sendMessage("§c未找到: " + name); - return; - } - p.playSound(p.getLocation(), key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - - public void playToLoginPlayers(String name) { - String key = findSongKey(name); - if (key == null) return; - for (String pName - : loginPlayers.keySet()) { - Player lp = Bukkit.getPlayer(pName); - if (lp != null && lp.isOnline()) { - lp.playSound(lp.getLocation(), - key, - SoundCategory.RECORDS, - 1.5f, 1.0f); - } - } - } - - - private String findSongKey(String name) { - List all = new ArrayList<>(); - collectOggs(radioDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - return "minecraft:custom." + name; - } - } - plugin.getLogger().warning( - "[Radio] 未找到歌曲: " + name); - return null; - } - - - public boolean hasSong(String name) { - return findSongKey(name) != null; - } - - // ========== 手动播放 ========== - public boolean playSpecific( - Player sender, String name) { - List all = new ArrayList<>(); - collectOggs(radioDir, all); - for (File f : all) { - String fn = f.getName() - .replace(".ogg", ""); - if (fn.equalsIgnoreCase(name)) { - String key = "sdf1radio:" + name; - for (Player p - : Bukkit.getOnlinePlayers()) { - p.playSound(p.getLocation(), - key, - SoundCategory.RECORDS, - 2.0f, 1.0f); - } - Bukkit.broadcastMessage( - "§6§l[广播] §f正在播放: §e" - + name); - return true; - } - } - if (sender != null) - sender.sendMessage("§c未找到: " + name); - return false; - } - public void stopMainRadio() { - if (mainRadioTask != null) { - mainRadioTask.cancel(); - mainRadioTask = null; - } - if (lastPlayedSound != null - && !lastPlayedSound.isEmpty()) { - for (Player p : Bukkit.getOnlinePlayers()) { - p.stopSound(lastPlayedSound, - SoundCategory.RECORDS); - } - lastPlayedSound = ""; - } - } - -// 在 init() 或 registerEvents 中保存引用: -// this.listener = new RadioDownloadListener(plugin); -private RadioDownloadListener radioListener; - - - - // ========== 重载 ========== - public void reload() { - stopMainRadio(); - stopAllLoginRadio(); - stopHttpServer(); - loadAllOggs(); - buildResourcePack(); - startHttpServer(); - loadRadioConfig(); - } -} \ No newline at end of file diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RefundRecord.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/RefundRecord.java deleted file mode 100644 index c0a3070..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RefundRecord.java +++ /dev/null @@ -1,62 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; -import java.util.UUID; - -public class RefundRecord { - public final UUID playerUUID; - public final String playerName; - public final String itemId; - public final String itemName; - public final Material material; - public final Material shulkerMaterial; - public int amount; - public int totalPaid; - public final long timestamp; - - // 普通购买 - public RefundRecord(UUID uuid, String name, - String itemId, String itemName, - Material material, int amount, - int totalPaid, long timestamp) { - this.playerUUID = uuid; - this.playerName = name; - this.itemId = itemId; - this.itemName = itemName; - this.material = material; - this.shulkerMaterial = null; - this.amount = amount; - this.totalPaid = totalPaid; - this.timestamp = timestamp; - } - - // 盒装购买 - public RefundRecord(UUID uuid, String name, - String itemId, String itemName, - Material material, - Material shulkerMat, - int amount, int totalPaid, - long timestamp) { - this.playerUUID = uuid; - this.playerName = name; - this.itemId = itemId; - this.itemName = itemName; - this.material = material; - this.shulkerMaterial = shulkerMat; - this.amount = amount; - this.totalPaid = totalPaid; - this.timestamp = timestamp; - } - - public boolean isExpired() { - return System.currentTimeMillis() - - timestamp > 300000L; - } - - public int getSecondsLeft() { - long left = 300000L - - (System.currentTimeMillis() - - timestamp); - return (int) Math.max(0, left / 1000); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RiskControlManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/RiskControlManager.java deleted file mode 100644 index 4ddedcc..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/RiskControlManager.java +++ /dev/null @@ -1,248 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 风控管理器:检测异常登录行为,记录尝试次数,触发安全报警 - */ -public class RiskControlManager { - - private final Main plugin; - - // 每个玩家名 -> 连续失败次数 - private final Map failAttempts = - new ConcurrentHashMap<>(); - - // 每个IP -> 关联的可疑玩家列表 - private final Map> ipSuspicious = - new ConcurrentHashMap<>(); - - // 每个玩家 -> 上次验证时间(用于冷却) - private final Map verifyCooldown = - new ConcurrentHashMap<>(); - - // 配置项 - private static final int MAX_FAIL_ATTEMPTS = 5; - private static final int MAX_ACCOUNTS_PER_IP = 5; - private static final long COOLDOWN_MS = 60000L; - - public RiskControlManager(Main plugin) { - this.plugin = plugin; - } - - /** - * 记录一次失败的登录尝试 - * - * @param playerName 玩家名 - * @return 剩余尝试次数 - */ - public int recordFailAttempt(String playerName) { - int current = failAttempts - .getOrDefault(playerName, 0) + 1; - failAttempts.put(playerName, current); - return Math.max(0, MAX_FAIL_ATTEMPTS - current); - } - - /** - * 重置指定玩家的失败计数(登录成功时调用) - */ - public void resetFailAttempts(String playerName) { - failAttempts.remove(playerName); - } - - /** - * 获取指定玩家的失败次数 - */ - public int getFailAttempts(String playerName) { - return failAttempts - .getOrDefault(playerName, 0); - } - - /** - * 判断玩家是否已被锁定(失败次数达到上限) - */ - public boolean isLocked(String playerName) { - return failAttempts - .getOrDefault(playerName, 0) - >= MAX_FAIL_ATTEMPTS; - } - - /** - * 检查IP是否达到注册上限 - * 直接查数据库,不依赖 DatabaseManager 的方法 - */ - public boolean canRegisterFromIP(String ip) { - if (ip == null || ip.isEmpty()) return true; - try { - java.sql.Connection conn = - plugin.getDb().getDb(); - if (conn == null) return true; - java.sql.PreparedStatement ps = - conn.prepareStatement( - "SELECT COUNT(*) FROM users " - + "WHERE ip_address = ?"); - ps.setString(1, ip); - java.sql.ResultSet rs = ps.executeQuery(); - int count = 0; - if (rs.next()) count = rs.getInt(1); - rs.close(); - ps.close(); - return count < 5; - } catch (Exception e) { - e.printStackTrace(); - return true; - } - } - - - /** - * 记录IP关联的可疑行为 - */ - public void recordSuspiciousIP(String ip, - String playerName) { - if (ip == null || ip.isEmpty()) return; - ipSuspicious - .computeIfAbsent(ip, - k -> ConcurrentHashMap.newKeySet()) - .add(playerName); - } - - /** - * 获取某IP关联的所有可疑玩家 - */ - public Set getSuspiciousAccounts(String ip) { - return ipSuspicious - .getOrDefault(ip, Collections.emptySet()); - } - - /** - * 检查是否在验证冷却期内 - */ - public boolean isOnCooldown(String playerName) { - Long last = verifyCooldown.get(playerName); - if (last == null) return false; - return System.currentTimeMillis() - last - < COOLDOWN_MS; - } - - /** - * 设置验证冷却时间 - */ - public void setCooldown(String playerName) { - verifyCooldown.put(playerName, - System.currentTimeMillis()); - } - - /** - * 当检测到可疑登录时触发报警 - * - 全服广播 - * - 有邮箱则发邮件 - * - 记录到数据库 - */ - public void triggerAlert(Player player, - String suspiciousIP) { - String name = player.getName(); - - // 记录到数据库 - plugin.getDb().recordSecurityAlert( - name, suspiciousIP); - - // 记录IP可疑行为 - recordSuspiciousIP(suspiciousIP, name); - - // 全服广播 - String serverMsg = "§c§l[安全警告] §e" + name - + " §f的账号疑似遭到 §c" + suspiciousIP - + " §f的盗号尝试, 请各位玩家注意账号安全!"; - Bukkit.broadcastMessage(serverMsg); - - // 有邮箱则发邮件 - String emailAddr = (String) plugin.getDb() - .getField(name, "email"); - if (emailAddr != null - && !emailAddr.isEmpty()) { - String subject = - "[安全警告] 您的账号疑似遭到盗号尝试"; - String body = "尊敬的玩家 " + name - + ":\n\n" - + "您的账号在 " - + new java.text.SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss") - .format(new java.util.Date()) - + " 疑似遭到 IP: " + suspiciousIP - + " 的盗号尝试。\n\n" - + "建议您:\n" - + "1. 立即修改密码" - + "(使用 /sdf1_login pw)\n" - + "2. 如非本人操作," - + "请联系管理员处理\n\n" - + "--Sdf1_login 安全系统"; - plugin.getEmail().sendTempPassword( - emailAddr, name, body); - } - } - - /** - * 处理登录失败时的风控逻辑 - * - * @param player 玩家 - * @param suspiciousIP 可疑来源IP - * @return true=已被锁定(应踢出), false=仍可继续尝试 - */ - public boolean handleLoginFailure(Player player, - String suspiciousIP) { - String name = player.getName(); - int remaining = recordFailAttempt(name); - - if (remaining <= 0) { - // 达到上限,触发报警 - triggerAlert(player, suspiciousIP); - player.kickPlayer( - "§c§l[风控] §f登录失败次数过多," - + " 账号已被临时锁定, 请联系管理员"); - return true; - } - - // 未达上限,提示剩余次数 - player.sendMessage( - "§c§l[风控] §f密码错误! " - + "剩余" + remaining + "次机会"); - return false; - } - - /** - * 登录成功后重置风控状态 - */ - public void onLoginSuccess(String playerName) { - resetFailAttempts(playerName); - } - - /** - * 清理过期的冷却记录(可由定时任务调用) - */ - public void cleanup() { - long now = System.currentTimeMillis(); - verifyCooldown.entrySet().removeIf( - e -> now - e.getValue() > COOLDOWN_MS * 10); - } - - /** - * 获取所有玩家的失败尝试记录(调试用) - */ - public Map getAllFailAttempts() { - return Collections.unmodifiableMap(failAttempts); - } - - /** - * 清除所有风控数据(管理员调试用) - */ - public void clearAll() { - failAttempts.clear(); - ipSuspicious.clear(); - verifyCooldown.clear(); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/SalesStatsManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/SalesStatsManager.java deleted file mode 100644 index 9d06a22..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/SalesStatsManager.java +++ /dev/null @@ -1,170 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public class SalesStatsManager { - - private final Main plugin; - - private final Map sessionSales = - new ConcurrentHashMap<>(); - private int sessionRevenue = 0; - private int sessionOrders = 0; - private int sessionItems = 0; - private int sessionRefunds = 0; - private int sessionRefundAmount = 0; - private long sessionStart = System.currentTimeMillis(); - - // 状态 - private boolean waitingForEmpty = false; - private boolean hasReported = false; - private BukkitRunnable waitTask = null; - - public SalesStatsManager(Main plugin) { - this.plugin = plugin; - startMonitor(); - } - - // ★ 每10秒检测一次 - private void startMonitor() { - new BukkitRunnable() { - @Override - public void run() { - int online = Bukkit.getOnlinePlayers().size(); - - if (online > 0) { - // 有玩家在线 → 重置等待状态 - if (waitingForEmpty) { - // 刚有人进来了,清除已汇报标记 - // 下次没人时会重新汇报 - } - waitingForEmpty = false; - hasReported = false; - cancelWaitTask(); - } else if (!waitingForEmpty && !hasReported) { - // 没人了,且还没汇报过 → 开始1分钟倒计时 - waitingForEmpty = true; - startWaitCountdown(); - } - } - }.runTaskTimer(plugin, 200L, 200L); - } - - // ★ 1分钟等待 - private void startWaitCountdown() { - cancelWaitTask(); - waitTask = new BukkitRunnable() { - @Override - public void run() { - int online = Bukkit.getOnlinePlayers().size(); - - if (online > 0) { - // 等待期间有人进来 → 取消,等下次没人 - waitingForEmpty = false; - hasReported = false; - waitTask = null; - cancel(); - return; - } - - // 1分钟没人 → 汇报一次 - if (!hasReported) { - hasReported = true; - report(); - } - waitingForEmpty = false; - waitTask = null; - cancel(); - } - }; - waitTask.runTaskLater(plugin, 1200L); // 60秒 - } - - private void cancelWaitTask() { - if (waitTask != null) { - waitTask.cancel(); - waitTask = null; - } - } - - // ===== 记录 ===== - - public void recordSale(String itemName, int price) { - sessionSales.merge(itemName, 1, Integer::sum); - sessionRevenue += price; - sessionOrders++; - sessionItems++; - } - - public void recordRefund(int amount) { - sessionRefunds++; - sessionRefundAmount += amount; - } - - // ===== 汇报 ===== - - public void report() { - if (sessionItems == 0 && sessionRefunds == 0) { - // plugin.getLogger().info("[Sales] 无数据,跳过"); - return; - } - - long minutes = - (System.currentTimeMillis() - sessionStart) - / 60000; - if (minutes < 1) minutes = 1; - - StringBuilder sb = new StringBuilder(); - sb.append("§6§l========== 销量汇总 =========="); - sb.append("\n§7时长: §f").append(minutes).append("分钟"); - sb.append("\n§7订单: §f").append(sessionOrders).append("笔"); - sb.append("\n§7商品: §f").append(sessionItems).append("件"); - sb.append("\n§7成交额: §6").append(sessionRevenue).append("枚"); - sb.append("\n§7退款: §c").append(sessionRefunds) - .append("次(§c").append(sessionRefundAmount) - .append("枚§7)"); - sb.append("\n§6§l------------------------------"); - - if (!sessionSales.isEmpty()) { - sb.append("\n§7销量排行:"); - List> sorted = - new ArrayList<>(sessionSales.entrySet()); - sorted.sort((a, b) -> - b.getValue() - a.getValue()); - int rank = 1; - for (Map.Entry e : sorted) { - if (rank > 10) break; - sb.append("\n§e").append(rank).append(". ") - .append(e.getKey()) - .append(" §7x").append(e.getValue()); - rank++; - } - } - sb.append("\n§6§l=============================="); - - String msg = sb.toString(); - for (Player p : Bukkit.getOnlinePlayers()) { - if (p.isOp()) { - for (String line : msg.split("\n")) - p.sendMessage(line); - } - } - plugin.getLogger().info("[Sales] 汇报完成"); - resetSession(); - } - - public void resetSession() { - sessionSales.clear(); - sessionRevenue = 0; - sessionOrders = 0; - sessionItems = 0; - sessionRefunds = 0; - sessionRefundAmount = 0; - sessionStart = System.currentTimeMillis(); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopCategory.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopCategory.java deleted file mode 100644 index 715e9ad..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopCategory.java +++ /dev/null @@ -1,32 +0,0 @@ -package Sdf1_login; - -import java.util.ArrayList; -import java.util.List; - -public class ShopCategory { - private final String name; - private final String fileName; - private final List items; - - public ShopCategory(String name, String fileName) { - this.name = name; - this.fileName = fileName; - this.items = new ArrayList<>(); - } - - public String getName() { return name; } - public String getFileName() { return fileName; } - public List getItems() { return items; } - - public ShopItem getItem(String id) { - for (ShopItem item : items) { - if (item.getId().equalsIgnoreCase(id)) - return item; - } - return null; - } - - public void addItem(ShopItem item) { - items.add(item); - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopItem.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopItem.java deleted file mode 100644 index d336c7c..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopItem.java +++ /dev/null @@ -1,49 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Material; - -public class ShopItem { - private final String id; - private final String displayName; - private final Material material; - private final int buyPrice; - private final int sellPrice; - private int stock; - private int hourlySales; - private int totalSales; - public ShopItem(String id, String displayName, - Material material, int buyPrice, - int sellPrice, int stock, - int hourlySales, int totalSales) { - this.id = id; - this.displayName = displayName; - this.material = material; - this.buyPrice = buyPrice; - this.sellPrice = sellPrice; - this.stock = stock; - this.hourlySales = hourlySales; - this.totalSales = totalSales; - } - - public int getEffectiveSellPrice() { - if (sellPrice <= 0) return 0; - return sellPrice; - } - - public String getId() { return id; } - public String getDisplayName() { return displayName; } - public Material getMaterial() { return material; } - public int getBuyPrice() { return buyPrice; } - public int getSellPrice() { return sellPrice; } - public int getStock() { return stock; } - public int getHourlySales() { return hourlySales; } - public int getTotalSales() { return totalSales; } - - public void setStock(int s) { this.stock = s; } - public void setHourlySales(int s) { this.hourlySales = s; } - public void setTotalSales(int s) { this.totalSales = s; } - public void addSales(int n) { - this.hourlySales += n; - this.totalSales += n; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopManager.java deleted file mode 100644 index cdcc045..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/ShopManager.java +++ /dev/null @@ -1,2610 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.Base64; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.inventory.meta.PotionMeta; - - - - -public class ShopManager implements Listener { - - private final Main plugin; - private final List categories = - new ArrayList<>(); - private final List refundRecords = - new ArrayList<>(); - private final Map activeDiscount = - new HashMap<>(); - private final Map viewCat = - new HashMap<>(); - private final Map viewPage = - new HashMap<>(); - private final Map> shoppingCarts = - new HashMap<>(); - private final Set cartModeEnabled = - new HashSet<>(); - private final java.util.Random rng = - new java.util.Random(); - private final Map> pendingCheckout = - new HashMap<>(); - private final Set couponListening = - new HashSet<>(); - private final Map pendingCouponCode = - new HashMap<>(); - // ===== 物品NBT数据 ===== - private final Map itemNbtData = - new HashMap<>(); - private final Map itemStackCache = - new HashMap<>(); - // 替换字段 - private final Map couponApplyTime = new HashMap<>(); - - - - private static final Material[] SHULKER_COLORS = { - Material.SHULKER_BOX, - Material.WHITE_SHULKER_BOX, - Material.ORANGE_SHULKER_BOX, - Material.MAGENTA_SHULKER_BOX, - Material.LIGHT_BLUE_SHULKER_BOX, - Material.YELLOW_SHULKER_BOX, - Material.LIME_SHULKER_BOX, - Material.PINK_SHULKER_BOX, - Material.GRAY_SHULKER_BOX, - Material.LIGHT_GRAY_SHULKER_BOX, - Material.CYAN_SHULKER_BOX, - Material.PURPLE_SHULKER_BOX, - Material.BLUE_SHULKER_BOX, - Material.BROWN_SHULKER_BOX, - Material.GREEN_SHULKER_BOX, - Material.RED_SHULKER_BOX, - Material.BLACK_SHULKER_BOX - }; - private static final Map EFFECT_MAP = new HashMap<>(); - static { - // ===== 基础效果 ===== - EFFECT_MAP.put("夜视", PotionEffectType.NIGHT_VISION); - EFFECT_MAP.put("隐身", PotionEffectType.INVISIBILITY); - EFFECT_MAP.put("迅捷", PotionEffectType.SPEED); - EFFECT_MAP.put("速度", PotionEffectType.SPEED); - EFFECT_MAP.put("力量", PotionEffectType.STRENGTH); // 1.21+ 改名 - EFFECT_MAP.put("治疗", PotionEffectType.INSTANT_HEALTH); // 1.21+ 改名 - EFFECT_MAP.put("治愈", PotionEffectType.INSTANT_HEALTH); - EFFECT_MAP.put("伤害", PotionEffectType.INSTANT_DAMAGE); // 1.21+ 改名 - EFFECT_MAP.put("剧毒", PotionEffectType.POISON); - EFFECT_MAP.put("中毒", PotionEffectType.POISON); - EFFECT_MAP.put("回复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("再生", PotionEffectType.REGENERATION); - EFFECT_MAP.put("生命恢复", PotionEffectType.REGENERATION); - EFFECT_MAP.put("抗性", PotionEffectType.RESISTANCE); // 1.21+ 改名 - EFFECT_MAP.put("抗火", PotionEffectType.FIRE_RESISTANCE); - EFFECT_MAP.put("水下呼吸", PotionEffectType.WATER_BREATHING); - EFFECT_MAP.put("急迫", PotionEffectType.HASTE); // 1.21+ 改名 - EFFECT_MAP.put("挖掘疲劳", PotionEffectType.MINING_FATIGUE); // 1.21+ 改名 - EFFECT_MAP.put("虚弱", PotionEffectType.WEAKNESS); - EFFECT_MAP.put("失明", PotionEffectType.BLINDNESS); - EFFECT_MAP.put("飘浮", PotionEffectType.LEVITATION); - EFFECT_MAP.put("缓降", PotionEffectType.SLOW_FALLING); // ★ 补充 - EFFECT_MAP.put("迟缓", PotionEffectType.SLOWNESS); - EFFECT_MAP.put("饱和", PotionEffectType.SATURATION); - EFFECT_MAP.put("伤害吸收", PotionEffectType.ABSORPTION); - EFFECT_MAP.put("幸运", PotionEffectType.LUCK); - EFFECT_MAP.put("不祥", PotionEffectType.UNLUCK); - EFFECT_MAP.put("发光", PotionEffectType.GLOWING); - EFFECT_MAP.put("黑暗", PotionEffectType.DARKNESS); - EFFECT_MAP.put("生命加成", PotionEffectType.HEALTH_BOOST); - EFFECT_MAP.put("饥饿", PotionEffectType.HUNGER); - EFFECT_MAP.put("反胃", PotionEffectType.NAUSEA); - EFFECT_MAP.put("海豚关怀", PotionEffectType.DOLPHINS_GRACE); - EFFECT_MAP.put("跳跃", PotionEffectType.JUMP_BOOST); - // ★ 神龟药水是双效果,特殊处理,这里只做名称匹配入口 - EFFECT_MAP.put("神龟", PotionEffectType.RESISTANCE); - } - - public static class CartItem { - String itemId; - String displayName; - Material material; - int unitPrice; - int quantity; - } - - - private static final int PAGE_SIZE = 28; - private static final int[] ITEM_SLOTS = { - 10,11,12,13,14,15,16, - 19,20,21,22,23,24,25, - 28,29,30,31,32,33,34, - 37,38,39,40,41,42,43 - }; - - public ShopManager(Main plugin) { - this.plugin = plugin; - loadCategories(); - startHourlyTask(); - loadItemNbtData(); - startCleanupTask(); - } - // ===== 切换购物车模式 ===== - // ===== 切换购物车模式 ===== - public boolean toggleCartMode(Player p) { - UUID uuid = p.getUniqueId(); - if (cartModeEnabled.contains(uuid)) { - cartModeEnabled.remove(uuid); - p.sendMessage("§7购物车模式已关闭,恢复正常购买"); - return false; - } else { - cartModeEnabled.add(uuid); - p.sendMessage("§a购物车模式已开启,点击商品加入购物车"); - return true; - } - } - - public boolean isCartMode(Player p) { - return cartModeEnabled.contains(p.getUniqueId()); - } - private String itemToBase64(ItemStack item) { - try { - ByteArrayOutputStream os = - new ByteArrayOutputStream(); - BukkitObjectOutputStream bos = - new BukkitObjectOutputStream(os); - bos.writeObject(item); - bos.close(); - return Base64.getEncoder() - .encodeToString(os.toByteArray()); - } catch (Exception e) { - return ""; - } - } - - private ItemStack base64ToItem(String b64) { - try { - byte[] data = - Base64.getDecoder().decode(b64); - ByteArrayInputStream is = - new ByteArrayInputStream(data); - BukkitObjectInputStream bis = - new BukkitObjectInputStream(is); - ItemStack item = - (ItemStack) bis.readObject(); - bis.close(); - return item; - } catch (Exception e) { - return null; - } - } - - private void loadItemNbtData() { - itemNbtData.clear(); - itemStackCache.clear(); - File f = new File(plugin.getDataFolder(), - "shop/item_nbt.properties"); - if (!f.exists()) return; - try { - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) { - line = line.trim(); - if (line.isEmpty() - || line.startsWith("#")) continue; - int eq = line.indexOf('='); - if (eq < 0) continue; - String id = - line.substring(0, eq).trim(); - String b64 = - line.substring(eq + 1).trim(); - itemNbtData.put(id, b64); - ItemStack stack = base64ToItem(b64); - if (stack != null) { - itemStackCache.put(id, stack); - } - } - br.close(); - // plugin.getLogger().info("[Shop] NBT数据: " - // + itemStackCache.size() + "条"); - } catch (Exception ignored) {} - } - - private void saveItemNbtData() { - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - File f = new File(dir, "item_nbt.properties"); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# 商品完整数据(自动生成)"); - for (Map.Entry e - : itemNbtData.entrySet()) { - pw.println(e.getKey() + "=" - + e.getValue()); - } - pw.flush(); - pw.close(); - } catch (Exception ignored) {} - } - - // ===== 加入/移除购物车 ===== - public boolean addToCart(Player p, ShopItem item) { - UUID uuid = p.getUniqueId(); - List cart = shoppingCarts - .computeIfAbsent( - uuid, k -> new ArrayList<>()); - // ★ 始终用原价,折扣在结算时统一应用 - int price = item.getBuyPrice(); - for (CartItem ci : cart) { - if (ci.itemId.equals(item.getId())) { - ci.quantity += 1; - p.sendMessage("§a+1 §e" - + item.getDisplayName() - + " §7购物车共§f" - + getCartCount(p) - + "件 §6需" - + getCartTotal(p) + "枚"); - return true; - } - } - CartItem ci = new CartItem(); - ci.itemId = item.getId(); - ci.displayName = item.getDisplayName(); - ci.material = item.getMaterial(); - ci.unitPrice = price; - ci.quantity = 1; - cart.add(ci); - p.sendMessage("§a+1 §e" - + item.getDisplayName() - + " §7购物车共§f" - + getCartCount(p) - + "件 §6需" - + getCartTotal(p) + "枚"); - return true; - } - - - public int getCartCount(Player p) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null) return 0; - int c = 0; - for (CartItem ci : cart) c += ci.quantity; - return c; - } - - public int getCartTotal(Player p) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null) return 0; - int t = 0; - for (CartItem ci : cart) - t += ci.unitPrice * ci.quantity; - return t; - } - - public void cartAddOne(Player p, int index) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null || index < 0 - || index >= cart.size()) return; - cart.get(index).quantity += 1; - } - - public void cartRemoveOne(Player p, int index) { - List cart = - shoppingCarts.get(p.getUniqueId()); - if (cart == null || index < 0 - || index >= cart.size()) return; - CartItem ci = cart.get(index); - ci.quantity -= 1; - if (ci.quantity <= 0) cart.remove(index); - } - - public void clearCart(Player p) { - shoppingCarts.remove(p.getUniqueId()); - } - - // ===== 打开购物车页面 ===== - public void openCart(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§a§l购物车"); - List cart = shoppingCarts - .getOrDefault(p.getUniqueId(), - new ArrayList<>()); - for (int i = 0; i < cart.size() && i < 25; i++) { - CartItem ci = cart.get(i); - ItemStack is = new ItemStack(ci.material); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e" + ci.displayName - + " §7x" + ci.quantity); - im.setLore(java.util.Arrays.asList( - "§7单价: §f" + ci.unitPrice + "枚", - "§7小计: §6" - + (ci.unitPrice * ci.quantity) - + "枚", - "", - "§a左键+1 §c右键-1")); - is.setItemMeta(im); - } - g.setItem(10 + i, is); - } - if (cart.isEmpty()) { - g.setItem(22, mkItem(Material.BARRIER, - "§7购物车为空")); - } - int total = getCartTotal(p); - int bal = plugin.getBonds().getBonds(p.getName()); - g.setItem(49, mkItem(Material.EMERALD, - "§6§l结算: §e" + total + " §6枚债券", - "§7余额: §f" + bal + "枚", - bal >= total ? "§a余额充足" : "§c余额不足", - "", "§a点击结算")); - g.setItem(48, mkItem( - Material.LIME_STAINED_GLASS_PANE, - "§7清空购物车")); - g.setItem(53, mkItem(Material.ARROW, "§7返回商店")); - - p.openInventory(g); - } - - // ===== 购物车点击处理 ===== - public boolean handleCartClick(Player p, int raw, - boolean left, - boolean right) { - if (raw == 53) { openShopMain(p); return true; } - if (raw == 48) { - clearCart(p); - p.sendMessage("§7购物车已清空"); - openCart(p); - return true; - } - if (raw == 49) { - checkoutCartInit(p); - return true; - } - if (raw >= 10 && raw < 35) { - int idx = raw - 10; - List cart = shoppingCarts - .getOrDefault(p.getUniqueId(), - new ArrayList<>()); - if (idx < cart.size()) { - if (right) cartRemoveOne(p, idx); - else cartAddOne(p, idx); - openCart(p); - } - return true; - } - return true; - } - - // ===== 结算入口:判断打包方式 ===== - private void checkoutCartInit(Player p) { - UUID uuid = p.getUniqueId(); - List cart = shoppingCarts.get(uuid); - if (cart == null || cart.isEmpty()) { - p.sendMessage("§c购物车为空"); - return; - } - int total = getCartTotal(p); - int bal = plugin.getBonds() - .getBonds(p.getName()); - if (bal < total) { - p.sendMessage("§c余额不足,需要 §e" - + total + "§c枚"); - return; - } - int totalQty = 0; - for (CartItem ci : cart) totalQty += ci.quantity; - if (cart.size() == 1 && totalQty <= 64) { - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - double factor = 1.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - deductAndGive(p, cart, - (int) Math.round(total * factor), - false, null, "", 0, - ecoPct, couponPct); - return; - } - pendingCheckout.put(uuid, - new ArrayList<>(cart)); - openPackingConfirm(p, total); - } - - - - // ===== 执行扣款+发货 ===== - private boolean deductAndGive(Player p, - List cart, int baseTotal, - boolean useShulker, Material color, - String packNote, int packFee, - int ecoPct, int couponPct) { - - UUID uuid = p.getUniqueId(); - int originalTotal = 0; - for (CartItem ci : cart) - originalTotal += ci.unitPrice * ci.quantity; - - String[] names = new String[cart.size()]; - int[] subs = new int[cart.size()]; - for (int i = 0; i < cart.size(); i++) { - CartItem ci = cart.get(i); - names[i] = ci.displayName; - subs[i] = ci.unitPrice * ci.quantity; - } - - if (baseTotal < originalTotal) { - double ratio = - (double) baseTotal / originalTotal; - int sum = 0; - for (int i = 0; i < subs.length; i++) { - if (i == subs.length - 1) { - subs[i] = baseTotal - sum; - } else { - subs[i] = (int) (subs[i] * ratio); - sum += subs[i]; - } - } - } - - boolean ok = plugin.getBonds().checkoutCart( - p.getName(), names, subs); - if (!ok) { - p.sendMessage("§c扣款失败"); - return false; - } - - if (packFee > 0) { - plugin.getBonds().deductBonds( - p.getName(), packFee, - "shop_packing", "", - "商店系统", "打包费"); - } - - for (CartItem ci : cart) { - ShopItem si = findItemById(ci.itemId); - if (si != null) { - if (si.getStock() >= 0) - si.setStock(si.getStock() - - ci.quantity); - si.addSales(ci.quantity); - } - } - - int newBal = plugin.getBonds() - .getBonds(p.getName()); - - - // ===== 优惠券码 ===== - String usedCouponCode = - pendingCouponCode.get(uuid); - - // ===== 记录订单 ===== - OrderManager.OrderRecord latestOrder = null; - try { - List orderItems = - new ArrayList<>(); - for (CartItem ci : cart) { - orderItems.add( - new OrderManager.OrderItem( - ci.displayName, - ci.material.name(), - ci.unitPrice, - ci.unitPrice, - ci.quantity)); - } - String dType = "none"; - String cCode = ""; - if (usedCouponCode != null) { - dType = "coupon"; - cCode = usedCouponCode; - } else if (ecoPct > 0) { - dType = "eco"; - } - plugin.getOrderManager().recordOrder( - p, orderItems, originalTotal, - baseTotal + packFee, - originalTotal - baseTotal, - dType, cCode, packFee, - useShulker ? (packFee > 0 - ? "custom" : "default") - : "none", - color != null ? color.name() : ""); - // 获取最新订单 - synchronized (plugin.getOrderManager() - .getOrders()) { - List all = - plugin.getOrderManager() - .getOrders(); - if (!all.isEmpty()) - latestOrder = - all.get(all.size() - 1); - } - // 散装打印小票 - if (!useShulker && latestOrder != null) { - plugin.getOrderManager() - .sendReceiptChat(p, latestOrder); - } - } catch (Exception ex) { - // plugin.getLogger().warning( - // "[Shop] 记录订单失败: " - // + ex.getMessage()); - } - -// ===== 发货 ===== - if (!useShulker) { - for (CartItem ci : cart) { - ShopItem si = findItemById(ci.itemId); - ItemStack give = (si != null) - ? getShopStack(si, ci.quantity) - : new ItemStack(ci.material, ci.quantity); - HashMap left2 = - p.getInventory().addItem(give); - for (ItemStack drop : left2.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - } - } else { - List shulkers = - packShulkers(cart, color); - - // ★ 给潜影盒打订单ID标签 - if (latestOrder != null) { - for (ItemStack sh : shulkers) { - ItemMeta shm = sh.getItemMeta(); - if (shm != null) { - List lore = shm.getLore() != null - ? new ArrayList<>(shm.getLore()) - : new ArrayList<>(); - lore.add("§8#" + latestOrder.orderId); - shm.setLore(lore); - sh.setItemMeta(shm); - } - } - } - - // 小票书塞入 - if (!shulkers.isEmpty() - && latestOrder != null) { - ItemStack book = plugin.getOrderManager() - .createReceiptBook(latestOrder); - plugin.getOrderManager() - .addBookToShulker(shulkers.get(0), book); - } - // 发货 - for (ItemStack sh : shulkers) { - HashMap left2 = - p.getInventory().addItem(sh); - for (ItemStack drop : left2.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - } - } - - - // ===== 退款记录 ===== - long now = System.currentTimeMillis(); - if (!useShulker) { - for (CartItem ci : cart) { - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - ci.itemId, ci.displayName, - ci.material, null, - ci.quantity, - ci.unitPrice * ci.quantity, - now)); - } - } else { - String summary = ""; - for (CartItem ci : cart) { - if (!summary.isEmpty()) summary += "+"; - summary += ci.displayName - + "x" + ci.quantity; - } - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - "cart_" + now, summary, - Material.SHULKER_BOX, color, - 1, baseTotal, now)); - } - - // ===== 核销优惠券 ===== - if (usedCouponCode != null) { - activeDiscount.remove(uuid); - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - m.invoke(sdf1, usedCouponCode); - } - } catch (Exception ignored) {} - p.sendMessage("§7优惠券 §e" + usedCouponCode - + " §7已核销"); - } - - shoppingCarts.remove(uuid); - pendingCheckout.remove(uuid); - return true; - } - - @EventHandler - public void onBlockPlace(org.bukkit.event.block.BlockPlaceEvent e) { - ItemStack item = e.getItemInHand(); - if (item == null) return; - if (!item.getType().name().contains("SHULKER")) return; - markShulkerUsed(item); - } - - // ★ 公共方法:标记潜影盒为已使用 - private void markShulkerUsed(ItemStack item) { - if (item == null) return; - ItemMeta meta = item.getItemMeta(); - if (meta == null || meta.getLore() == null) return; - for (String line : meta.getLore()) { - if (line.startsWith("§8#")) { - try { - long orderId = Long.parseLong( - line.replace("§8#", "").trim()); - // shulkerUsedOrders.add(orderId); - } catch (NumberFormatException ignored) {} - } - } - } - - @EventHandler - public void onPlayerDrop(org.bukkit.event.player.PlayerDropItemEvent e) { - ItemStack item = e.getItemDrop().getItemStack(); - if (item != null && item.getType().name().contains("SHULKER")) { - markShulkerUsed(item); - } - } - - - // ===== 潠影盒打包(指定颜色)===== - private List packShulkers( - List items, Material color) { - List result = new ArrayList<>(); - List all = new ArrayList<>(); - for (CartItem ci : items) { - int rem = ci.quantity; - while (rem > 0) { - int sz = Math.min(rem, 64); - all.add(new ItemStack(ci.material, sz)); - rem -= sz; - } - } - while (!all.isEmpty()) { - ItemStack shulker = new ItemStack(color); - org.bukkit.inventory.meta.BlockStateMeta meta = - (org.bukkit.inventory.meta.BlockStateMeta) - shulker.getItemMeta(); - if (meta != null) { - org.bukkit.block.BlockState state = - meta.getBlockState(); - if (state instanceof org.bukkit.block.Container) { - org.bukkit.block.Container c = - (org.bukkit.block.Container) state; - List remain = new ArrayList<>(); - for (ItemStack stack : all) { - HashMap left = - c.getInventory().addItem(stack); - for (ItemStack l : left.values()) - remain.add(l); - } - all = remain; - meta.setBlockState(c); - shulker.setItemMeta(meta); - } - } - result.add(shulker); - } - - return result; - } - - // ===== 打包确认GUI ===== - public void openPackingConfirm(Player p, int baseTotal) { - Inventory g = Bukkit.createInventory( - null, 27, "§6§l选择包装方式"); - g.setItem(10, mkItem(Material.CHEST, - "§a§l不打包", - "§7直接放入背包", - "§7适合少量物品")); - g.setItem(13, mkItem(Material.SHULKER_BOX, - "§6§l默认打包", - "§7随机颜色潜影盒", - "§7加收 §e5 §6枚债券")); - g.setItem(16, mkItem(Material.ENDER_CHEST, - "§b§l自选颜色", - "§7选择你喜欢的潜影盒颜色", - "§7加收 §e5 §6枚债券")); - g.setItem(22, mkItem(Material.PAPER, - "§7消费金额: §e" + baseTotal + "枚")); - - p.openInventory(g); - } - - public boolean handlePackingConfirm( - Player p, int raw) { - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.remove(uuid); - if (cart == null) return false; - - int originalTotal = 0; - for (CartItem ci : cart) - originalTotal += ci.unitPrice * ci.quantity; - - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - - if (raw == 10) { - ecoPct = 2; - } - - double factor = 1.0; - if (ecoPct > 0) - factor *= (100 - ecoPct) / 100.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - int discounted = (int) Math.round( - originalTotal * factor); - - int totalQty = 0; - for (CartItem ci : cart) - totalQty += ci.quantity; - boolean multiAndBig = (cart.size() > 1 - && originalTotal >= 100); - - if (raw == 10) { - deductAndGive(p, cart, discounted, - false, null, "", 0, - ecoPct, couponPct); - return true; - } - if (raw == 13) { - Material c = Material.SHULKER_BOX; - if (multiAndBig) { - deductAndGive(p, cart, discounted, - true, c, "", 0, - ecoPct, couponPct); - } else { - deductAndGive(p, cart, discounted, - true, c, "默认打包", 5, - ecoPct, couponPct); - } - return true; - } - if (raw == 16) { - pendingCheckout.put(uuid, cart); - openColorSelect(p, discounted); - return true; - } - return true; - } - - - // ===== 自选颜色GUI ===== - public void openColorSelect(Player p, int baseTotal) { - Inventory g = Bukkit.createInventory( - null, 54, "§b§l选择潜影盒颜色"); - String[] colorNames = { - "白色","橙色","品红","淡蓝", - "黄色","淡绿","粉色","灰色", - "淡灰","青色","紫色","蓝色", - "棕色","绿色","红色","黑色","原色" - }; - for (int i = 0; i < SHULKER_COLORS.length; i++) { - ItemStack is = new ItemStack(SHULKER_COLORS[i]); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName("§e" + colorNames[i] - + "§6潜影盒"); - im.setLore(java.util.Arrays.asList( - "§7加收 §e5 §6枚债券", - "", - "§a点击选择")); - is.setItemMeta(im); - } - g.setItem(i, is); - } - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - g.setItem(45, mkItem(Material.PAPER, - "§7消费: §e" + baseTotal - + "枚 + §e5§6打包费")); - - p.openInventory(g); - } - - public boolean handleColorSelect( - Player p, int raw) { - if (raw == 49) { - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.get(uuid); - if (cart != null) { - int total = 0; - for (CartItem ci : cart) - total += ci.unitPrice * ci.quantity; - openPackingConfirm(p, total); - } - return true; - } - if (raw < 0 || raw >= SHULKER_COLORS.length) - return false; - UUID uuid = p.getUniqueId(); - List cart = - pendingCheckout.remove(uuid); - if (cart == null) return false; - int total = 0; - for (CartItem ci : cart) - total += ci.unitPrice * ci.quantity; - - int couponPct = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int ecoPct = 0; - double factor = 1.0; - if (couponPct > 0) - factor *= (100 - couponPct) / 100.0; - int discounted = (int) Math.round( - total * factor); - - Material selectedColor = SHULKER_COLORS[raw]; - if (selectedColor == Material.SHULKER_BOX) { - deductAndGive(p, cart, discounted, true, - Material.SHULKER_BOX, - "原色打包", 0, - ecoPct, couponPct); - } else { - deductAndGive(p, cart, discounted, true, - selectedColor, "自选颜色", 5, - ecoPct, couponPct); - } - return true; - } - - - - // ===== 加载分类 ===== - - public void loadCategories() { - // plugin.getLogger().info("[Shop] === loadCategories 被调用 ==="); // 过滤调试日志 - categories.clear(); - viewCat.clear(); // ★ 清缓存 - viewPage.clear(); - - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - - convertTxtToMd(dir); - - File[] allMd = dir.listFiles( - (d, n) -> n.endsWith(".md") - && !n.equals("coupons.md")); - if (allMd == null || allMd.length == 0) { - createDefaultCategories(dir); - allMd = dir.listFiles( - (d, n) -> n.endsWith(".md") - && !n.equals("coupons.md")); - } - if (allMd == null) { - // plugin.getLogger().warning( - // "[Shop] shop目录为空"); - return; - } - - /* plugin.getLogger().info("[Shop] === 开始加载 ==="); - plugin.getLogger().info("[Shop] 扫描到 " - + allMd.length + " 个md文件"); -*/ - for (File f : allMd) { - /* plugin.getLogger().info("[Shop] 读取: " - + f.getName());*/ - ShopCategory cat = parseFile(f); - if (cat != null) { - /* plugin.getLogger().info("[Shop] 分类: " - + cat.getName() - + " 商品数: " - + cat.getItems().size());*/ - if (!cat.getItems().isEmpty()) { - categories.add(cat); - } - } - } - - int totalItems = 0; - for (ShopCategory c : categories) - totalItems += c.getItems().size(); - - plugin.getLogger().info("[Shop] === 加载完成 ===" - + " 分类=" + categories.size() - + " 商品=" + totalItems); - } - - -// ===== txt → md 自动迁移 ===== - - private void convertTxtToMd(File dir) { - File[] txtFiles = dir.listFiles( - (d, n) -> n.endsWith(".txt") - && !n.equals("coupons.txt")); - if (txtFiles == null) return; - for (File txt : txtFiles) { - String mdName = txt.getName() - .replace(".txt", ".md"); - File mdFile = new File(dir, mdName); - if (mdFile.exists()) continue; - - try { - List lines = new ArrayList<>(); - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(txt), - StandardCharsets.UTF_8)); - String line; - while ((line = br.readLine()) != null) - lines.add(line); - br.close(); - - String catName = txt.getName() - .replace(".txt", ""); - List out = new ArrayList<>(); - out.add("# " + catName); - out.add(""); - out.add("| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |"); - out.add("| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |"); - - for (String l : lines) { - l = l.trim(); - if (l.isEmpty() || l.startsWith("#")) - continue; - // 旧格式: ID | 名称 | 材质 | ... - // 保持原样转换为 md 表格行 - out.add("| " + l.replace("|", "|") - + " |"); - } - - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(mdFile), - StandardCharsets.UTF_8)); - for (String o : out) - pw.println(o); - pw.flush(); - pw.close(); - - txt.delete(); - // plugin.getLogger().info( - // "[Shop] 已迁移: " - // + txt.getName() - // + " → " + mdName); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 迁移失败: " - // + txt.getName()); - } - } - } - -// ===== 解析 md 文件 ===== - - private ShopCategory parseFile(File f) { - String name = f.getName() - .replace(".md", ""); - ShopCategory cat = - new ShopCategory(name, f.getName()); - try { - BufferedReader br = new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets.UTF_8)); - String line; - boolean headerPassed = false; - boolean separatorPassed = false; - - while ((line = br.readLine()) != null) { - line = line.trim(); - - // 空行跳过 - if (line.isEmpty()) continue; - - // 标题行:# 分类名 - if (line.startsWith("#")) { - String t = line.replaceFirst( - "^#+\\s*", "").trim(); - if (!t.isEmpty()) { - // 更新分类名(用文件内标题) - // cat.name 是 final,不改 - } - continue; - } - - // 表格分隔行:| --- | --- | - if (line.contains("---")) { - separatorPassed = true; - continue; - } - - // 表头行:| ID | 品名 | ... - if (line.startsWith("|")) { - if (!separatorPassed) { - headerPassed = true; - continue; - } - // 数据行 - ShopItem item = - parseTableRow(line); - if (item != null) - cat.addItem(item); - } - } - br.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 解析失败: " + f.getName() - // + " 原因: " + e.getMessage()); - return null; - } - return cat; - } - -// ===== 解析表格行 ===== - - private ShopItem parseTableRow(String row) { - // plugin.getLogger().info("[Shop] 行: " + row); // 过滤调试日志 - - String trimmed = row.trim(); - if (trimmed.startsWith("|")) - trimmed = trimmed.substring(1); - if (trimmed.endsWith("|")) - trimmed = trimmed.substring(0, - trimmed.length() - 1); - - String[] p = trimmed.split("\\|"); - // plugin.getLogger().info("[Shop] 列数=" + p.length); // 过滤调试日志 - - if (p.length < 6) { - // plugin.getLogger().warning( - // "[Shop] ✗ 列数不足, 跳过"); - return null; - } - - String rawId = p[0].trim(); - String rawName = p[1].trim(); - String rawMat = p[2].trim().toUpperCase(); - String rawBuy = p[3].trim(); - String rawSell = p[4].trim(); - String rawStock = p[5].trim(); - - // plugin.getLogger().info("[Shop] ID=" - // + rawId + " 名=" + rawName - // + " 材质=" + rawMat - // + " 购=" + rawBuy + " 售=" + rawSell - // + " 库=" + rawStock); // 过滤调试日志 - - // 检查材质是否存在 - Material mat = Material.matchMaterial(rawMat); - if (mat == null) { - // plugin.getLogger().warning( - // "[Shop] ✗ 无效材质: " + rawMat - // + " (行: " + row + ")"); - return null; - } - - try { - ShopItem item = new ShopItem( - rawId, rawName, mat, - Integer.parseInt(rawBuy), - Integer.parseInt(rawSell), - Integer.parseInt(rawStock), - p.length > 6 - ? Integer.parseInt( - p[6].trim()) : 0, - p.length > 7 - ? Integer.parseInt( - p[7].trim()) : 0); - // plugin.getLogger().info("[Shop] ✓ 解析成功: " + rawId); // 过滤调试日志 - return item; - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] ✗ 数字解析失败: " - // + e.getMessage()); - return null; - } - } - - -// ===== 保存为 md ===== - - public void saveCategory(ShopCategory cat) { - File dir = new File( - plugin.getDataFolder(), "shop"); - if (!dir.exists()) dir.mkdirs(); - File f = new File(dir, cat.getFileName()); - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.println("# " + cat.getName()); - pw.println(""); - pw.println("| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |"); - pw.println("| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |"); - for (ShopItem item : cat.getItems()) { - pw.println("| " - + item.getId() - + " | " + item.getDisplayName() - + " | " + item.getMaterial().name() - + " | " + item.getBuyPrice() - + " | " + item.getSellPrice() - + " | " + item.getStock() - + " | " + item.getHourlySales() - + " | " + item.getTotalSales() - + " |"); - } - pw.flush(); - pw.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 保存失败: " - // + cat.getFileName()); - } - } - -// ===== 创建示例分类 ===== - - private void createDefaultCategories(File dir) { - writeMd(dir, "建筑材料.md", - "# 建筑材料\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| STONE | 石头 | STONE" - + " | 10 | -1 | -1 | 0 | 0 |\n" - + "| COBBLESTONE | 圆石 | COBBLESTONE" - + " | 5 | -1 | -1 | 0 | 0 |\n" - + "| OAK_PLANKS | 橡木木板 | OAK_PLANKS" - + " | 8 | 3 | -1 | 0 | 0 |\n" - + "| BRICKS | 砖块 | BRICKS" - + " | 12 | -1 | -1 | 0 | 0 |\n" - + "| GLASS | 玻璃 | GLASS" - + " | 15 | 6 | -1 | 0 | 0 |\n" - + "| QUARTZ_BLOCK | 石英块 | QUARTZ_BLOCK" - + " | 20 | 8 | -1 | 0 | 0 |\n"); - - writeMd(dir, "装备工具.md", - "# 装备工具\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| IRON_PICKAXE | 铁镐 | IRON_PICKAXE" - + " | 50 | 20 | -1 | 0 | 0 |\n" - + "| DIAMOND_PICKAXE | 钻石镐 | DIAMOND_PICKAXE" - + " | 500 | 200 | -1 | 0 | 0 |\n" - + "| IRON_SWORD | 铁剑 | IRON_SWORD" - + " | 40 | 16 | -1 | 0 | 0 |\n" - + "| DIAMOND_SWORD | 钻石剑 | DIAMOND_SWORD" - + " | 400 | 160 | -1 | 0 | 0 |\n" - + "| ELYTRA | 鞘翅 | ELYTRA" - + " | 2000 | 800 | -1 | 0 | 0 |\n"); - - writeMd(dir, "食物补给.md", - "# 食物补给\n" - + "\n" - + "| ID | 品名 | 材质" - + " | 购入价 | 售出价" - + " | 库存" - + " | 本小时销量 | 总销量 |\n" - + "| --- | --- | ---" - + " | --- | --- | ---" - + " | --- | --- |\n" - + "| COOKED_BEEF | 熟牛排 | COOKED_BEEF" - + " | 15 | 6 | -1 | 0 | 0 |\n" - + "| GOLDEN_APPLE | 金苹果 | GOLDEN_APPLE" - + " | 500 | 200 | -1 | 0 | 0 |\n" - + "| BREAD | 面包 | BREAD" - + " | 8 | 3 | -1 | 0 | 0 |\n"); - - // plugin.getLogger().info( - // "[Shop] 已创建3个示例分类(md)"); - } - - private void writeMd(File dir, String name, - String content) { - File f = new File(dir, name); - if (f.exists()) return; - try { - PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets.UTF_8)); - pw.print(content); - pw.flush(); - pw.close(); - } catch (Exception e) { - // plugin.getLogger().warning( - // "[Shop] 创建失败: " + name); - } - } - - /** - * 从商品名称+材质智能推断完整物品 - * - * "夜视药水" + POTION - * → PotionEffect(NIGHT_VISION, 600, 0) - * - * "喷溅型治疗药水" + SPLASH_POTION - * → PotionEffect(HEALING, 600, 0) - * - * "长效夜视之箭" + TIPPED_ARROW - * → PotionEffect(NIGHT_VISION, 1800, 0) - * - * "强效力量喷溅药水" + SPLASH_POTION - * → PotionEffect(STRENGTH, 600, 1) - */ - private ItemStack inferFromName( - String name, Material mat) { - // 只处理药水和箭 - if (mat != Material.POTION - && mat != Material.SPLASH_POTION - && mat != Material.LINGERING_POTION - && mat != Material.TIPPED_ARROW) { - return null; - } - - // 1. 找效果类型 - PotionEffectType effectType = null; - for (Map.Entry e - : EFFECT_MAP.entrySet()) { - if (name.contains(e.getKey())) { - effectType = e.getValue(); - break; - } - } - if (effectType == null) return null; - - // 2. 找时长 - int duration = 600; - if (name.contains("长效") || name.contains("长时") - || name.contains("持久") || name.contains("长命")) { - duration = 1800; - } -// ★ 瞬间效果药水:duration = 1 - if (name.contains("治疗") || name.contains("治愈") - || name.contains("伤害")) { - duration = 1; - } - - // 3. 找等级 - int amplifier = 0; - if (name.contains("强效") - || name.contains("强化") - || name.contains("加强")) { - amplifier = 1; - } - - // 4. 构造完整物品 - try { - ItemStack item = new ItemStack(mat); - PotionMeta meta = - (PotionMeta) item.getItemMeta(); - - // ★ 神龟药水:双效果 - if (name.contains("神龟")) { - meta.addCustomEffect( - new PotionEffect( - PotionEffectType.RESISTANCE, - duration, amplifier + 2), - true); - meta.addCustomEffect( - new PotionEffect( - PotionEffectType.SLOWNESS, - duration, amplifier + 3), - true); - } else { - // 普通药水:单效果 - meta.addCustomEffect( - new PotionEffect(effectType, - duration, amplifier), - true); - } - - item.setItemMeta(meta); - return item; - } catch (Exception e) { - return null; - } - } - - public void saveAll() { - for (ShopCategory cat : categories) - saveCategory(cat); - } - private ItemStack getShopStack( - ShopItem item, int amount) { - // 优先级1: 手动捕获的NBT数据 - ItemStack cached = - itemStackCache.get(item.getId()); - if (cached != null) { - ItemStack copy = cached.clone(); - copy.setAmount(amount); - return copy; - } - // 优先级2: 根据名称智能推断 - ItemStack inferred = inferFromName( - item.getDisplayName(), - item.getMaterial()); - if (inferred != null) { - ItemStack copy = inferred.clone(); - copy.setAmount(amount); - return copy; - } - // 优先级3: 纯材质(默认) - return new ItemStack( - item.getMaterial(), amount); - } - - // ===== 购买 ===== - - public boolean buyItem(Player p, ShopItem item, - int amount) { - // plugin.getLogger().info("[Shop] ★ buyItem item=" + (item != null ? item.getId() : "null") + " amount=" + amount); - // plugin.getLogger().info("buyItem被调用"); - if (item == null) return false; - if (item.getStock() >= 0 - && item.getStock() < amount) { - p.sendMessage("§c库存不足,剩余: " - + item.getStock()); - return false; - } - if (p.getInventory().firstEmpty() == -1 - && !p.getInventory().contains( - item.getMaterial(), 1)) { - p.sendMessage("§c背包已满"); - return false; - } - - int unit = item.getBuyPrice(); - int disc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - int total = Math.max(0, - unit * amount * (100 - disc) / 100); - - int balance = plugin.getBonds() - .getBonds(p.getName()); - if (balance < total) { - p.sendMessage("§c债券不足,需要 §e" - + total + "§c枚,当前 §e" - + balance + "§c枚"); - return false; - } - - boolean ok = plugin.getBonds().deductBonds( - p.getName(), total, - "shop_buy", item.getId(), - "商店系统", "购买" - + item.getDisplayName()); - if (!ok) { - p.sendMessage("§c扣款失败"); - return false; - } - - ItemStack give = getShopStack(item, amount); - HashMap left = - p.getInventory().addItem(give); - for (ItemStack drop : left.values()) - p.getWorld().dropItemNaturally( - p.getLocation(), drop); - - if (item.getStock() >= 0) - item.setStock( - item.getStock() - amount); - item.addSales(amount); - - mergeOrAddRefund(p, item, amount, total); - Long applyTime = couponApplyTime.get(p.getUniqueId()); - if (applyTime != null - && System.currentTimeMillis() - applyTime > 300000L) { - // 超过5分钟,自动失效 - pendingCouponCode.remove(p.getUniqueId()); - activeDiscount.remove(p.getUniqueId()); - couponApplyTime.remove(p.getUniqueId()); - p.sendMessage("§7优惠券已过期,请重新输入"); - disc = 0; // 重置折扣 - } - - - String discMsg = disc > 0 - ? " §7(优惠" + disc + "%)" : ""; - p.sendMessage("§a购买成功: §e" - + item.getDisplayName() - + " x" + amount - + " §a-" + total + "枚债券" - + discMsg); - if (disc > 0) - activeDiscount.remove( - p.getUniqueId()); - // ★ 直购成功后核销优惠券 - String usedCode = pendingCouponCode - .remove(p.getUniqueId()); - if (usedCode != null) { - activeDiscount.remove(p.getUniqueId()); - couponApplyTime.remove(p.getUniqueId()); - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - m.invoke(sdf1, usedCode); - } - } catch (Exception ignored) {} - p.sendMessage("§7优惠券 §e" + usedCode - + " §7已核销"); - couponApplyTime.remove(p.getUniqueId()); - } - // ★ 记录订单(包裹try-catch,失败不影响购买) - try { - String usedCouponCode = - pendingCouponCode.get(p.getUniqueId()); - List orderItems = - new ArrayList<>(); - orderItems.add(new OrderManager.OrderItem( - item.getDisplayName(), - item.getMaterial().name(), - item.getBuyPrice(), - item.getBuyPrice(), amount)); - plugin.getOrderManager().recordOrder( - p, orderItems, - item.getBuyPrice() * amount, total, - item.getBuyPrice() * amount - total, - usedCouponCode != null ? "coupon" : "none", - usedCouponCode != null ? usedCouponCode : "", - 0, "none", ""); - // 打印小票 - List all = - plugin.getOrderManager().getOrders(); - if (!all.isEmpty()) { - plugin.getOrderManager().sendReceiptChat( - p, all.get(all.size() - 1)); - } - } catch (Exception ex) { - // plugin.getLogger().warning( - // "[Shop] 记录订单失败: " + ex.getMessage()); - } - - return true; - } - - - public void handleRefundItemClick(Player p, int raw) { - if (raw < 10 || raw >= 45) return; - int target = raw - 10; - int cnt = 0; - synchronized (refundRecords) { - Iterator it = - refundRecords.iterator(); - while (it.hasNext()) { - RefundRecord r = it.next(); - if (!r.playerUUID.equals(p.getUniqueId())) - continue; - if (r.isExpired()) { it.remove(); continue; } - if (cnt == target) { - requestRefund(p, r); - return; - } - cnt++; - } - } - } - - - // ===== 出售 ===== - - public boolean sellItem(Player p, ShopItem item, - int amount) { - if (item == null) return false; - - // ★ 先判断售价,禁止出售的不收物品 - if (item.getEffectiveSellPrice() <= 0) { - p.sendMessage("§c该商品不可出售"); - return false; - } - - if (!p.getInventory().containsAtLeast( - getShopStack(item, 1), amount)) { - p.sendMessage("§c你没有足够的 §e" - + item.getDisplayName()); - return false; - } - - int total = item.getEffectiveSellPrice() * amount; - - p.getInventory().removeItem( - getShopStack(item, amount)); - plugin.getBonds().addBonds( - p.getName(), total, "shop_sell", - item.getId(), "商店系统", - "出售" + item.getDisplayName()); - if (item.getStock() >= 0) - item.setStock(item.getStock() + amount); - p.sendMessage("§a出售成功: §e" - + item.getDisplayName() - + " x" + amount - + " §a+" + total + "枚债券"); - return true; - } - - // ===== 退款合并:30秒内同商品合并为一个订单 ===== - private boolean mergeOrAddRefund(Player p, ShopItem item, - int amount, int total) { - long now = System.currentTimeMillis(); - synchronized (refundRecords) { - for (int i = refundRecords.size() - 1; i >= 0; i--) { - RefundRecord r = refundRecords.get(i); - boolean same = r.playerUUID.equals(p.getUniqueId()) - && r.itemId.equals(item.getId()) - && (now - r.timestamp) < 30000L; - if (same) { - // final 字段不能改,只能删旧建新 - int newAmount = r.amount + amount; - int newTotal = r.totalPaid + total; - refundRecords.remove(i); - refundRecords.add(new RefundRecord( - r.playerUUID, r.playerName, - r.itemId, r.itemName, - r.material, newAmount, - newTotal, now)); - return true; - } - } - } - refundRecords.add(new RefundRecord( - p.getUniqueId(), p.getName(), - item.getId(), item.getDisplayName(), - item.getMaterial(), amount, total, now)); - return false; - } - - - - // ===== 退款 ===== - - public boolean requestRefund(Player p, RefundRecord rec) { - if (rec == null) return false; - if (!rec.playerUUID.equals(p.getUniqueId())) { - p.sendMessage("§c这不是你的购买记录"); - return false; - } - if (rec.isExpired()) { - p.sendMessage("§c已超过5分钟退款期限"); - synchronized (refundRecords) { - refundRecords.remove(rec); - } - return false; - } - - // 检测物品/潜影盒是否还在 - synchronized (refundRecords) { - if (rec.shulkerMaterial != null) { - int boxCount = countShulkers(p, rec.shulkerMaterial); - if (boxCount < 1) { - p.sendMessage("§c潜影盒已移出背包,无法退款"); - refundRecords.remove(rec); - return false; - } - } else { - int have = countDirect(p, rec.material); - if (have < rec.amount) { - p.sendMessage("§c物品已转移或消耗,无法退款"); - refundRecords.remove(rec); - return false; - } - } - } - - // 退款:扣物品/潜影盒 - if (rec.shulkerMaterial != null) { - // 盒装:扣一个潜影盒 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == rec.shulkerMaterial) { - p.getInventory().clear(i); - break; - } - } - } else { - // 普通:扣商品 - p.getInventory().removeItem( - new ItemStack(rec.material, rec.amount)); - } - - // 退款:加债券 - plugin.getBonds().addBonds( - p.getName(), rec.totalPaid, - "refund", rec.itemId, - "商店系统", "退款" + rec.itemName); - - // 恢复库存 - ShopItem si = findItemById(rec.itemId); - if (si != null && si.getStock() >= 0) - si.setStock(si.getStock() + rec.amount); - - synchronized (refundRecords) { - refundRecords.remove(rec); - } - - p.sendMessage("§a退款成功: §e" + rec.itemName - + " §a+" + rec.totalPaid + "枚债券"); - return true; - } - - - - - public void setPlayerDiscount(Player p, - int pct) { - activeDiscount.put( - p.getUniqueId(), pct); - p.sendMessage("§a已设置 " + pct - + "% 折扣,下次购买生效"); - } - - // ===== 管理员操作 ===== - - public boolean addItemToCategory( - String catName, String id, - String displayName, Material mat, - int buyPrice, int sellPrice, - int stock) { - ShopCategory cat = - findCategory(catName); - if (cat == null) return false; - if (cat.getItem(id) != null) return false; - cat.addItem(new ShopItem(id, displayName, - mat, buyPrice, sellPrice, - stock, 0, 0)); - saveCategory(cat); - return true; - } - - public boolean removeItem(String id) { - for (ShopCategory cat : categories) { - ShopItem item = cat.getItem(id); - if (item != null) { - cat.getItems().remove(item); - saveCategory(cat); - return true; - } - } - return false; - } - - public boolean setStock(String id, int stock) { - ShopItem item = findItemById(id); - if (item == null) return false; - item.setStock(stock); - for (ShopCategory cat : categories) { - if (cat.getItem(id) != null) { - saveCategory(cat); - return true; - } - } - return false; - } - - public ShopCategory findCategory(String n) { - for (ShopCategory c : categories) - if (c.getName().equals(n)) return c; - return null; - } - - public ShopItem findItemById(String id) { - for (ShopCategory c : categories) { - ShopItem item = c.getItem(id); - if (item != null) return item; - } - return null; - } - - public List getCategories() { - return categories; - } - - // ===== 命令处理 ===== - - public boolean handleCommand( - CommandSender s, String[] a) { - if (a.length <= 1) { - if (s instanceof Player) { - openShopMain((Player) s); - } else { - s.sendMessage("§c仅玩家可使用"); - } - return true; - } - - String sub = a[1].toLowerCase(); - - if (sub.equals("reload")) { - loadCategories(); - s.sendMessage("§a商店已重载,共 " - + categories.size() - + " 个分类"); - return true; - } - - if (sub.equals("add")) { - if (a.length < 7) { - s.sendMessage( - "§e用法: shop add <分类>" - + " <名称> <材质>" - + " <购入价>" - + " [售出价-1自动]" - + " [库存-1无限]"); - return true; - } - Material mat; - try { - mat = Material.valueOf( - a[5].toUpperCase()); - } catch (Exception e) { - s.sendMessage( - "§c无效材质: " + a[5]); - return true; - } - int price = Integer.parseInt(a[6]); - int sell = a.length > 7 - ? Integer.parseInt(a[7]) : -1; - int stock = a.length > 8 - ? Integer.parseInt(a[8]) : -1; - if (addItemToCategory(a[2], a[3], - a[4], mat, price, sell, stock)) { - s.sendMessage("§a已添加: " - + a[4] + " → " + a[2]); - } else { - s.sendMessage("§c添加失败" - + "(分类不存在或ID重复)"); - } - return true; - } - - if (sub.equals("remove")) { - if (a.length < 3) { - s.sendMessage( - "§e用法: shop remove "); - return true; - } - if (removeItem(a[2])) { - s.sendMessage( - "§a已移除: " + a[2]); - } else { - s.sendMessage( - "§c未找到: " + a[2]); - } - return true; - } - - if (sub.equals("set")) { - if (a.length < 4) { - s.sendMessage( - "§e用法: shop set " - + " <库存数(-1无限)>"); - return true; - } - int stock = Integer.parseInt(a[3]); - if (setStock(a[2], stock)) { - s.sendMessage("§a已设置 " - + a[2] + " 库存为 " - + stock); - } else { - s.sendMessage( - "§c未找到: " + a[2]); - } - return true; - } - - if (sub.equals("pro")) { - if (a.length < 4) { - s.sendMessage( - "§e用法: shop pro" - + " <玩家名> <折扣%>"); - return true; - } - Player t = - Bukkit.getPlayerExact(a[2]); - if (t == null) { - s.sendMessage("§c玩家不在线"); - return true; - } - int pct = Integer.parseInt(a[3]); - setPlayerDiscount(t, pct); - s.sendMessage("§a已对 " + a[2] - + " 设置 " + pct + "% 折扣"); - return true; - } - - - s.sendMessage("§e商店子命令:" - + " reload / add / remove" - + " / set / pro "); - return true; - } - @EventHandler(priority = EventPriority.LOW) - public void onCouponChat( - org.bukkit.event.player.AsyncPlayerChatEvent e) { - Player p = e.getPlayer(); - UUID uuid = p.getUniqueId(); - if (!couponListening.contains(uuid)) return; - e.setCancelled(true); - couponListening.remove(uuid); - String code = e.getMessage().trim(); - if (code.isEmpty()) { - p.sendMessage("§c已取消输入"); - return; - } - // ★ 预处理:标准化输入 - String normalized = normalizeCouponInput(code); - if (normalized.isEmpty()) { - p.sendMessage("§c优惠券码无效(输入为空或仅含特殊字符)"); - couponListening.remove(uuid); - return; - } - - p.sendMessage("§7正在验证优惠券: §e" + code); - - int discount = -1; - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "validateCoupon", - String.class); - Object result = m.invoke(sdf1, normalized); - discount = (result != null) - ? (Integer) result : -1; - } - } catch (Exception ex) { - p.sendMessage("§c验证异常: " - + ex.getMessage()); - return; - } - if (discount <= 0) { - p.sendMessage("§c优惠券无效或已使用"); - return; - } - int offPct = 100 - discount; - int current = activeDiscount - .getOrDefault(uuid, 0); - if (offPct > current) { - pendingCouponCode.put(uuid, normalized); - activeDiscount.put(uuid, offPct); - couponApplyTime.put(uuid, System.currentTimeMillis()); - String zhe; - if (discount % 10 == 0) - zhe = (discount / 10) + "折"; - else - zhe = (discount / 10.0) + "折"; - p.sendMessage("§6§l[优惠券] §a验证通过!"); - p.sendMessage("§7折扣: §e" + zhe - + " §7(减" + offPct + "%)"); - p.sendMessage("§7结算时自动核销"); - final UUID u = uuid; - Bukkit.getScheduler().runTask(plugin, () -> { - Player pl = Bukkit.getPlayer(u); - if (pl != null && pl.isOnline()) { - openShopMain(pl); - } - }); - } else { - String curZhe; - if ((100 - current) % 10 == 0) - curZhe = ((100 - current) / 10) + "折"; - else - curZhe = ((100 - current) / 10.0) + "折"; - p.sendMessage("§c当前已有更优折扣: " - + curZhe + ",本次不生效"); - } - } - - /** - * 优惠券输入标准化 - * 全角→半角、去分隔符、转小写 - * "AAA-BBB-CCCC" → "aaaabbbbcccc" - * "AAA-BBB" → "aaabbb" - * "aaa_bbb_cccc" → "aaabbbcccc" - */ - private String normalizeCouponInput(String s) { - if (s == null) return ""; - StringBuilder sb = new StringBuilder(s.length()); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - // 全角数字→半角 - if (c >= '\uFF10' && c <= '\uFF19') { - sb.append((char)(c - 0xFF10 + '0')); - } - // 全角字母→半角 - else if (c >= '\uFF21' && c <= '\uFF3A') { - sb.append((char)(c - 0xFF21 + 'A')); - } - else if (c >= '\uFF41' && c <= '\uFF5A') { - sb.append((char)(c - 0xFF41 + 'a')); - } - // ★ 保留中文字符(Unicode CJK统一汉字范围) - else if ((c >= '\u4E00' && c <= '\u9FFF') - || (c >= '\u3400' && c <= '\u4DBF')) { - sb.append(c); - } - // 保留半角字母和数字 - else if ((c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z') - || (c >= '0' && c <= '9')) { - sb.append(c); - } - // 分隔符(- _ 空格等)丢弃 - } - return sb.toString().toLowerCase(); - } - - - - - - private String formatZhe(int discount) { - if (discount % 10 == 0) { - return (discount / 10) + "折"; - } - return (discount / 10.0) + "折"; - } - - public boolean redeemCoupon(Player p, String code) { - int discount = -1; - try { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("SDF1"); - if (sdf1 != null && sdf1.isEnabled()) { - java.lang.reflect.Method m = - sdf1.getClass().getMethod( - "redeemCoupon", - String.class); - Object result = m.invoke(sdf1, code); - discount = (result != null) - ? (Integer) result : -1; - } - } catch (Exception e) { - return false; - } - if (discount <= 0) return false; - int offPct = 100 - discount; - int cur = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (offPct > cur) { - activeDiscount.put(p.getUniqueId(), offPct); - } - return true; - } - - /** - * 商店主界面点击"优惠券"按钮后调用 - * 开启聊天监听,等待玩家输入优惠券码 - */ - public void openCouponInput(Player p) { - UUID uuid = p.getUniqueId(); - // 先取消已有的待核销优惠券 - pendingCouponCode.remove(uuid); - couponListening.add(uuid); - // 关闭UI - Bukkit.getScheduler().runTask(plugin, () -> { - if (p.isOnline()) p.closeInventory(); - }); - p.sendMessage("§6§l[优惠券] §e请在聊天栏输入优惠券码"); - p.sendMessage("§715秒内输入,过期自动取消"); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - if (couponListening.remove(uuid)) { - Player pl = Bukkit.getPlayer(uuid); - if (pl != null && pl.isOnline()) { - pl.sendMessage("§c优惠券输入已超时取消"); - } - } - }, 300L); - } - - - // ===== GUI: 主界面 ===== - - public void openShopMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l商店"); - int si = 0; - for (ShopCategory cat : categories) { - if (si >= ITEM_SLOTS.length) break; - ItemStack is = new ItemStack( - Material.CHEST); - ItemMeta im = is.getItemMeta(); - if (im != null) { - im.setDisplayName( - "§e§l" + cat.getName()); - im.setLore(Arrays.asList( - "§7商品: §f" - + cat.getItems().size() - + " 种", - "", - "§a点击进入")); - is.setItemMeta(im); - } - g.setItem(ITEM_SLOTS[si], is); - si++; - } - // ★ 改为跳转订单中心 - g.setItem(49, mkItem(Material.CLOCK, - "§6§l订单中心", - "§7查看订单记录、申请退款", - "", "§a点击进入")); - - // 在 openShopMain 方法中,原来的 slot 49/53 附近加: - - g.setItem(48, mkItem( - isCartMode(p) - ? Material.LIME_WOOL - : Material.GRAY_WOOL, - isCartMode(p) - ? "§a§l购物车模式 §7[开]" - : "§7§l购物车模式 §7[关]", - isCartMode(p) - ? "§a当前: 点击商品加入购物车" - : "§7当前: 直接购买", - "§7点击切换")); - g.setItem(50, mkItem(Material.CHEST, - "§6§l购物车 §7[" + getCartCount(p) + "件]", - "§7总价: §6" + getCartTotal(p) + "枚", - "", - "§a点击打开购物车")); - - // 在现有按钮之后,p.openInventory(g) 之前加: - g.setItem(49, mkItem(Material.CLOCK, - "§6§l订单中心", - "§7查看购物订单记录", - "", "§a点击进入")); - - // 优惠券按钮 - slot 47 - String discStatus = "§7"; - int curDisc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (curDisc > 0) { - discStatus = "§a当前: " + formatZhe(100 - curDisc); - } - g.setItem(47, mkItem(Material.NAME_TAG, - "§6§l优惠券", - discStatus, - "", - "§a点击输入优惠券码", - "§7一次性使用,核销后失效")); - - g.setItem(53, mkItem(Material.ARROW, - "§7返回")); - - p.openInventory(g); - } - - // ===== GUI: 分类 ===== - public void openCategory(Player p, - ShopCategory cat, - int page) { - // ★ 每次重新获取最新对象 ★ - ShopCategory fresh = null; - for (ShopCategory c : categories) { - if (c.getName().equals(cat.getName())) { - fresh = c; - break; - } - } - if (fresh == null) { - openShopMain(p); - return; - } - cat = fresh; - viewCat.put(p.getUniqueId(), cat); - - int total = (int) Math.ceil( - (double) cat.getItems().size() - / PAGE_SIZE); - if (total < 1) total = 1; - if (page < 0) page = 0; - if (page >= total) page = total - 1; - viewPage.put(p.getUniqueId(), page); - - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l" + cat.getName()); - - - int start = page * PAGE_SIZE; - int end = Math.min(start + PAGE_SIZE, - cat.getItems().size()); - - for (int i = start; i < end; i++) { - int idx = i - start; - if (idx >= ITEM_SLOTS.length) break; - - // ★ item 保留原名,代表 ShopItem 数据 - ShopItem item = cat.getItems().get(i); - - // ★ is 是带完整元数据的 ItemStack(用于GUI图标) - ItemStack is = getShopStack(item, 1); - ItemMeta im = is.getItemMeta(); - - if (im != null) { - im.setDisplayName("§e§l" - + item.getDisplayName()); - List lore = new ArrayList<>(); - lore.add("§7购入: §a" - + item.getBuyPrice() - + "§7枚债券"); - lore.add("§7售出: §e" - + item.getEffectiveSellPrice() - + "§7枚债券"); - if (item.getStock() >= 0) - lore.add("§7库存: §f" - + item.getStock()); - else - lore.add("§7库存: §a无限"); - lore.add(""); - int disc = activeDiscount - .getOrDefault(p.getUniqueId(), 0); - if (disc > 0) { - int dis = item.getBuyPrice() - * (100 - disc) / 100; - lore.add("§7优惠价: §6" - + dis + "枚" - + " §7(-" + disc + "%)"); - } - lore.add("§a左键买1 §6右键卖1"); - lore.add("§aShift+左键买64" - + " §6Shift+右键卖64"); - im.setLore(lore); - is.setItemMeta(im); - } - - // ★ 用 is(ItemStack)放入GUI格子 - g.setItem(ITEM_SLOTS[idx], is); - } - - - g.setItem(4, mkItem(Material.BOOK, - "§e§l" + cat.getName(), - "§7共 " + cat.getItems().size() - + " 种商品")); - g.setItem(48, page > 0 - ? mkItem(Material.ARROW, - "§7上一页") - : mkItem(Material - .BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(49, mkItem(Material.PAPER, - "§e" + (page + 1) + "/" + total)); - g.setItem(50, page < total - 1 - ? mkItem(Material.ARROW, - "§7下一页") - : mkItem(Material - .BLACK_STAINED_GLASS_PANE, " ")); - g.setItem(53, mkItem(Material.ARROW, - "§7返回商店")); - - p.openInventory(g); - } - - // ===== GUI: 退款 ===== - - public void openRefundPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§c§l退款中心"); - - // 位移检测:比对上次记录的背包内容 - validateRefunds(p); - - // 过期清理 - synchronized (refundRecords) { - refundRecords.removeIf(RefundRecord::isExpired); - } - - int idx = 0; - synchronized (refundRecords) { - for (RefundRecord r : refundRecords) { - if (idx >= 45) break; - if (!r.playerUUID.equals(p.getUniqueId())) continue; - if (r.isExpired()) continue; - - // 盒装用潜影盒图标,普通用商品图标 - Material iconMat = (r.shulkerMaterial != null) - ? r.shulkerMaterial : r.material; - - ItemStack is = new ItemStack(iconMat); - ItemMeta im = is.getItemMeta(); - if (im != null) { - int secs = r.getSecondsLeft(); - im.setDisplayName("§e" + r.itemName - + (r.shulkerMaterial != null - ? " §7[盒装]" : " x" + r.amount)); - - List lore = new ArrayList<>(); - lore.add("§7订单: §f#" + r.timestamp); - lore.add("§7支付: §a" + r.totalPaid - + "§7枚债券"); - - if (r.shulkerMaterial != null) { - int boxCount = countShulkers(p, - r.shulkerMaterial); - lore.add("§7潜影盒: §f" - + (boxCount >= 1 - ? "§a在背包中" - : "§c已移出")); - } else { - int have = countDirect(p, r.material); - lore.add("§7物品: §f" + have - + "/" + r.amount); - } - - lore.add("§7剩余: §e" + secs + "秒"); - lore.add(""); - lore.add("§c点击退款"); - im.setLore(lore); - } - is.setItemMeta(im); - g.setItem(10 + idx, is); - idx++; - } - } - - if (idx == 0) { - g.setItem(22, mkItem(Material.BARRIER, - "§7暂无可退款记录")); - } - - g.setItem(49, mkItem(Material.ARROW, "§7返回")); - - p.openInventory(g); - } - // 统计背包中某物品数量 - private int countInInventory(Player p, Material mat) { - int count = 0; - for (ItemStack is : p.getInventory().getContents()) { - if (is != null && is.getType() == mat) { - count += is.getAmount(); - } - } - // 潜影盒也要检查里面 - for (ItemStack is : p.getInventory().getContents()) { - if (is != null - && is.getType().name() - .endsWith("_SHULKER_BOX")) { - count += is.getAmount(); - } - } - return count; - } - - // 只统计背包中直接可见的数量(不含盒内) - // 新代码:增加按完整物品匹配的方法 - private int countDirect(Player p, Material mat) { - int count = 0; - for (ItemStack is : - p.getInventory().getContents()) { - if (is != null && is.getType() == mat) - count += is.getAmount(); - } - return count; - } - - private int countDirectByStack(Player p, - ItemStack target) { - int count = 0; - for (ItemStack is : - p.getInventory().getContents()) { - if (is != null && is.isSimilar(target)) - count += is.getAmount(); - } - return count; - } - // 统计背包中潜影盒数量 - private int countShulkers(Player p, Material shulker) { - if (shulker == null) return Integer.MAX_VALUE; - int count = 0; - for (ItemStack is : p.getInventory().getContents()) { - if (is != null && is.getType() == shulker) - count += is.getAmount(); - } - return count; - } - - // 退款检测:位移 OR 消耗 → 不能退 - private void validateRefunds(Player p) { - synchronized (refundRecords) { - Iterator it = - refundRecords.iterator(); - while (it.hasNext()) { - RefundRecord r = it.next(); - if (!r.playerUUID.equals(p.getUniqueId())) - continue; - if (r.isExpired()) { - it.remove(); - continue; - } - - if (r.shulkerMaterial != null) { - // 盒装:检测潜影盒是否还在背包 - int now = countShulkers( - p, r.shulkerMaterial); - if (now < 1) { - it.remove(); - p.sendMessage("§c[退款] " + r.itemName - + " 潜影盒已移出背包,无法退款"); - } - } else { - // 普通:检测商品是否还在背包 - int now = countDirect(p, r.material); - if (now < r.amount) { - it.remove(); - p.sendMessage("§c[退款] " + r.itemName - + " 物品已被使用或移出,无法退款"); - } - } - } - } - } - - - public int getPlayerPage(java.util.UUID uuid) { - return viewPage.getOrDefault(uuid, 0); - } - - public ShopCategory getPlayerCategory(java.util.UUID uuid) { - return viewCat.get(uuid); - } - - // ===== 供GUIManager调用:商店主界面点击 ===== - public boolean handleMainShopClick(Player p, int raw) { - if (raw == 49) { plugin.getOrderManager().openOrderCenter(p); return true; } - - if (raw == 48) { - toggleCartMode(p); - openShopMain(p); - return true; - } - if (raw == 50) { - openCart(p); - return true; - } - if (raw == 53) { - plugin.getGui().openMain(p); - return true; - } - - // ★ 新增:优惠券按钮 - if (raw == 47) { - openCouponInput(p); - return true; - } - if (raw == 49) { plugin.getOrderManager().openOrderCenter(p); return true;} - int idx = slotToIndex(raw); - if (idx < 0 || idx >= categories.size()) - return true; - openCategory(p, categories.get(idx), 0); - return true; - } - - // ===== 供GUIManager调用:退款中心点击 ===== - public boolean handleRefundClick(Player p, int raw) { - if (raw == 49) { openShopMain(p); return true; } - return true; - } - - // ===== 供GUIManager调用:分类页点击 ===== - public boolean handleCategoryClick(Player p, String title, - int raw, boolean left, - boolean shift) { - // plugin.getLogger().info("[Shop] ★ handleCategoryClick raw=" + raw); - - ShopManager sm = this; - ShopCategory match = null; - for (ShopCategory c : categories) { - if (title.equals("§6§l" + c.getName())) { - match = c; - break; - } - } - if (match == null) { - // plugin.getLogger().info("[Shop] ★ 匹配失败, title=[" + title + "]"); - return false; - } - // plugin.getLogger().info("[Shop] ★ 匹配到: " + match.getName()); - - viewCat.put(p.getUniqueId(), match); - - if (raw == 53) { openShopMain(p); return true; } - int pg = getPlayerPage(p.getUniqueId()); - if (raw == 48) { openCategory(p, match, pg - 1); return true; } - if (raw == 50) { openCategory(p, match, pg + 1); return true; } - - int itemIdx = slotToIndex(raw); - if (itemIdx < 0) { - // plugin.getLogger().info("[Shop] ★ slot=" + raw + " 不是商品格"); - return true; - } - int actual = itemIdx + pg * PAGE_SIZE; - if (actual < 0 || actual >= match.getItems().size()) { - // plugin.getLogger().info("[Shop] ★ 超出范围 actual=" + actual + " size=" + match.getItems().size()); - return true; - } - ShopItem si = match.getItems().get(actual); - // plugin.getLogger().info("[Shop] ★ 准备购买: " + si.getId() + " left=" + left); - - int amount = shift ? 64 : 1; - if (left) { - if (shift && isCartMode(p)) { addToCart(p, si); } - else if (shift) { buyItem(p, si, 64); } - else if (isCartMode(p)) { addToCart(p, si); } - else { buyItem(p, si, 1); } - } else { - if (shift) { sellAllOf(p, si); } - else { sellItem(p, si, 1); } - } - openCategory(p, match, pg); - return true; - } - - // ===== 批量出售某商品全部数量 ===== - private void sellAllOf(Player p, ShopItem item) { - if (item == null) return; - // ★ 补上禁止出售检查 - if (item.getEffectiveSellPrice() <= 0) { - p.sendMessage("§c该商品不可出售"); - return; - } - if (item == null) return; - Material mat = item.getMaterial(); - int total = 0; - int count = 0; - // 遍历背包清点 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - total += is.getAmount(); - count++; - } - } - if (total <= 0) { - p.sendMessage("§c你没有可出售的 §e" - + item.getDisplayName()); - return; - } - // 清除背包中该物品 - for (int i = 0; i < p.getInventory().getSize(); i++) { - ItemStack is = p.getInventory().getItem(i); - if (is != null && is.getType() == mat) { - p.getInventory().clear(i); - } - } - int money = item.getEffectiveSellPrice() * total; - plugin.getBonds().addBonds( - p.getName(), money, "shop_sell", - item.getId(), "商店系统", - "批量出售" + item.getDisplayName() - + " x" + total); - if (item.getStock() >= 0) - item.setStock(item.getStock() + total); - p.sendMessage("§a批量出售: §e" - + item.getDisplayName() - + " x" + total - + " §a+" + money + "枚债券"); - } - - - // ===== 定时 ===== - - private void startHourlyTask() { - new BukkitRunnable() { - @Override - public void run() { - // ★ 汇报交给 SalesStatsManager - if (plugin.getSalesStats() != null) { - plugin.getSalesStats().report(); - } - // 重置小时销量 - for (ShopCategory cat : categories) - for (ShopItem item : cat.getItems()) - item.setHourlySales(0); - saveAll(); - } - }.runTaskTimer(plugin, 72000L, 72000L); - } - - - private void startCleanupTask() { - new BukkitRunnable() { - @Override - public void run() { - synchronized (refundRecords) { - refundRecords.removeIf( - RefundRecord::isExpired); - } - } - }.runTaskTimer(plugin, 1200L, 1200L); - } - - // ===== 工具 ===== - - private int slotToIndex(int slot) { - for (int i = 0; i < ITEM_SLOTS.length; i++) - if (ITEM_SLOTS[i] == slot) return i; - return -1; - } - - private ItemStack mkItem(Material mat, - String name, - String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) - im.setLore(Arrays.asList(lore)); - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/TicketManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/TicketManager.java deleted file mode 100644 index c21edc2..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/TicketManager.java +++ /dev/null @@ -1,1644 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class TicketManager { - - private final Main plugin; - - public static final String T_TICKET_MAIN = - "§6§l工单中心"; - public static final String T_TICKET_CREATE = - "§e§l创建工单"; - public static final String T_TICKET_MY = - "§b§l我的工单"; - public static final String T_TICKET_ADMIN = - "§c§l工单管理"; - public static final String T_TICKET_DETAIL = - "§a§l工单详情"; - - public static final String TYPE_BUG = - "bug反馈"; - public static final String TYPE_HELP = - "求助"; - public static final String TYPE_REPORT = - "举报"; - public static final String TYPE_APPLY = - "申请"; - public static final String TYPE_OTHER = - "其他"; - public static final String TYPE_DELETE = - "删号"; - public static final String TYPE_PASSWORD = - "修改密码"; - - - - private static final Map - PRICES = new LinkedHashMap<>(); - static { - PRICES.put(TYPE_BUG, 10); - PRICES.put(TYPE_HELP, 5); - PRICES.put(TYPE_REPORT, 8); - PRICES.put(TYPE_APPLY, 15); - PRICES.put(TYPE_OTHER, 5); - PRICES.put(TYPE_DELETE, 0); - PRICES.put(TYPE_PASSWORD, 0); - } - - - public TicketManager(Main plugin) { - this.plugin = plugin; - } - - // ========== GUI ========== - - public void openMain(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_MAIN); - fillBg(g); - g.setItem(10, mk(Material.PAPER, - "§e创建工单", - "§7提交bug、求助、举报等")); - g.setItem(12, mk(Material.BOOK, - "§b我的工单", - "§7查看已提交的工单")); - g.setItem(14, mk(Material.CHEST, - "§6工单记录", - "§7查看历史工单")); - if (adm(p)) { - g.setItem(16, mk(Material.REDSTONE, - "§c工单管理", - "§7管理员专用")); - } - if (plugin.getDb() - .isServiceProvider( - p.getName())) { - g.setItem(18, mk(Material.EMERALD, - "§a服务商面板", - "§7接单、处理工单")); - } - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openCreate(Player p) { - Inventory g = Bukkit.createInventory( - null, 36, T_TICKET_CREATE); - fillBg(g); - g.setItem(10, mk(Material.BOOK, - "§cbug反馈")); - g.setItem(11, mk(Material.EMERALD, - "§a求助")); - g.setItem(12, mk(Material.BLAZE_POWDER, - "§6举报")); - g.setItem(13, mk(Material.NAME_TAG, - "§e申请")); - g.setItem(14, mk(Material.PAPER, - "§7其他")); - g.setItem(20, mk(Material.BARRIER, - "§c申请删号", - "§7申请删除账号")); - g.setItem(21, mk(Material.NETHER_WART, - "§d申请改密", - "§7请求管理员协助改密")); - g.setItem(31, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyTickets(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_MY); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByPlayer( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openDetail(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - Inventory g = Bukkit.createInventory( - null, 27, T_TICKET_DETAIL); - fillBg(g); - - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = sv(t, "requester"); - String title = sv(t, "title"); - String desc = sv(t, "description"); - String assigned = sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - int confirmed = nv(t, "admin_confirmed"); - - g.setItem(10, mk(Material.PAPER, - "§e§l" + (title.isEmpty() - ? type : title), - "§7类型: §f" + type, - "§7状态: " + stxt(status), - "§7提交者: §f" + requester, - "§7奖励: §e" + reward + " 积分")); - - g.setItem(12, mk(Material.BOOK, - "§7详细描述", - desc.isEmpty() ? "§7无描述" - : "§7" + (desc.length() > 40 - ? desc.substring(0, 40) - + "..." : desc))); - - if (!assigned.isEmpty()) { - g.setItem(14, mk(Material.PLAYER_HEAD, - "§7处理人: §f" + assigned, - confirmed == 1 - ? "§7评分: §e" + score - : "§7处理中")); - } else { - g.setItem(14, mk(Material.BARRIER, - "§7未分配")); - } - - // 驳回原因 - if ("closed".equals(status)) { - String reason = sv(t, "reject_reason"); - if (!reason.isEmpty()) { - g.setItem(13, mk(Material.BARRIER, - "§c驳回原因", - "§7" + reason)); - } - } - - // 回复按钮(非关闭/已完结状态) - if (!"closed".equals(status) - && !"withdrawn".equals(status) - && !"resolved".equals(status)) { - g.setItem(15, mk( - Material.WRITABLE_BOOK, - "§b回复工单", - "§7向对方发送消息")); - g.setItem(16, mk(Material.BOOK, - "§7查看回复记录")); - } - - // ===== 管理员按钮 ===== - if (adm(p)) { - if ("pending".equals(status)) { - // 待分配:可分配给服务商 - g.setItem(19, mk(Material.EMERALD, - "§a分配工单", - "§7指定服务商处理")); - // 也可自己处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && !assigned.equals("admin") - && !assigned.isEmpty()) { - // 服务商在处理:可取消指派收回 - g.setItem(19, mk(Material.BARRIER, - "§c取消指派", - "§7取消服务商指派,收回工单")); - // 也可自己接手处理 - g.setItem(20, mk(Material.LIME_DYE, - "§a我来处理", - "§7管理员直接处理此工单")); - } - if ("processing".equals(status) - && assigned.equals("admin")) { - // 管理员自己在处理:可标记完结 - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - if ("replied".equals(status) - && assigned.equals("admin")) { - g.setItem(19, mk(Material.LIME_DYE, - "§a标记完结", - "§7确认工单处理完成")); - } - // 驳回(非已关闭/已完结状态) - if (!"closed".equals(status) - && !"resolved".equals(status) - && !"withdrawn".equals(status)) { - g.setItem(21, mk(Material.RED_DYE, - "§c驳回工单", - "§7驳回并关闭此工单")); - } - } - - // ===== 服务商按钮 ===== - if (plugin.getDb() - .isServiceProvider(p.getName()) - && !TYPE_DELETE.equals(type) - && !TYPE_PASSWORD.equals(type)) { - if ("pending".equals(status) - && assigned.isEmpty()) { - // 待分配无人:抢单 - g.setItem(19, mk( - Material.GOLD_INGOT, - "§6抢单", - "§7接取此工单")); - } - if (assigned.equals(p.getName()) - && ("processing".equals(status) - || "replied".equals(status))) { - // 我在处理:标记完结/上报异常 - g.setItem(19, mk( - Material.LIME_DYE, - "§a标记完结", - "§7上报处理完成")); - g.setItem(20, mk( - Material.REDSTONE, - "§c上报异常", - "§7报告处理中的问题")); - } - } - - g.setItem(22, mk(Material.ARROW, - "§7返回")); - - ItemStack info = g.getItem(10); - if (info != null) { - ItemMeta im = info.getItemMeta(); - if (im != null) { - im.setCustomModelData(tid); - info.setItemMeta(im); - } - } - p.openInventory(g); - } - - - public void openAdminList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, T_TICKET_ADMIN); - fillBg(g); - g.setItem(45, mk(Material.EMERALD, - "§a邀请服务商")); - g.setItem(46, mk(Material.BARRIER, - "§c移除服务商")); - g.setItem(47, mk(Material.CLOCK, - "§e服务商列表")); - g.setItem(48, mk(Material.NETHER_STAR, - "§e直接派单", - "§7管理员直接创建并派单")); - List> all = - new ArrayList<>(); - all.addAll(plugin.getDb() - .getTicketsByStatus("pending")); - all.addAll(plugin.getDb() - .getTicketsByStatus("processing")); - all.addAll(plugin.getDb() - .getTicketsByStatus("replied")); - all.addAll(plugin.getDb() - .getTicketsByStatus("completed")); - all.addAll(plugin.getDb() - .getTicketsByStatus("resolved")); - all.addAll(plugin.getDb() - .getTicketsByStatus("closed")); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - - public void openProviderPanel(Player p) { - Inventory g = Bukkit.createInventory( - null, 27, "§d§l服务商面板"); - fillBg(g); - g.setItem(10, mk(Material.CHEST, - "§e抢单大厅", - "§7查看可接工单")); - g.setItem(12, mk(Material.CLOCK, - "§b我的工单", - "§7我正在处理的工单")); - g.setItem(14, mk(Material.PAPER, - "§6历史工单", - "§7已完成的工单")); - g.setItem(22, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openGrabList(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, "§6§l抢单大厅"); - fillBg(g); - List> pending = - plugin.getDb() - .getTicketsByStatus( - "pending"); - List> list = - new ArrayList<>(); - for (Map t : pending) { - if (sv(t, "assigned_to").isEmpty()) { - list.add(t); - } - } - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - public void openMyAssigned(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§b§l我处理中的工单"); - fillBg(g); - List> list = - plugin.getDb() - .getTicketsByProvider( - p.getName()); - fillList(g, list); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - // ========== 业务 ========== - - public int submitTicket(Player p, - String type, String title, - String desc) { - int price = PRICES - .getOrDefault(type, 5); - int id = plugin.getDb().createTicket( - type, "pending", "normal", - p.getName(), title, desc, - price, ""); - if (id > 0) { - if (TYPE_DELETE.equals(type) - || TYPE_PASSWORD.equals(type)) { - plugin.getDb().updateTicketField( - id, "assigned_to", "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage( - "§a§l[工单] §f工单 #" - + id + " 已提交," - + "等待管理员处理"); - } else { - p.sendMessage( - "§a§l[工单] §f创建成功!" - + " #§e" + id - + " 奖励: §e" - + price + "积分"); - } - notifyAdmins(p.getName(), id); - } else { - p.sendMessage("§c创建失败"); - } - return id; - } - - - public void adminCreateAndAssign( - Player p, String type, - String title, String desc, - String provider, int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - int id = plugin.getDb().createTicket( - type, "processing", "normal", - p.getName(), title, desc, - price, ""); - if (id <= 0) { - p.sendMessage("§c创建失败"); - return; - } - plugin.getDb().assignTicket( - id, provider); - if (price > 0) { - plugin.getDb().updateTicketField( - id, "reward_amount", price); - } - p.sendMessage("§a工单 #" + id - + " 已创建并分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f管理员指派工单 #" - + id + " 给您"); - playAssignedSound(tp); - } - } - - public void grabTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!"pending".equals( - sv(t, "status"))) { - p.sendMessage("§c该工单已被接单"); - return; - } - if (!sv(t, "assigned_to").isEmpty()) { - p.sendMessage("§c该工单已被分配"); - return; - } - plugin.getDb().assignTicket( - tid, p.getName()); - p.sendMessage("§a抢单成功!工单 #" + tid); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f您的工单 #" - + tid + " 已被 " - + p.getName() - + " 接单"); - } - } - - public void assignToProvider(Player p, - int tid, String provider, - int price) { - if (!plugin.getDb() - .isServiceProvider(provider)) { - p.sendMessage("§c" + provider - + " 不是服务商"); - return; - } - plugin.getDb().assignTicket( - tid, provider); - if (price > 0) { - plugin.getDb().updateTicketStatus( - tid, "processing"); - plugin.getDb().updateTicketField( - tid, "reward_amount", price); - } - p.sendMessage("§a工单 #" + tid - + " 已分配给 " + provider); - Player tp = Bukkit.getPlayer(provider); - if (tp != null && tp.isOnline()) { - tp.sendMessage( - "§e§l[工单] §f您有新工单: #" - + tid); - playAssignedSound(tp); - } - } - - public void confirmAndScore(Player p, - int tid, int score) { - if (score < 1 || score > 5) { - p.sendMessage("§c评分1-5"); - return; - } - plugin.getDb().confirmTicket( - tid, score); - p.sendMessage("§a工单 #" + tid - + " 已确认,评分: " + score); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已确认完结"); - } - } - settleTicket(tid); - } - - public void completeAsProvider(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "assigned_to") - .equals(p.getName())) { - p.sendMessage("§c这不是你的工单"); - return; - } - plugin.getDb().completeTicket(tid); - p.sendMessage("§a工单 #" + tid - + " 已标记完结,等待确认"); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 已处理完成"); - } - } - - public void rejectTicket(Player p, - int tid, String reason) { - plugin.getDb().updateTicketStatus( - tid, "closed"); - plugin.getDb().updateTicketField( - tid, "reject_reason", reason); - p.sendMessage("§c工单 #" + tid - + " 已驳回并关闭"); - Map t = - plugin.getDb().getTicket(tid); - if (t != null) { - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 被驳回: " - + reason); - } - } - } - - public void replyTicket(Player p, - int tid, String message) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - String status = sv(t, "status"); - if ("closed".equals(status) - || "withdrawn".equals(status) - || "resolved".equals(status)) { - p.sendMessage("§c该工单已关闭"); - return; - } - plugin.getDb().addTicketReply( - tid, p.getName(), message); - p.sendMessage("§a回复成功"); - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - String sender = p.getName(); - if (!requester.equals(sender)) { - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - if (!assigned.isEmpty() - && !assigned.equals(sender)) { - Player ap = Bukkit.getPlayer( - assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§e§l[工单] §f工单 #" - + tid + " 新回复"); - } - } - } - - public void showReplies(Player p, - int tid) { - List> replies = - plugin.getDb() - .getTicketReplies(tid); - if (replies.isEmpty()) { - p.sendMessage("§7暂无回复"); - return; - } - p.sendMessage("§e§l=== 回复记录 ==="); - SimpleDateFormat sdf = - new SimpleDateFormat( - "MM-dd HH:mm"); - for (Map r : replies) { - String sender = sv(r, "sender"); - String msg2 = sv(r, "message"); - long time = ((Number) r.get( - "create_time")).longValue(); - p.sendMessage("§7[" + sdf.format( - new Date(time)) + "] §f" - + sender + "§7: " + msg2); - } - p.sendMessage( - "§e§l================="); - } - - public void withdrawTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!sv(t, "requester") - .equals(p.getName()) - && !adm(p)) { - p.sendMessage("§c无权操作"); - return; - } - plugin.getDb().updateTicketStatus( - tid, "withdrawn"); - p.sendMessage("§a工单 #" + tid - + " 已撤回"); - } - - public void showProviderList(Player p) { - List> list = - plugin.getDb() - .getAllServiceProviders(); - if (list.isEmpty()) { - p.sendMessage("§7暂无服务商"); - return; - } - p.sendMessage( - "§e§l=== 服务商列表 ==="); - for (Map pro : list) { - p.sendMessage("§7- §f" - + pro.get("player_name") - + " §7角色: §e" - + pro.get("role")); - } - p.sendMessage( - "§e§l==================="); - } - - public void autoProcessCompleted() { - long timeoutMs = 10 * 60 * 1000L; - - // 自动处理删号工单(5分钟自动通过) - List> deleteTickets = - plugin.getDb() - .getTicketsByStatus( - "processing"); - for (Map t : deleteTickets) { - String dtype = sv(t, "type"); - if (!TYPE_DELETE.equals(dtype)) continue; - int did = nv(t, "id"); - int dconf = nv(t, "admin_confirmed"); - // 已经处理过了跳过 - if (dconf != 0) continue; - String dasg = sv(t, "assigned_to"); - // 只处理分配给admin的 - if (!"admin".equals(dasg)) continue; - long createTime = ((Number) t.get( - "create_time")).longValue(); - if (System.currentTimeMillis() - - createTime >= 5 * 60 * 1000L) { - processDeleteAccount(did, t); - } - } - - - // 已完结未确认→10分钟自动完结 - List> list = - plugin.getDb() - .getUnconfirmedCompleted( - timeoutMs); - for (Map t : list) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 已自动完结"); - String assigned = - sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) - pp.sendMessage( - "§e§l[工单] §f工单 #" - + id - + " 已自动完结"); - } - settleTicket(id); - } - - // 已回复但10分钟无互动→自动完结 - List> stale = - plugin.getDb() - .getRepliedStaleTickets( - timeoutMs); - for (Map t : stale) { - int id = nv(t, "id"); - plugin.getDb().confirmTicket(id, 3); - Player rp = Bukkit.getPlayer( - sv(t, "requester")); - if (rp != null && rp.isOnline()) - rp.sendMessage( - "§e§l[工单] §f工单 #" - + id + " 超时自动完结"); - settleTicket(id); - } - } - - - - // ========== GUI点击 ========== - public boolean handleClick(Player p, - String title, int slot) { - - if (title.equals(T_TICKET_MAIN)) { - if (slot == 10) openCreate(p); - else if (slot == 12) - openMyTickets(p); - else if (slot == 14) - openMyTickets(p); - else if (slot == 16) { - if (adm(p)) openAdminList(p); - else p.sendMessage("§c权限不足"); - } - else if (slot == 18) { - if (plugin.getDb() - .isServiceProvider(p.getName())) { - openProviderPanel(p); - } else { - p.sendMessage("§c权限不足"); - } - } - else if (slot == 22) - plugin.getGui().openMain(p); - return true; - } - - if (title.equals(T_TICKET_CREATE)) { - if (slot == 31) { - openMain(p); - return true; - } - String type = null; - if (slot == 10) type = TYPE_BUG; - else if (slot == 11) type = TYPE_HELP; - else if (slot == 12) type = TYPE_REPORT; - else if (slot == 13) type = TYPE_APPLY; - else if (slot == 14) type = TYPE_OTHER; - else if (slot == 20) type = TYPE_DELETE; - else if (slot == 21) type = TYPE_PASSWORD; - if (type != null) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_TITLE; - plugin.getChatInput() - .getState(p) - .ticketType = type; - if (TYPE_DELETE.equals(type)) { - p.sendMessage("§e请描述删号原因:"); - } else if (TYPE_PASSWORD.equals(type)) { - p.sendMessage("§e请描述需要协助的内容:"); - } else { - p.sendMessage("§e请输入工单标题:"); - } - } - return true; - } - - - if (title.equals(T_TICKET_MY)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals(T_TICKET_DETAIL)) { - return handleClickDetail(p, slot); - } - - if (title.equals(T_TICKET_ADMIN)) { - if (slot == 49) { - openMain(p); - return true; - } - if (slot == 45) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_INVITE_PROVIDER; - p.sendMessage("§e输入服务商玩家名:"); - return true; - } - if (slot == 46) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REMOVE_PROVIDER; - p.sendMessage("§e输入要移除的服务商:"); - return true; - } - if (slot == 47) { - showProviderList(p); - return true; - } - if (slot == 48) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ADMIN_TYPE; - p.sendMessage("§e选择工单类型:"); - p.sendMessage("§7bug/求助/举报/申请/其他"); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§d§l服务商面板")) { - if (slot == 22) { - plugin.getGui().openMain(p); - return true; - } - if (slot == 10) openGrabList(p); - else if (slot == 12) openMyAssigned(p); - else if (slot == 14) openMyTickets(p); - return true; - } - - if (title.equals("§6§l抢单大厅")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - if (title.equals("§b§l我处理中的工单")) { - if (slot == 49) { - openProviderPanel(p); - return true; - } - if (slot >= 0 && slot < 45) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(slot); - int id = gt(it); - if (id > 0) openDetail(p, id); - } - return true; - } - - return false; - } - - private boolean handleClickDetail(Player p, - int slot) { - if (slot == 22) { - openMyTickets(p); - return true; - } - int id = gtFromDetail(p); - if (id <= 0) return true; - Map t = - plugin.getDb().getTicket(id); - if (t == null) return true; - - String st = sv(t, "status"); - int conf = nv(t, "admin_confirmed"); - String asg = sv(t, "assigned_to"); - - // 回复按钮 - if (slot == 15) { - if (!"closed".equals(st) - && !"withdrawn".equals(st) - && !"resolved".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPLY; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入回复内容:"); - } - return true; - } - - // 查看回复记录 - if (slot == 16) { - showReplies(p, id); - return true; - } - - // slot 19 - if (slot == 19) { - // 管理员:分配工单 - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_ASSIGN; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage( - "§e输入: 服务商名 价格"); - return true; - } - // 管理员:取消指派(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - cancelTicket(p, id); - openDetail(p, id); - return true; - } - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - - // 管理员:标记完结(已回复状态) - if (adm(p) && "replied".equals(st) - && asg.equals("admin")) { - Map tk = - plugin.getDb().getTicket(id); - String tkType = sv(tk, "type"); - if (TYPE_DELETE.equals(tkType)) { - approveDeleteTicket(p, id); - return true; - } - plugin.getDb().completeTicket(id); - p.sendMessage("§a工单 #" + id - + " 已标记完结"); - openDetail(p, id); - return true; - } - } - // 服务商:抢单 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && "pending".equals(st) - && asg.isEmpty()) { - grabTicket(p, id); - openDetail(p, id); - return true; - } - // 服务商:标记完结 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - completeAsProvider(p, id); - openDetail(p, id); - return true; - } - return true; - } - - // slot 20 - if (slot == 20) { - // 管理员:自己处理(pending) - if (adm(p) && "pending".equals(st) - && asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - plugin.getDb().updateTicketStatus( - id, "processing"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 管理员:自己接手(服务商在处理中) - if (adm(p) && "processing".equals(st) - && !asg.equals("admin") - && !asg.isEmpty()) { - plugin.getDb().assignTicket( - id, "admin"); - p.sendMessage("§a工单 #" + id - + " 已由您接手处理"); - openDetail(p, id); - return true; - } - // 服务商:上报异常 - if (plugin.getDb() - .isServiceProvider(p.getName()) - && asg.equals(p.getName()) - && ("processing".equals(st) - || "replied".equals(st))) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REPORT_ISSUE; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入异常描述:"); - return true; - } - return true; - } - - // slot 21:驳回 - if (slot == 21) { - if (adm(p) && !"closed".equals(st) - && !"resolved".equals(st) - && !"withdrawn".equals(st)) { - p.closeInventory(); - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_REJECT; - plugin.getChatInput() - .getState(p).ticketId = id; - p.sendMessage("§e输入驳回原因:"); - } - return true; - } - - return true; - } - - // ========== 聊天输入 ========== - - public boolean handleChatInput(Player p, - String msg, - ChatInputManager.InputType type) { - switch (type) { - case TICKET_TITLE: { - plugin.getChatInput() - .getState(p) - .ticketTitle = msg; - plugin.getChatInput() - .getState(p).type = - ChatInputManager.InputType - .TICKET_DESC; - p.sendMessage( - "§e请输入工单详细描述:"); - return true; - } - case TICKET_DESC: { - String tt = plugin.getChatInput() - .getState(p).ticketType; - String tl = plugin.getChatInput() - .getState(p).ticketTitle; - plugin.getChatInput().reset(p); - submitTicket(p, tt, tl, msg); - return true; - } - case TICKET_SCORE: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - try { - int sc = Integer.parseInt( - msg.trim()); - Map tk = - plugin.getDb() - .getTicket(tid2); - String st = tk != null - ? sv(tk, "status") - : ""; - if ("completed".equals(st)) { - plugin.getChatInput() - .reset(p); - confirmAndScore( - p, tid2, sc); - } else { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c工单尚未处理完成"); - } - } catch ( - NumberFormatException e) { - plugin.getChatInput() - .reset(p); - p.sendMessage( - "§c请输入数字1-5"); - } - return true; - } - case TICKET_REJECT: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - plugin.getChatInput().reset(p); - rejectTicket(p, tid2, msg); - return true; - } - case TICKET_ASSIGN: { - int tid2 = plugin.getChatInput() - .getState(p).ticketId; - String[] parts = msg.trim() - .split("\\s+"); - String prov = parts[0]; - int price = 0; - if (parts.length >= 2) { - try { - price = Integer.parseInt( - parts[1]); - } catch ( - NumberFormatException e) { - p.sendMessage( - "§c价格格式错误"); - } - } - plugin.getChatInput().reset(p); - assignToProvider(p, tid2, - prov, price); - runSync(() -> - openDetail(p, tid2)); - return true; - } - case TICKET_INVITE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .userExists(tgt)) { - p.sendMessage( - "§c玩家不存在"); - return true; - } - plugin.getDb() - .addServiceProvider( - tgt, "waiter"); - p.sendMessage("§a已邀请 " + tgt - + " 成为服务商"); - return true; - } - case TICKET_REMOVE_PROVIDER: { - String tgt = msg.trim(); - plugin.getChatInput().reset(p); - if (!plugin.getDb() - .isServiceProvider( - tgt)) { - p.sendMessage("§c" + tgt - + " 不是服务商"); - return true; - } - plugin.getDb() - .removeServiceProvider( - tgt); - p.sendMessage( - "§a已移除 " + tgt); - return true; - } - default: - return false; - } - } - - // ========== 私有 ========== - - private void settleTicket(int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) return; - String requester = - sv(t, "requester"); - String assigned = - sv(t, "assigned_to"); - int reward = nv(t, "reward_amount"); - int score = nv(t, "score"); - ConfigManager cfg = - plugin.getConfig2(); - if (reward > 0) { - plugin.getDb().addPoints( - requester, reward); - Player rp = Bukkit.getPlayer( - requester); - if (rp != null && rp.isOnline()) { - rp.sendMessage( - "§e§l[工单] §f获得 " - + reward - + " 积分奖励"); - } - } - if (!assigned.isEmpty()) { - int pts = (int) (score - * cfg.providerPointsPerScore); - if (pts > 0) { - plugin.getDb().addPoints( - assigned, pts); - } - double money = - cfg.baseEconomyReward - + score - * cfg.providerEconomyPerScore; - boolean ok = false; - try { - if (plugin.getEconomy() != null) { - OfflinePlayer op = - Bukkit.getOfflinePlayer( - assigned); - plugin.getEconomy() - .depositPlayer( - op, money); - ok = true; - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 经济失败: " - + e.getMessage()); - } - Player pp = - Bukkit.getPlayer(assigned); - if (pp != null && pp.isOnline()) { - pp.sendMessage( - "§a§l[工单] §f工单 #" - + tid - + " 确认完结"); - if (pts > 0) - pp.sendMessage("§7积分: §e" - + pts); - if (ok) - pp.sendMessage("§7经济: §a$" - + String.format( - "%.0f", money)); - } - } - } - - private void notifyAdmins(String creator, - int tid) { - String tag = - plugin.getConfig2().adminTag; - for (Player op : - Bukkit.getOnlinePlayers()) { - if (op.getScoreboardTags() - .contains(tag)) { - op.sendMessage( - "§e§l[工单] §f" + creator - + " 提交了新工单 #" - + tid); - } - } - } - - private void playAssignedSound(Player p) { - for (int i = 0; i < 3; i++) { - final int d = i * 15; - Bukkit.getScheduler() - .runTaskLater(plugin, () -> { - if (p.isOnline()) { - 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); - } - }, d); - } - } - - private void runSync(Runnable task) { - Bukkit.getScheduler() - .runTask(plugin, task); - } - - private boolean adm(Player p) { - return p.getScoreboardTags().contains( - plugin.getConfig2().adminTag); - } - - private void fillBg(Inventory g) { - ItemStack glass = mk( - Material.GRAY_STAINED_GLASS_PANE, - " "); - for (int i = 0; i < g.getSize(); i++) { - if (g.getItem(i) == null - || g.getItem(i).getType() - == Material.AIR) { - g.setItem(i, glass); - } - } - } - - private void fillList(Inventory g, - List> list) { - if (list.isEmpty()) { - g.setItem(22, mk(Material.BARRIER, - "§7暂无工单")); - return; - } - int slot = 0; - for (Map t : list) { - if (slot >= 45) break; - g.setItem(slot, makeItem(t)); - slot++; - } - } - /** - * 执行删号流程 - */ - /** - * 执行删号流程 - */ - public void processDeleteAccount(int tid, - Map t) { - String requester = sv(t, "requester"); - plugin.getDb().updateTicketStatus( - tid, "resolved"); - plugin.getDb().updateTicketField(tid, - "admin_confirmed", 1); - Player target = - Bukkit.getPlayer(requester); - if (target != null && target.isOnline()) { - plugin.backupInventory(target); - target.sendMessage( - "§a§l[工单] §f您的删号请求已通过"); - target.sendMessage( - "§7物品已备份,下次登录可取回"); - target.kickPlayer( - "§a删号成功,物品已备份。\n" - + "请等待5分钟后重新注册取回物品。"); - } else { - // 离线:仅标记,不操作数据库 - plugin.getLogger().info( - "[Sdf1_login] " + requester - + " 删号工单已通过,等待上线执行"); - } - // 延迟执行删号 - final String name = requester; - Bukkit.getScheduler().runTaskLater( - plugin, () -> { - plugin.getDb().deleteUser(name); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 删号工单已执行"); - }, 100L); - } - - /** - * 管理员手动批准删号 - */ - public void approveDeleteTicket(Player p, - int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - if (!TYPE_DELETE.equals(sv(t, "type"))) { - p.sendMessage("§c这不是删号工单"); - return; - } - p.sendMessage("§a工单 #" + tid - + " 已批准,正在执行删号..."); - processDeleteAccount(tid, t); - } - - /** - * 管理员取消工单(服务商异常后) - */ - /** - * 管理员取消工单(解除服务商绑定,管理员可自行处理) - */ - public void cancelTicket(Player p, int tid) { - Map t = - plugin.getDb().getTicket(tid); - if (t == null) { - p.sendMessage("§c工单不存在"); - return; - } - // 解除服务商绑定,状态回退到pending - plugin.getDb().updateTicketField( - tid, "assigned_to", ""); - plugin.getDb().updateTicketStatus( - tid, "pending"); - p.sendMessage("§a工单 #" + tid - + " 已取消指派,回到待分配状态"); - // 通知原服务商 - String assigned = sv(t, "assigned_to"); - if (!assigned.isEmpty()) { - Player ap = Bukkit.getPlayer(assigned); - if (ap != null && ap.isOnline()) { - ap.sendMessage( - "§c§l[工单] §f工单 #" - + tid - + " 已被管理员取消指派"); - } - } - } - - - /** - * 服务商查看历史工单 - */ - public void openProviderHistory(Player p) { - Inventory g = Bukkit.createInventory( - null, 54, - "§6§l我的历史工单"); - fillBg(g); - List> all = - plugin.getDb() - .getTicketsByProviderAll( - p.getName()); - fillList(g, all); - g.setItem(49, mk(Material.ARROW, - "§7返回")); - p.openInventory(g); - } - - private ItemStack makeItem( - Map t) { - int id = nv(t, "id"); - String type = sv(t, "type"); - String status = sv(t, "status"); - String requester = - sv(t, "requester"); - String title = sv(t, "title"); - int reward = nv(t, "reward_amount"); - Material mat; - switch (status) { - case "processing": - mat = Material.YELLOW_DYE; - break; - case "replied": - mat = Material.CYAN_DYE; - break; - case "completed": - mat = Material.LIME_DYE; - break; - case "resolved": - mat = Material.LIME_DYE; - break; - case "closed": - mat = Material.GRAY_DYE; - break; - case "rejected": - mat = Material.RED_DYE; - break; - case "withdrawn": - mat = Material.GRAY_DYE; - break; - default: - mat = Material.ORANGE_DYE; - break; - } - ItemStack item = new ItemStack(mat); - ItemMeta im = item.getItemMeta(); - if (im != null) { - im.setDisplayName("§e#" + id - + " " + (title.isEmpty() - ? type : title)); - List lore = - new ArrayList<>(); - lore.add("§7类型: §f" + type); - lore.add("§7状态: " - + stxt(status)); - lore.add("§7提交者: §f" - + requester); - lore.add("§7奖励: §e" + reward - + " 积分"); - lore.add(""); - lore.add("§7点击查看详情"); - im.setLore(lore); - im.setCustomModelData(id); - item.setItemMeta(im); - } - return item; - } - - private int gt(ItemStack item) { - if (item == null) return -1; - ItemMeta im = item.getItemMeta(); - if (im == null) return -1; - if (im.hasCustomModelData()) - return im.getCustomModelData(); - return -1; - } - - private int gtFromDetail(Player p) { - ItemStack it = p.getOpenInventory() - .getTopInventory().getItem(10); - return gt(it); - } - - private String stxt(String status) { - switch (status) { - case "pending": - return "§e待处理"; - case "processing": - return "§b处理中"; - case "replied": - return "§9已回复"; - case "completed": - return "§a待确认"; - case "resolved": - return "§a已完结"; - case "closed": - return "§7已关闭"; - case "rejected": - return "§c已驳回"; - case "withdrawn": - return "§7已撤回"; - default: - return "§7" + status; - } - } - - private String sv(Map m, - String key) { - Object v = m.get(key); - return v != null - ? String.valueOf(v) : ""; - } - - private int nv(Map m, - String key) { - Object v = m.get(key); - return v instanceof Number - ? ((Number) v).intValue() : 0; - } - - private ItemStack mk(Material mat, - String name, String... lore) { - ItemStack it = new ItemStack(mat); - ItemMeta im = it.getItemMeta(); - if (im != null) { - im.setDisplayName(name); - if (lore.length > 0) { - List list = - new ArrayList<>(); - for (String l : lore) { - if (!l.isEmpty()) - list.add(l); - } - if (!list.isEmpty()) - im.setLore(list); - } - it.setItemMeta(im); - } - return it; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/UpdateChecker.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/UpdateChecker.java deleted file mode 100644 index c5c01bb..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/UpdateChecker.java +++ /dev/null @@ -1,523 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URL; - -public class UpdateChecker { - - private final Main plugin; - private final String currentVersion; - - // ===== 仓库信息 ===== - private static final String GITHUB_OWNER = - "ypsdf1"; - private static final String GITHUB_REPO = - "sdf1_login"; - private static final String GITEE_OWNER = - "nihaoshidifu"; - private static final String GITEE_REPO = - "sdf1_login"; - - - // API 地址 - private static final String GITHUB_API = - "https://api.github.com/repos/" - + GITHUB_OWNER + "/" - + GITHUB_REPO - + "/tags"; - private static final String GITEE_API = - "https://gitee.com/api/v5/repos/" - + GITEE_OWNER + "/" - + GITEE_REPO - + "/tags"; - - // 仓库页面链接(给用户看的) - private static final String GITHUB_LINK = - "https://github.com/" - + GITHUB_OWNER + "/" - + GITHUB_REPO + "/releases"; - private static final String GITEE_LINK = - "https://gitee.com/" - + GITEE_OWNER + "/" - + GITEE_REPO + "/releases"; - - - // 单次请求超时(毫秒) - private static final int TIMEOUT_MS = 3000; - - // 熔断阈值 - private static final int TRIP_THRESHOLD = 5; - - // ===== 熔断状态(内存态,重启自动清除) ===== - private volatile boolean circuitTripped = - false; - private volatile int failCount = 0; - - // ===== 自动检测禁用(持久化到文件) ===== - private volatile boolean autoCheckDisabled = - false; - private final File disabledFile; - - public UpdateChecker(Main plugin) { - this.plugin = plugin; - this.currentVersion = - plugin.getDescription() - .getVersion(); - this.disabledFile = new File( - plugin.getDataFolder(), - "update_disabled.dat"); - loadDisabledState(); - } - - // ========== 持久化禁用状态 ========== - - private void loadDisabledState() { - if (disabledFile.exists()) { - try (BufferedReader br = - new BufferedReader( - new FileReader( - disabledFile))) { - String line = br.readLine(); - if (line != null) { - autoCheckDisabled = - "true".equals( - line.trim()); - } - } catch (Exception ignored) { - } - } - } - - private void saveDisabledState() { - try (PrintWriter pw = - new PrintWriter( - new FileWriter( - disabledFile))) { - pw.println(autoCheckDisabled - ? "true" : "false"); - } catch (Exception ignored) { - } - } - - // ========== 公开方法 ========== - - /** - * onEnable 调用(异步静默) - * 熔断或禁用则直接跳过 - */ - public void checkOnEnable() { - if (autoCheckDisabled) return; - if (circuitTripped) return; - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> doCheck(null, true)); - } - - /** - * /sdf1_login update 调用(异步) - * 忽略熔断和禁用状态,强制检测 - * 只有拿到版号才解除熔断 - */ - public void checkUpdate( - CommandSender sender) { - Bukkit.getScheduler() - .runTaskAsynchronously(plugin, - () -> - doCheck(sender, - false)); - } - - // ========== 核心检测逻辑 ========== - private void doCheck(CommandSender sender, - boolean isAuto) { - - if (isAuto) { - if (autoCheckDisabled - || circuitTripped) { - return; - } - } - - // 通道1:GitHub - CheckResult gh = - tryChannel(GITHUB_API, - "GitHub", GITHUB_LINK); - - // 通道2:Gitee - CheckResult ge = - tryChannel(GITEE_API, - "Gitee", GITEE_LINK); - - // 选择成功的结果 - CheckResult chosen = null; - String failMsg = null; - - if (gh.isSuccess()) { - chosen = gh; - } else if (ge.isSuccess()) { - chosen = ge; - } else { - // 双通道均失败 - failCount++; - // 拼接两个通道的失败原因 - StringBuilder sb = - new StringBuilder(); - if (gh.error != null) { - sb.append("§7GitHub: §c") - .append(gh.error); - } - if (ge.error != null) { - if (sb.length() > 0) - sb.append("\n"); - sb.append("§7Gitee: §c") - .append(ge.error); - } - if (failCount >= TRIP_THRESHOLD) { - circuitTripped = true; - plugin.getLogger().warning( - "[Sdf1_login] 更新检测" - + "已熔断,连续失败" - + failCount + "次"); - } - if (sender != null) { - final String msg = - sb.toString(); - final boolean tripped = - circuitTripped; - final int fc = failCount; - Bukkit.getScheduler() - .runTask(plugin, () -> { - sender.sendMessage( - "§c§l[更新] " - + "检测失败"); - sender.sendMessage(msg); - sender.sendMessage( - "§e失败次数: " - + fc + "/" - + TRIP_THRESHOLD); - if (tripped) { - sender.sendMessage( - "§c已熔断," - + "请重启服务器" - + "重置"); - } - }); - } - return; - } - - // 至少一个通道成功 - failCount = 0; - - // 检查禁用信号 - if (isDisableSignal(chosen.version)) { - autoCheckDisabled = true; - saveDisabledState(); - plugin.getLogger().info( - "[Sdf1_login] 远程版本信号" - + " \"" + chosen.version - + "\",自动更新检测已禁用"); - if (sender != null) { - Bukkit.getScheduler() - .runTask(plugin, () -> - sender.sendMessage( - "§e[更新] 远程已" - + "禁用自动" - + "更新检测")); - } - return; - } - - // 手动调用:成功才解除熔断和禁用 - if (!isAuto) { - if (circuitTripped) { - circuitTripped = false; - } - if (autoCheckDisabled) { - autoCheckDisabled = false; - saveDisabledState(); - } - } - - final String src = chosen.source; - final String lnk = chosen.link; - final String rv = chosen.version; - - Bukkit.getScheduler() - .runTask(plugin, () -> { - if (sender == null) return; - - sender.sendMessage( - "§e[更新] 检测成功" - + " via: §a" + src); - sender.sendMessage( - "§e[更新] 本地版本:" - + " §f" - + currentVersion); - sender.sendMessage( - "§e[更新] 远程版本:" - + " §f" + rv); - - if (isNewer(rv, - currentVersion)) { - sender.sendMessage( - "§c[更新] 发现新" - + "版本: " + rv); - sender.sendMessage( - "§a请前往仓库下载" - + "更新:"); - sender.sendMessage( - "§a" + lnk); - } else { - sender.sendMessage( - "§a[更新] 已是" - + "最新版本"); - } - - // 显示另一通道的失败原因 - // (仅信息,不影响结果) - CheckResult other = - (src.equals("GitHub")) - ? ge : gh; - if (!other.isSuccess() - && other.error != null) { - sender.sendMessage( - "§7[更新] " - + other.source - + "不可用: " - + other.error); - } - }); - } - - - // ========== 禁用信号判断 ========== - - private boolean isDisableSignal( - String version) { - if (version == null) return false; - String v = version.trim().toLowerCase(); - return "999".equals(v) - || "关闭".equals(v) - || "false".equals(v) - || "禁用".equals(v); - } - - // ========== HTTP 请求(带超时) ========== - - // ========== 检测结果 ========== - - private static class CheckResult { - final String version; - final String source; - final String link; - final String error; - - CheckResult(String version, - String source, - String link, - String error) { - this.version = version; - this.source = source; - this.link = link; - this.error = error; - } - - boolean isSuccess() { - return version != null; - } - } - -// ========== HTTP 请求 ========== - - private CheckResult tryChannel( - String apiUrl, - String sourceName, - String link) { - HttpURLConnection conn = null; - try { - URL url = new URL(apiUrl); - conn = (HttpURLConnection) - url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty( - "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; " - + "Win64; x64) AppleWebKit/537.36 " - + "(KHTML, like Gecko) " - + "Chrome/120.0.0.0 Safari/537.36"); - conn.setRequestProperty( - "Accept", - "*/*"); - conn.setRequestProperty( - "Accept-Language", - "zh-CN,zh;q=0.9"); - conn.setConnectTimeout(TIMEOUT_MS); - conn.setReadTimeout(TIMEOUT_MS); - conn.setInstanceFollowRedirects(true); - - int code = conn.getResponseCode(); - - if (code == 404) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 未找到 (HTTP 404)"); - } - if (code == 403) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 被限流 (HTTP 403)"); - } - if (code != 200) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": HTTP " + code); - } - - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - conn - .getInputStream())); - StringBuilder sb = - new StringBuilder(); - String line; - while ((line = reader.readLine()) - != null) { - sb.append(line); - } - reader.close(); - - String tag = - parseTagName(sb.toString()); - if (tag == null) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 无法解析版本号"); - } - - return new CheckResult(tag, - sourceName, link, null); - - } catch (java.net - .SocketTimeoutException e) { - return new CheckResult(null, - sourceName, link, - sourceName + ": 连接超时"); - } catch (java.net.ConnectException e) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": 连接被拒绝"); - } catch (java.net - .UnknownHostException e) { - return new CheckResult(null, - sourceName, link, - sourceName - + ": DNS解析失败"); - } catch (Exception e) { - return new CheckResult(null, - sourceName, link, - sourceName + ": " - + e.getMessage()); - } finally { - if (conn != null) { - conn.disconnect(); - } - } - } - - - - /** - * 从 JSON 中提取 tag_name - */ - private String parseTagName(String json) { - // tags API 返回数组: [{"name":"v1.1",...}] - // releases API 返回对象: {"tag_name":"v1.1"} - - // 尝试 tag_name(releases格式) - int idx = - json.indexOf("\"tag_name\""); - if (idx >= 0) { - int colon = - json.indexOf(":", idx); - int q1 = - json.indexOf("\"", colon + 1); - int q2 = - json.indexOf("\"", q1 + 1); - if (q2 > q1) { - return cleanTag( - json.substring( - q1 + 1, q2)); - } - } - - // 尝试 "name"(tags格式) - idx = json.indexOf("\"name\""); - if (idx >= 0) { - int colon = - json.indexOf(":", idx); - int q1 = - json.indexOf("\"", colon + 1); - int q2 = - json.indexOf("\"", q1 + 1); - if (q2 > q1) { - return cleanTag( - json.substring( - q1 + 1, q2)); - } - } - - return null; - } - - private String cleanTag(String tag) { - if (tag.startsWith("v") - || tag.startsWith("V")) { - tag = tag.substring(1); - } - return tag.isEmpty() ? null : tag; - } - - - // ========== 版本比较 ========== - - private boolean isNewer(String remote, - String local) { - String[] r = remote.split("\\."); - String[] l = local.split("\\."); - int len = Math.max(r.length, l.length); - for (int i = 0; i < len; i++) { - int ri = i < r.length - ? parseNum(r[i]) : 0; - int li = i < l.length - ? parseNum(l[i]) : 0; - if (ri > li) return true; - if (ri < li) return false; - } - return false; - } - - private int parseNum(String s) { - try { - return Integer.parseInt( - s.replaceAll("[^0-9]", "")); - } catch (Exception e) { - return 0; - } - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/UserGroupManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/UserGroupManager.java deleted file mode 100644 index adaf907..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/UserGroupManager.java +++ /dev/null @@ -1,370 +0,0 @@ -package Sdf1_login; - -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.sql.*; -import java.util.*; -import java.util.Base64; -import java.util.concurrent.ConcurrentHashMap; - -public class UserGroupManager { - - private final Main plugin; - private Connection db; - private final Map groupConfigs = new ConcurrentHashMap<>(); - - public UserGroupManager(Main plugin) { - this.plugin = plugin; - initDB(); - loadGroupConfigs(); - startExpiryChecker(); - } - - // ===== DB ===== - - private void initDB() { - try { - Class.forName("org.sqlite.JDBC"); - File dbFile = new File(plugin.getDataFolder(), "usergroup.db"); - db = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath()); - Statement st = db.createStatement(); - st.execute("PRAGMA journal_mode=WAL"); - st.execute("CREATE TABLE IF NOT EXISTS user_group_config (" - + "group_name TEXT PRIMARY KEY," - + "display_name TEXT DEFAULT ''," - + "display_color TEXT DEFAULT '§f'," - + "display_emoji TEXT DEFAULT ''," - + "priority INTEGER DEFAULT 0," - + "is_permanent INTEGER DEFAULT 1," - + "duration_minutes INTEGER DEFAULT 0," - + "pack_url TEXT DEFAULT ''," - + "pack_hash TEXT DEFAULT ''," - + "pack_prompt TEXT DEFAULT 'Custom Resource Pack'," - + "pack_size INTEGER DEFAULT 0)"); - st.execute("CREATE TABLE IF NOT EXISTS user_group_member (" - + "player_name TEXT NOT NULL," - + "group_name TEXT NOT NULL," - + "added_by TEXT DEFAULT 'system'," - + "added_time INTEGER DEFAULT 0," - + "expiry_time INTEGER DEFAULT 0," - + "PRIMARY KEY(player_name, group_name))"); - st.close(); - } catch (Exception e) { - throw new RuntimeException("[UserGroup] DB init failed: " + e.getMessage(), e); - } - } - - // ===== 配置加载 ===== - - public void loadGroupConfigs() { - groupConfigs.clear(); - File dir = new File(plugin.getDataFolder(), "用户组"); - if (!dir.exists()) dir.mkdirs(); - File[] files = dir.listFiles((d, n) -> n.endsWith(".txt")); - if (files == null) return; - - for (File f : files) { - try { - List lines = java.nio.file.Files.readAllLines( - f.toPath(), StandardCharsets.UTF_8); - UserGroupConfig cfg = parseGroupConfig(lines); - if (cfg != null) { - groupConfigs.put(cfg.name, cfg); - saveGroupConfigToDB(cfg); - } - } catch (Exception e) { - plugin.getLogger().warning("[UserGroup] Failed to load: " + f.getName()); - } - } - } - - private UserGroupConfig parseGroupConfig(List lines) { - UserGroupConfig cfg = new UserGroupConfig(); - boolean inBlock = false; - - for (String raw : lines) { - String l = raw; - - // 块注释 - if (l.contains("")) { inBlock = false; continue; } - if (l.contains("/*")) { inBlock = true; continue; } - if (l.contains("*/")) { inBlock = false; continue; } - if (inBlock) continue; - - // 行注释 - l = l.trim(); - if (l.isEmpty()) continue; - if (l.startsWith("#") || l.startsWith("//")) continue; - - // 行内注释 - int h = l.indexOf('#'); - int s = l.indexOf("//"); - if (h >= 0) l = l.substring(0, h).trim(); - if (s >= 0 && (h < 0 || s < h)) l = l.substring(0, s).trim(); - if (l.isEmpty()) continue; - - // 解析键值对 - String key = l; - String val = ""; - int idx = l.indexOf('\uff1a'); - if (idx < 0) idx = l.indexOf(':'); - if (idx >= 0) { - key = l.substring(0, idx).trim(); - val = l.substring(idx + 1).trim(); - if (val.startsWith("\"") && val.endsWith("\"")) - val = val.substring(1, val.length() - 1); - } - - switch (key) { - case "组名": - case "name": cfg.name = val; break; - case "显示名": - case "display": cfg.displayName = val; break; - case "颜色": - case "color": cfg.displayColor = val; break; - case "符号": - case "emoji": cfg.displayEmoji = val; break; - case "优先级": - case "priority": - try { cfg.priority = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "类型": - case "type": - cfg.permanent = val.equalsIgnoreCase("永久") || val.equalsIgnoreCase("permanent"); - break; - case "时长": - case "duration": - try { cfg.durationMinutes = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "资源包URL": - case "pack_url": cfg.packUrl = val; break; - case "资源包Hash": - case "pack_hash": cfg.packHash = val; break; - case "资源包提示": - case "pack_prompt": cfg.packPrompt = val; break; - case "资源包大小": - case "pack_size": - try { cfg.packSize = Integer.parseInt(val); } catch (Exception ignored) {} - break; - case "本地文件": - case "local_file": cfg.localFile = val; break; - } - } - - if (cfg.name == null || cfg.name.isEmpty()) return null; - return cfg; - } - - private void saveGroupConfigToDB(UserGroupConfig cfg) { - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO user_group_config " - + "(group_name, display_name, display_color, display_emoji," - + " priority, is_permanent, duration_minutes," - + " pack_url, pack_hash, pack_prompt, pack_size)" - + " VALUES (?,?,?,?,?,?,?,?,?,?,?)"); - ps.setString(1, cfg.name); - ps.setString(2, cfg.displayName); - ps.setString(3, cfg.displayColor); - ps.setString(4, cfg.displayEmoji); - ps.setInt(5, cfg.priority); - ps.setInt(6, cfg.permanent ? 1 : 0); - ps.setInt(7, cfg.durationMinutes); - ps.setString(8, cfg.packUrl); - ps.setString(9, cfg.packHash); - ps.setString(10, cfg.packPrompt); - ps.setInt(11, cfg.packSize); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ===== 玩家操作 ===== - - public boolean addPlayer(String player, String groupName, String addedBy, long durationMinutes) { - UserGroupConfig cfg = groupConfigs.get(groupName); - if (cfg == null) return false; - long now = System.currentTimeMillis(); - long expiry = cfg.permanent ? 0 : now + durationMinutes * 60 * 1000; - if (!cfg.permanent && durationMinutes > 0) expiry = now + durationMinutes * 60 * 1000; - else if (!cfg.permanent) expiry = now + cfg.durationMinutes * 60 * 1000; - try { - PreparedStatement ps = db.prepareStatement( - "INSERT OR REPLACE INTO user_group_member " - + "(player_name, group_name, added_by, added_time, expiry_time)" - + " VALUES (?,?,?,?,?)"); - ps.setString(1, player); - ps.setString(2, groupName); - ps.setString(3, addedBy); - ps.setLong(4, now); - ps.setLong(5, expiry); - ps.executeUpdate(); - ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - Player p = plugin.getServer().getPlayerExact(player); - if (p != null) sendResourcePack(p); - return true; - } - - public boolean removePlayer(String player, String groupName) { - try { - PreparedStatement ps = db.prepareStatement( - "DELETE FROM user_group_member WHERE player_name=? AND group_name=?"); - ps.setString(1, player); - ps.setString(2, groupName); - int rows = ps.executeUpdate(); - ps.close(); - return rows > 0; - } catch (SQLException e) { - e.printStackTrace(); - return false; - } - } - - public List> getPlayerGroups(String player) { - List> list = new ArrayList<>(); - try { - PreparedStatement ps = db.prepareStatement( - "SELECT * FROM user_group_member WHERE player_name=?"); - ps.setString(1, player); - ResultSet rs = ps.executeQuery(); - long now = System.currentTimeMillis(); - while (rs.next()) { - Map row = new LinkedHashMap<>(); - row.put("group_name", rs.getString("group_name")); - row.put("added_by", rs.getString("added_by")); - long expiry = rs.getLong("expiry_time"); - row.put("expiry_time", expiry); - row.put("expired", expiry > 0 && expiry <= now); - list.add(row); - } - rs.close(); ps.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - return list; - } - - public UserGroupConfig getHighestGroup(String player) { - List> groups = getPlayerGroups(player); - long now = System.currentTimeMillis(); - UserGroupConfig best = null; - for (Map g : groups) { - long expiry = (long) g.get("expiry_time"); - if (expiry > 0 && expiry <= now) continue; - String name = (String) g.get("group_name"); - UserGroupConfig cfg = groupConfigs.get(name); - if (cfg != null && (best == null || cfg.priority > best.priority)) { - best = cfg; - } - } - return best; - } - - public boolean isPlayerInGroup(String player, String groupName) { - try { - PreparedStatement ps = db.prepareStatement( - "SELECT 1 FROM user_group_member WHERE player_name=? AND group_name=?"); - ps.setString(1, player); - ps.setString(2, groupName); - boolean exists = ps.executeQuery().next(); - ps.close(); - return exists; - } catch (SQLException e) { - return false; - } - } - - // ===== 过期自动移除 ===== - - private void startExpiryChecker() { - new BukkitRunnable() { - @Override - public void run() { - checkExpiry(); - } - }.runTaskTimerAsynchronously(plugin, 1200L, 1200L); // 每分钟检查 - } - - private void checkExpiry() { - try { - long now = System.currentTimeMillis(); - PreparedStatement ps = db.prepareStatement( - "SELECT player_name, group_name FROM user_group_member " - + "WHERE expiry_time > 0 AND expiry_time <= ?"); - ps.setLong(1, now); - ResultSet rs = ps.executeQuery(); - List expired = new ArrayList<>(); - while (rs.next()) { - expired.add(new String[]{ - rs.getString("player_name"), - rs.getString("group_name")}); - } - rs.close(); ps.close(); - - for (String[] e : expired) { - PreparedStatement del = db.prepareStatement( - "DELETE FROM user_group_member WHERE player_name=? AND group_name=?"); - del.setString(1, e[0]); - del.setString(2, e[1]); - del.executeUpdate(); - del.close(); - plugin.getLogger().info("[UserGroup] 过期移除: " + e[0] + " -> " + e[1]); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } - - // ===== 资源包分发 ===== - - public void sendResourcePack(Player p) { - UserGroupConfig cfg = getHighestGroup(p.getName()); - if (cfg == null || cfg.packUrl == null || cfg.packUrl.isEmpty()) return; - - String url = cfg.packUrl; - String prompt = cfg.packPrompt != null - ? cfg.packPrompt : "Custom Resource Pack"; - - plugin.getServer().getScheduler().runTaskLater(plugin, () -> { - try { - p.setResourcePack(url, new byte[0], prompt, false); - plugin.getLogger().info("[UserGroup] 资源包已发送: " - + p.getName() + " -> " + cfg.displayName); - } catch (Exception e) { - plugin.getLogger().warning( - "[UserGroup] 资源包发送失败: " + e.getMessage()); - } - }, 40L); - } - - public Map getGroupConfigs() { - return groupConfigs; - } - - // ===== 配置类 ===== - - public static class UserGroupConfig { - public String name; - public String displayName = ""; - public String displayColor = "§f"; - public String displayEmoji = ""; - public int priority = 0; - public boolean permanent = true; - public int durationMinutes = 0; - public String packUrl = ""; - public String packHash = ""; - public String packPrompt = "Custom Resource Pack"; - public int packSize = 0; - public String localFile = ""; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/VerificationManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/VerificationManager.java deleted file mode 100644 index 3fc4ceb..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/VerificationManager.java +++ /dev/null @@ -1,158 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.UUID; -import java.util.function.Consumer; - -public class VerificationManager { - - private final Main plugin; - - public VerificationManager(Main plugin) { - this.plugin = plugin; - } - - public boolean isBedrockPlayer(Player p) { - String uuid = p.getUniqueId().toString(); - return uuid.contains("f851ba4") - || uuid.startsWith("00000000-0000-0000"); - } - - public boolean isOnlineMode() { - return Bukkit.getServer().getOnlineMode(); - } - - public void verifyPremiumAsync(Player player, - Consumer callback) { - Thread t = new Thread(() -> doVerify(player, callback), - "Sdf1_login-PremiumVerify"); - t.setDaemon(true); - t.start(); - } - - private void doVerify(Player player, - Consumer callback) { - String name = player.getName(); - String uuid = player.getUniqueId().toString(); - - plugin.getLogger().info( - "[Sdf1_login] ====== 验证开始 ======"); - plugin.getLogger().info( - "[Sdf1_login] 玩家: " + name); - plugin.getLogger().info( - "[Sdf1_login] 当前UUID: " + uuid); - - try { - UUID offlineUuid = UUID.nameUUIDFromBytes( - ("OfflinePlayer:" + name) - .getBytes(StandardCharsets.UTF_8)); - String offlineStr = offlineUuid.toString(); - - plugin.getLogger().info( - "[Sdf1_login] 离线UUID: " + offlineStr); - - boolean isOfflineUuid = - player.getUniqueId().equals(offlineUuid); - - plugin.getLogger().info( - "[Sdf1_login] UUID匹配离线格式: " - + isOfflineUuid); - - if (!isOfflineUuid) { - plugin.getLogger().info( - "[Sdf1_login] 结论: 正版玩家"); - plugin.getLogger().info( - "[Sdf1_login] ====== 验证结束 ======"); - callback.accept(true); - return; - } - - plugin.getLogger().info( - "[Sdf1_login] UUID为离线格式," - + "查询Mojang API..."); - UUID mojangUuid = fetchMojangUuid(name); - - if (mojangUuid != null) { - plugin.getLogger().info( - "[Sdf1_login] Mojang返回UUID: " - + mojangUuid); - plugin.getLogger().info( - "[Sdf1_login] 结论: 盗版客户端" - + "使用了正版名字"); - } else { - plugin.getLogger().info( - "[Sdf1_login] Mojang无此名字"); - plugin.getLogger().info( - "[Sdf1_login] 结论: 纯离线玩家"); - } - - plugin.getLogger().info( - "[Sdf1_login] ====== 验证结束 ======"); - callback.accept(false); - - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] 验证异常: " - + e.getMessage()); - callback.accept(false); - } - } - - private UUID fetchMojangUuid(String name) { - HttpURLConnection conn = null; - try { - URL url = new URL( - "https://api.mojang.com/users/profiles/minecraft/" - + name); - conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - conn.setRequestMethod("GET"); - - int code = conn.getResponseCode(); - plugin.getLogger().info( - "[Sdf1_login] Mojang API响应: " + code); - - if (code == 200) { - BufferedReader reader = - new BufferedReader( - new InputStreamReader( - conn.getInputStream(), - StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - } - reader.close(); - - String json = sb.toString(); - String uuidStr = json.replaceAll( - ".*\"id\"\\s*:\\s*\"([^\"]+)\".*", - "$1"); - plugin.getLogger().info( - "[Sdf1_login] Mojang原始UUID: " - + uuidStr); - - String formatted = uuidStr.replaceFirst( - "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", - "$1-$2-$3-$4-$5"); - return UUID.fromString(formatted); - } - } catch (Exception e) { - plugin.getLogger().warning( - "[Sdf1_login] Mojang查询异常: " - + e.getMessage()); - } finally { - if (conn != null) conn.disconnect(); - } - return null; - } -} diff --git a/backup/src_backup_20260605_125051/main/java/Sdf1_login/WelcomeManager.java b/backup/src_backup_20260605_125051/main/java/Sdf1_login/WelcomeManager.java deleted file mode 100644 index 68bd3ef..0000000 --- a/backup/src_backup_20260605_125051/main/java/Sdf1_login/WelcomeManager.java +++ /dev/null @@ -1,163 +0,0 @@ -package Sdf1_login; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -public class WelcomeManager { - - private final Main plugin; - private final Random random = new Random(); - private final List registerMsgs = - new ArrayList<>(); - private final List loginMsgs = - new ArrayList<>(); - - public WelcomeManager(Main plugin) { - this.plugin = plugin; - loadMessages(); - } - - public void loadMessages() { - registerMsgs.clear(); - loginMsgs.clear(); - File f = new File( - plugin.getDataFolder(), - "欢迎仪式.txt"); - if (!f.exists()) { - writeDefault(f); - } - try (BufferedReader r = - new BufferedReader( - new InputStreamReader( - new FileInputStream(f), - StandardCharsets - .UTF_8))) { - String section = ""; - String line; - while ((line = r.readLine()) != null) { - if (line.startsWith("\uFEFF")) - line = line.substring(1); - String t = line.trim(); - if (t.isEmpty() - || t.startsWith("#")) - continue; - if (t.equals("注册") - || t.equals("注册:")) { - section = "register"; - continue; - } - if (t.equals("登录") - || t.equals("登录:")) { - section = "login"; - continue; - } - // 支持 = 和 : 两种分隔 - if (section.isEmpty()) { - if (t.startsWith("注册") - && (t.contains("=") - || t.contains(":"))) { - int idx = t.indexOf('='); - int idx2 = t.indexOf(':'); - if (idx < 0) idx = idx2; - else if (idx2 >= 0) - idx = Math.min(idx, idx2); - String v = t.substring(idx + 1) - .trim(); - if (!v.isEmpty()) - registerMsgs.add( - colorize(v)); - continue; - } - if (t.startsWith("登录") - && (t.contains("=") - || t.contains(":"))) { - int idx = t.indexOf('='); - int idx2 = t.indexOf(':'); - if (idx < 0) idx = idx2; - else if (idx2 >= 0) - idx = Math.min(idx, idx2); - String v = t.substring(idx + 1) - .trim(); - if (!v.isEmpty()) - loginMsgs.add( - colorize(v)); - continue; - } - continue; - } - if (!t.isEmpty()) { - if (section.equals("register")) - registerMsgs.add( - colorize(t)); - else - loginMsgs.add( - colorize(t)); - } - } - } catch (Exception e) { - plugin.getLogger().severe( - "[欢迎] 加载失败: " - + e.getMessage()); - } - plugin.getLogger().info( - "[欢迎] 加载完成: 注册=" - + registerMsgs.size() - + " 登录=" + loginMsgs.size()); - } - - public void onRegister(Player p) { - sendRandom(p, registerMsgs, "注册"); - } - - public void onLogin(Player p) { - sendRandom(p, loginMsgs, "登录"); - } - - private void sendRandom(Player p, - List msgs, String tag) { - if (msgs.isEmpty()) return; - String msg = msgs.get( - random.nextInt(msgs.size())); - msg = msg.replace("{player}", - p.getName()); - final String finalMsg = msg; - Bukkit.getScheduler() - .runTaskLater(plugin, - () -> Bukkit - .broadcastMessage( - finalMsg), 10L); - } - - private String colorize(String msg) { - return msg.replace("&", "\u00a7"); - } - - private void writeDefault(File f) { - try (PrintWriter pw = new PrintWriter( - new OutputStreamWriter( - new FileOutputStream(f), - StandardCharsets - .UTF_8))) { - pw.println("# 欢迎仪式配置"); - pw.println("# &为颜色代码"); - pw.println("# {player} = 玩家名"); - pw.println(); - pw.println("注册:"); - pw.println("&a&l欢迎 &f{player} &a加入服务器!&7[注册]"); - pw.println("&6&l{player} &e已完成注册,欢迎入驻!&7[注册]"); - pw.println("&b&l新成员 &f{player} &b已就位!&7[注册]"); - pw.println(); - pw.println("登录:"); - pw.println("&a&l欢迎回来 &f{player}&a!&7[登录]"); - pw.println("&e&l{player} &e已登录,继续冒险吧!&7[登录]"); - pw.println("&b&l{player} &b回来了!&7[登录]"); - } catch (IOException ignored) { - } - } -} diff --git a/backup/src_backup_20260605_125051/main/resources/plugin.yml b/backup/src_backup_20260605_125051/main/resources/plugin.yml deleted file mode 100644 index f6868d9..0000000 --- a/backup/src_backup_20260605_125051/main/resources/plugin.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Sdf1_login -version: 1.0 -main: Sdf1_login.Main -api-version: '1.21' -commands: - sdf1_login: - description: Sdf1_login主命令 - usage: /sdf1_login - reg: - description: 注册 - usage: /reg <密码> - login: - description: 登录 - usage: /login <密码> - l: - description: 登录快捷版 - usage: /l <密码> - 注册: - description: 注册(中文) - usage: /注册 <密码> - 登录: - description: 登录(中文) - usage: /登录 <密码> - 玩家信息: - description: 打开面板(中文) - usage: /玩家信息 - 改密码: - description: 修改密码(中文) - usage: /改密码 <旧密码> <新密码> <确认新密码> - 设置邮箱: - description: 设置邮箱(中文) - usage: /设置邮箱 <邮箱地址> - 撤销: - description: 撤销操作(中文) - usage: /撤销 - 删除: - description: 删除玩家(中文) - usage: /删除 <玩家> <密码> - 找回密码: - description: 找回密码(中文) - usage: /找回密码 - 签到: - description: 每日签到(中文) - usage: /签到 - #oa: - # description: 申请删除账号 - # usage: /oa [玩家名] - 删除账号: - description: 申请删除账号(中文) - usage: /删除账号 [玩家名] - 通过: - description: 批准OA请求(中文) - usage: /通过 <编号> - 拒绝: - description: 拒绝OA请求(中文) - usage: /拒绝 <编号> [理由] - accept: - description: 批准OA请求 - usage: /accept <编号> - deny: - description: 拒绝OA请求 - usage: /deny <编号> [理由] - email: - description: 绑定邮箱 - usage: /email <邮箱> - 绑定: - description: 绑定邮箱(中文) - usage: /绑定 <邮箱> - 绑定邮箱: - description: 绑定邮箱 - usage: /绑定邮箱 - chat: - description: 聊天过滤管理 - usage: /chat - recycle: - description: 打开垃圾回收站 - usage: /recycle - 垃圾清理: - description: 手动清理掉落物 - usage: /垃圾清理 - menu: - description: 雪球菜单开关控制 - usage: /menu <开/关> - pvp : - description: PVP榜单 - usage: /pvp - cypay : - description: 草原探险债券 - usage: /cypay - printer : - description: 打印债券流水 - usage: /printer - shop : - description: 游戏商人 - usage: /shop - 商店: - description: 打开商店 - usage: /商店 - #testorder: - # description: 测试订单中心 - # usage: /testorder - protect: - description: 区域保护 - usage: /protect \ No newline at end of file diff --git a/src/main/java/Sdf1_login/AccountRequestManager.java b/src/main/java/Sdf1_login/AccountRequestManager.java index ae5eaad..8ed2fdc 100644 --- a/src/main/java/Sdf1_login/AccountRequestManager.java +++ b/src/main/java/Sdf1_login/AccountRequestManager.java @@ -95,11 +95,24 @@ public class AccountRequestManager { public boolean approveRequest(int id) { Request req = requests.get(id); if (req == null || !"PENDING".equals(req.status)) return false; + // ★ 精准匹配:TEST 与 test 是两个人,名字必须完全相等才删 + if (plugin.getDb().findExactUserName(req.target) == null) { + String twin = plugin.getDb() + .findSimilarUserNameIgnoreCase(req.target); + System.out.println("[Sdf1_login] 删号申请 #"+ id + + " 未执行:账号 " + req.target + + (twin != null ? " 不存在,仅存在大小写不同的账号 " + twin : " 不存在") + + "(精准匹配,未误删)"); + return false; + } + int deleted = plugin.getDb().deleteUser(req.target); + if (deleted <= 0) { + return false; + } req.status = "APPROVED"; - plugin.getDb().deleteUser(req.target); pendingByPlayer.remove(req.requester); - // 清背包+传送出生点+踢出 - Player target = Bukkit.getPlayer(req.target); + // 清背包+传送出生点+踢出(★ getPlayerExact:避免误伤大小写不同的在线玩家) + Player target = Bukkit.getPlayerExact(req.target); if (target != null && target.isOnline()) { target.getInventory().clear(); target.getInventory().setArmorContents(new org.bukkit.inventory.ItemStack[4]); diff --git a/src/main/java/Sdf1_login/AreaProtection.java b/src/main/java/Sdf1_login/AreaProtection.java index c5339a4..1c93f1b 100644 --- a/src/main/java/Sdf1_login/AreaProtection.java +++ b/src/main/java/Sdf1_login/AreaProtection.java @@ -18,7 +18,6 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.event.block.BlockBurnEvent; import org.bukkit.event.block.BlockIgniteEvent; import org.bukkit.event.block.BlockSpreadEvent; -import org.bukkit.event.block.BlockGrowEvent; import org.bukkit.event.block.SignChangeEvent; import org.bukkit.entity.Entity; import org.bukkit.entity.Animals; @@ -2606,7 +2605,7 @@ public class AreaProtection implements Listener { try { PreparedStatement stmt = dbConnection.prepareStatement( "SELECT permissions FROM area_land_permissions " - + "WHERE land_id = ? AND player_name = ?"); + + "WHERE land_id = ? AND player_name = ? COLLATE NOCASE"); stmt.setInt(1, landId); stmt.setString(2, playerName); ResultSet rs = stmt.executeQuery(); @@ -2752,7 +2751,7 @@ public class AreaProtection implements Listener { if (dbConnection == null) return false; try { PreparedStatement stmt = dbConnection.prepareStatement( - "SELECT role FROM area_land_permissions WHERE land_id = ? AND player_name = ?"); + "SELECT role FROM area_land_permissions WHERE land_id = ? AND player_name = ? COLLATE NOCASE"); stmt.setInt(1, landId); stmt.setString(2, playerName); ResultSet rs = stmt.executeQuery(); @@ -3187,6 +3186,12 @@ public class AreaProtection implements Listener { Location to = event.getTo(); if (to == null) return; + // ★ 未登录(冻结)玩家跳过领地移动判定: + // 登录系统冻结期间玩家本就无法移动,领地系统不应在此期间 + // 对其传送/施加效果;登录成功后由 onPlayerJoin 恢复区域效果。 + // (消除登录系统与领地系统在冻结态下的交叉干扰) + if (plugin.isFrozen(p)) return; + Location from = event.getFrom(); // X/Z debounce removed to allow denyMove teleport for small movements @@ -5167,12 +5172,12 @@ public class AreaProtection implements Listener { BondManager bnd = plugin.getBonds(); int bal = (bnd != null) ? bnd.getBonds(p.getName()) : 0; p.sendMessage("§e§l[防护] §f选区完成!面积: §a" + a + "㎡§7(" + w + "×" + l + ") 单价: §f" + pricePerSqm + "/㎡" + src + " 预估: §e" + totalCost + "§7债券 余额: §a" + bal); - // ★ 一键创建超链接:自动用玩家名,冲突则加数字 + // ★ 一键创建超链接:自动用玩家名,冲突则加数字(重名不区分大小写) String autoName = p.getName(); - if (areas.containsKey(autoName)) { + if (areaNameExists(autoName)) { for (int i = 0; i <= 99; i++) { String candidate = autoName + i; - if (!areas.containsKey(candidate)) { autoName = candidate; break; } + if (!areaNameExists(candidate)) { autoName = candidate; break; } } } String finalAutoName = autoName; @@ -5528,7 +5533,8 @@ public class AreaProtection implements Listener { String name = line.trim(); if (!name.isEmpty() && !name.startsWith("#")) { - set.add(name); + // ★ 统一小写存储,与 isPlayerWhitelisted 的小写比对对齐 + set.add(name.toLowerCase()); } } if (!set.isEmpty()) { @@ -5686,19 +5692,9 @@ public class AreaProtection implements Listener { } } - // ★ 农作物状态检测(播种&收获) - @EventHandler - public void onCropHarvest(BlockGrowEvent e) { - Location loc = e.getBlock().getLocation(); - AreaConfig ac = getArea( - loc.getWorld().getName(), - loc.getBlockX(), - loc.getBlockY(), - loc.getBlockZ()); - if (ac != null && ac.denyCropHarvest) { - e.setCancelled(true); - } - } + // ★ 农作物自然生长不受 denyCropHarvest 影响。 + // denyCropHarvest 只拦截"种植/收获"交互(BlockPlace / BlockBreak / 右键瓜果), + // 此处曾误取消 BlockGrowEvent,导致领地一旦开启"禁用农作物收获"庄稼就完全不长。 // ★ 耕地破坏(玩家在耕地上跳跃/掉落时触发) @EventHandler @@ -5948,6 +5944,87 @@ public class AreaProtection implements Listener { return null; } + /** + * ★ 严格解析领地名(写操作/权限敏感操作专用) + * 只做"完全一致"匹配:先区分大小写,再忽略大小写; + * 不做包含匹配,不做编辑距离模糊匹配。 + * 目的:防止攻击者用大小写变体、部分字符串或特殊字符 + * 把输入"归一化"成他人领地名后越权操作。 + */ + public String resolveAreaNameStrict(String input) { + if (input == null || input.isEmpty()) return null; + if (input.equalsIgnoreCase("global")) return "global"; + if (areas.containsKey(input)) return input; + for (String name : areas.keySet()) { + if (name.equalsIgnoreCase(input)) return name; + } + return null; + } + + /** + * ★ 大小写不敏感的领地重名检查(创建/改名防重) + */ + public boolean areaNameExists(String name) { + if (name == null || name.isEmpty()) return false; + for (String k : areas.keySet()) { + if (k.equalsIgnoreCase(name)) return true; + } + return false; + } + + /** + * ★ 领地名格式校验(创建/改名) + * 只允许中英文、数字、下划线、连字符,1-20 字符。 + * 拒绝空格、颜色代码、斜杠、点号等特殊字符——领地名会被 + * 直接当作白名单/配置文件名落盘,特殊字符会造成文件互相 + * 覆盖或路径穿越(Windows 下文件名不区分大小写,A/a 会互覆盖)。 + * @return null=合法,非 null=错误提示 + */ + public static String validateAreaName(String name) { + if (name == null || name.trim().isEmpty()) { + return "名字不能为空"; + } + String n = name.trim(); + if (n.length() > 20) return "名字不能超过20个字符"; + if (!n.matches("^[A-Za-z0-9\u4e00-\u9fa5_-]{1,20}$")) { + return "名字只能包含中英文、数字、下划线和连字符"; + } + if (n.equalsIgnoreCase("global")) { + return "global 是保留名,请换一个"; + } + return null; + } + + /** + * ★ 写操作权限闸门:领地所有者 / 领地管理员 / 插件管理员 / 控制台 + * 凡是"解析出领地名后直接改数据"的子命令,都必须先过这道闸。 + */ + private boolean requireLandOwnership(CommandSender sender, String areaName) { + if (areaName == null || areaName.equalsIgnoreCase("global")) { + return requireGlobalAdmin(sender, "全服级(global)操作"); + } + AreaConfig ac = areas.get(areaName); + if (ac == null) { + sender.sendMessage("§c区域不存在: " + areaName); + return false; + } + if (!(sender instanceof Player)) return true; // 控制台=管理员 + if (!hasPermission((Player) sender, ac, PermissionLevel.OWNER)) { + sender.sendMessage("§c需要领地所有者或管理员权限: " + areaName); + return false; + } + return true; + } + + /** + * ★ 全服级(global)写操作闸门:仅插件管理员 / 控制台 + */ + private boolean requireGlobalAdmin(CommandSender sender, String what) { + if (isAreaAdmin(sender)) return true; + sender.sendMessage("§c" + what + " 仅管理员可用"); + return false; + } + /** * 判断字符串是否是区域名(精准或模糊) */ @@ -6549,10 +6626,10 @@ public class AreaProtection implements Listener { } else { // 有选点但没有名字:用默认名创建 String autoName = p.getName(); - if (areas.containsKey(autoName)) { + if (areaNameExists(autoName)) { for (int i = 0; i <= 999; i++) { String c = autoName + i; - if (!areas.containsKey(c)) { autoName = c; break; } + if (!areaNameExists(c)) { autoName = c; break; } } } p.sendMessage("§e用法: /protect 创建 <自定义名>"); @@ -6563,10 +6640,16 @@ public class AreaProtection implements Listener { return true; } - String areaName = args[1]; - // 检查重名(数据库+txt) - if (areas.containsKey(areaName)) { - p.sendMessage("§c区域已存在"); + String areaName = args[1].trim(); + // ★ 领地名格式校验:特殊字符会污染按名字落盘的白名单/配置文件 + String nameErr = validateAreaName(areaName); + if (nameErr != null) { + p.sendMessage("§c§l[防护] §f领地名无效: " + nameErr); + return true; + } + // 检查重名(不区分大小写:Windows 文件名大小写不敏感会互相覆盖) + if (areaNameExists(areaName)) { + p.sendMessage("§c区域已存在(大小写不同也算重复)"); return true; } // ★ 检查每人领地数量上限(用户组专属上限)— 同时查内存和DB兜底 @@ -6636,13 +6719,19 @@ public class AreaProtection implements Listener { if (!(sender instanceof Player)) { sender.sendMessage("§c仅玩家可用"); return true; } if (args.length < 2) { sender.sendMessage("§c用法: /protect confirm_create <领地名>"); return true; } Player cp = (Player) sender; - String confirmName = args[1]; + String confirmName = args[1].trim(); + // ★ 领地名格式校验(交互链接可被直接拼装,必须在这里再校验一次) + String cnErr = validateAreaName(confirmName); + if (cnErr != null) { + cp.sendMessage("§c§l[防护] §f领地名无效: " + cnErr); + return true; + } // 检查选点 if (!pos1.containsKey(cp.getUniqueId()) || !pos2.containsKey(cp.getUniqueId())) { cp.sendMessage("§c请先用工具选点"); return true; } - // 检查重名 - if (areas.containsKey(confirmName)) { cp.sendMessage("§c区域已存在"); return true; } + // 检查重名(不区分大小写) + if (areaNameExists(confirmName)) { cp.sendMessage("§c区域已存在(大小写不同也算重复)"); return true; } // ★ 检查上限(含DB兜底) UserGroupManager cugm = plugin.getUserGroup(); int cMaxLands = (cugm != null) ? cugm.getPlayerMaxLands(cp.getName(), globalMaxLandsPerPlayer) : globalMaxLandsPerPlayer; @@ -6701,6 +6790,8 @@ public class AreaProtection implements Listener { sender.sendMessage("§c区域不存在"); return true; } + // ★ 权限闸门:只有领地主/领地管理员才能改和平白名单 + if (!requireLandOwnership(sender, parsed[0])) return true; ac.peaceWhitelist.add(parsed[1]); saveAreaToDb(ac); sender.sendMessage("§a已添加: " @@ -6725,6 +6816,8 @@ public class AreaProtection implements Listener { sender.sendMessage("§c区域不存在"); return true; } + // ★ 权限闸门:只有领地主/领地管理员才能改和平白名单 + if (!requireLandOwnership(sender, parsed[0])) return true; ac.peaceWhitelist.remove(parsed[1]); saveAreaToDb(ac); sender.sendMessage("§a已移除: " @@ -6772,6 +6865,8 @@ public class AreaProtection implements Listener { sender.sendMessage("§c区域不存在"); return true; } + // ★ 权限闸门:只有领地主/领地管理员才能改模式排除名单 + if (!requireLandOwnership(sender, parsed[0])) return true; ac.modeExempt.add(parsed[1]); saveAreaToDb(ac); sender.sendMessage("§a已添加: " @@ -6796,6 +6891,8 @@ public class AreaProtection implements Listener { sender.sendMessage("§c区域不存在"); return true; } + // ★ 权限闸门:只有领地主/领地管理员才能改模式排除名单 + if (!requireLandOwnership(sender, parsed[0])) return true; ac.modeExempt.remove(parsed[1]); saveAreaToDb(ac); sender.sendMessage("§a已移除: " @@ -7301,6 +7398,8 @@ public class AreaProtection implements Listener { // ===== 物品黑名单 ===== if (sub.equals("additem")) { if (args.length == 2) { + // ★ 全服级物品黑名单,必须管理员 + if (!requireGlobalAdmin(sender, "全局物品黑名单操作")) return true; String itemName = args[1].toUpperCase(); if (Material.matchMaterial(itemName) == null) { @@ -7314,8 +7413,8 @@ public class AreaProtection implements Listener { return true; } if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); + String r1 = resolveAreaNameStrict(args[1]); + String r2 = resolveAreaNameStrict(args[2]); String areaName; String itemName; if (r1 != null) { @@ -7328,6 +7427,8 @@ public class AreaProtection implements Listener { areaName = "global"; itemName = args[1].toUpperCase(); } + // ★ 权限闸门:领地黑名单须领地主/领地管理员,global 须插件管理员 + if (!requireLandOwnership(sender, areaName)) return true; if (Material.matchMaterial(itemName) == null) { sender.sendMessage( @@ -7355,6 +7456,8 @@ public class AreaProtection implements Listener { } if (sub.equals("removeitem")) { if (args.length == 2) { + // ★ 全服级物品黑名单,必须管理员 + if (!requireGlobalAdmin(sender, "全局物品黑名单操作")) return true; String itemName = args[1].toUpperCase(); if (!globalItemBlacklist .contains(itemName)) { @@ -7368,8 +7471,8 @@ public class AreaProtection implements Listener { return true; } if (args.length == 3) { - String r1 = resolveAreaName(args[1]); - String r2 = resolveAreaName(args[2]); + String r1 = resolveAreaNameStrict(args[1]); + String r2 = resolveAreaNameStrict(args[2]); String areaName; String itemName; if (r1 != null) { @@ -7382,6 +7485,8 @@ public class AreaProtection implements Listener { areaName = "global"; itemName = args[1].toUpperCase(); } + // ★ 权限闸门:领地黑名单须领地主/领地管理员,global 须插件管理员 + if (!requireLandOwnership(sender, areaName)) return true; if (areaName.equalsIgnoreCase("global")) { globalItemBlacklist.remove(itemName); } else { @@ -7443,6 +7548,11 @@ public class AreaProtection implements Listener { p.sendMessage("§c你不在任何防护区域内"); return true; } + // ★ 权限闸门:改边界必须是领地主/领地管理员 + if (!hasPermission(p, ac, PermissionLevel.OWNER)) { + p.sendMessage("§c需要领地所有者或管理员权限"); + return true; + } int amount = 5; if (args.length >= 2) { try { @@ -7487,6 +7597,11 @@ public class AreaProtection implements Listener { p.sendMessage("§c你不在任何防护区域内"); return true; } + // ★ 权限闸门:改边界必须是领地主/领地管理员 + if (!hasPermission(p, ac, PermissionLevel.OWNER)) { + p.sendMessage("§c需要领地所有者或管理员权限"); + return true; + } int amount = 5; if (args.length >= 2) { try { @@ -7530,8 +7645,8 @@ public class AreaProtection implements Listener { return true; } String areaName = args[1]; - // 精确匹配 - String resolved = resolveAreaName(areaName); + // 严格匹配(写操作不做模糊归一化) + String resolved = resolveAreaNameStrict(areaName); if (resolved != null) areaName = resolved; if (!areas.containsKey(areaName)) { sender.sendMessage("§c领地不存在: " + areaName); @@ -7594,8 +7709,10 @@ public class AreaProtection implements Listener { return true; } String areaName = args[1]; - String resolved = resolveAreaName(areaName); + String resolved = resolveAreaNameStrict(areaName); if (resolved != null) areaName = resolved; + // ★ 权限闸门:只有领地主/领地管理员能取消删除 + if (!requireLandOwnership(sender, areaName)) return true; PendingDelete pd = pendingDeletes.remove(areaName); if (pd == null) { sender.sendMessage("§c没有待删除的领地: " + areaName); @@ -7690,13 +7807,15 @@ public class AreaProtection implements Listener { // ===== add 加白名单 ===== if (sub.equals("add")) { if (args.length == 2) { - String name = args[1]; + // ★ 全服级白名单(等于全服免检),必须管理员 + if (!requireGlobalAdmin(sender, "全局白名单操作")) return true; + String name = args[1].toLowerCase(); if (globalPlayerWhitelist.contains(name)) { sender.sendMessage("§e" + name + " 已在全局白名单中"); return true; } - globalPlayerWhitelist.add(name.toLowerCase()); + globalPlayerWhitelist.add(name); saveWhitelists(); sender.sendMessage("§a已全局加白: " + name); return true; @@ -7709,9 +7828,11 @@ public class AreaProtection implements Listener { return true; } String areaName = parsed[0]; - String playerName = parsed[1]; + String playerName = parsed[1].toLowerCase(); + // ★ 权限闸门:领地白名单须领地主/领地管理员,global 须插件管理员 + if (!requireLandOwnership(sender, areaName)) return true; if (areaName.equalsIgnoreCase("global")) { - globalPlayerWhitelist.add(playerName.toLowerCase()); + globalPlayerWhitelist.add(playerName); saveWhitelists(); sender.sendMessage("§a已全局加白: " + playerName); @@ -7720,7 +7841,7 @@ public class AreaProtection implements Listener { .computeIfAbsent(areaName, k -> ConcurrentHashMap .newKeySet()); - wl.add(playerName.toLowerCase()); + wl.add(playerName); saveWhitelists(); sender.sendMessage("§a已加白: " + playerName + " → " + areaName); @@ -7735,7 +7856,9 @@ public class AreaProtection implements Listener { // ===== remove 移除白名单 ===== if (sub.equals("remove")) { if (args.length == 2) { - String name = args[1]; + // ★ 全服级白名单,必须管理员 + if (!requireGlobalAdmin(sender, "全局白名单操作")) return true; + String name = args[1].toLowerCase(); if (!globalPlayerWhitelist.contains(name)) { sender.sendMessage("§e" + name + " 不在全局白名单中"); @@ -7755,7 +7878,9 @@ public class AreaProtection implements Listener { return true; } String areaName = parsed[0]; - String playerName = parsed[1]; + String playerName = parsed[1].toLowerCase(); + // ★ 权限闸门:领地白名单须领地主/领地管理员,global 须插件管理员 + if (!requireLandOwnership(sender, areaName)) return true; if (areaName.equalsIgnoreCase("global")) { globalPlayerWhitelist.remove(playerName); saveWhitelists(); @@ -8734,11 +8859,12 @@ public class AreaProtection implements Listener { private String[] parseAreaAndTarget(String arg1, String arg2) { - String r1 = resolveAreaName(arg1); + // ★ 严格匹配:写操作绝不做模糊归一化,避免部分/大小写/特殊字符输入命中他人领地 + String r1 = resolveAreaNameStrict(arg1); if (r1 != null) { return new String[]{r1, arg2}; } - String r2 = resolveAreaName(arg2); + String r2 = resolveAreaNameStrict(arg2); if (r2 != null) { return new String[]{r2, arg1}; } @@ -9108,7 +9234,7 @@ public class AreaProtection implements Listener { // 清除数据库中的自定义权限 PreparedStatement ps = dbConnection.prepareStatement( - "UPDATE area_land_permissions SET permissions = '' WHERE land_id = ? AND player_name = ?"); + "UPDATE area_land_permissions SET permissions = '' WHERE land_id = ? AND player_name = ? COLLATE NOCASE"); ps.setInt(1, landId); ps.setString(2, playerName); ps.executeUpdate(); @@ -9174,15 +9300,22 @@ public class AreaProtection implements Listener { * @return null=成功, 非null=错误/冷却信息 */ public String renameLand(Player p, String oldName, String newName) { - // 1. 基本校验 + // 0. 权限校验:只有领地主/领地管理员可改名(纵深防御) + AreaConfig ownerAc = areas.get(oldName); + if (ownerAc != null + && !isAreaAdmin(p) + && !p.getName().equalsIgnoreCase(ownerAc.owner)) { + return "需要领地所有者或管理员权限"; + } + // 1. 基本校验(格式 + 特殊字符,防止按名字落盘的文件被覆盖或路径穿越) if (newName == null || newName.trim().isEmpty()) return "新名字不能为空"; newName = newName.trim(); - if (newName.length() > 20) return "名字不能超过20个字符"; - if (newName.contains(" ") || newName.contains("§")) return "名字不能包含空格或颜色代码"; + String fmtErr = validateAreaName(newName); + if (fmtErr != null) return fmtErr; if (newName.equalsIgnoreCase(oldName)) return "新名字和旧名字相同"; - // 2. 重名检查 - if (getLand(newName) != null) return "已存在同名领地: " + newName; + // 2. 重名检查(不区分大小写:大小写变体会指向同一份文件) + if (areaNameExists(newName)) return "已存在同名领地: " + newName; // 3. 冷却检查 long now = System.currentTimeMillis(); @@ -9461,7 +9594,7 @@ public class AreaProtection implements Listener { if (dbConnection == null) return null; try { PreparedStatement stmt = dbConnection.prepareStatement( - "SELECT role FROM area_land_permissions WHERE land_id = ? AND player_name = ?"); + "SELECT role FROM area_land_permissions WHERE land_id = ? AND player_name = ? COLLATE NOCASE"); stmt.setInt(1, landId); stmt.setString(2, playerName); ResultSet rs = stmt.executeQuery(); @@ -11741,13 +11874,13 @@ public class AreaProtection implements Listener { } } - // ★ 自动命名:玩家名,冲突加数字0~999 + // ★ 自动命名:玩家名,冲突加数字0~999(重名判定不区分大小写) String autoName = p.getName(); - if (areas.containsKey(autoName)) { + if (areaNameExists(autoName)) { boolean found = false; for (int i = 0; i <= 999; i++) { String candidate = p.getName() + i; - if (!areas.containsKey(candidate)) { autoName = candidate; found = true; break; } + if (!areaNameExists(candidate)) { autoName = candidate; found = true; break; } } if (!found) { p.sendMessage("§c§l[防护] §f无法找到可用的领地名(已用尽0~999后缀)"); diff --git a/src/main/java/Sdf1_login/ChatInputManager.java b/src/main/java/Sdf1_login/ChatInputManager.java index 5d0666d..234ef7b 100644 --- a/src/main/java/Sdf1_login/ChatInputManager.java +++ b/src/main/java/Sdf1_login/ChatInputManager.java @@ -90,7 +90,8 @@ public class ChatInputManager { name, "password_salt"); String hash = PasswordUtils.hash(msg, salt); - String result = db.checkPasswordWithFallback(name, hash); + // ★ 同时校验真实密码 + 临时密码(改密不受临时密码5分钟有效期限制) + String result = db.checkPasswordForChange(name, hash); if ("main".equals(result)) { state.type = InputType.CHANGE_PWD_STEP2; @@ -143,11 +144,23 @@ public class ChatInputManager { "password_hash", newHash); db.setField(name, "password_salt", newSalt); + // ★ 改密成功:作废旧的临时密码 + db.clearTempPassword(name); + // ★ 同步新密码到Web端(异步,不阻塞主线程) + final Main self = mainPlugin; + Bukkit.getScheduler().runTaskAsynchronously(self, () -> { + if (self.webManager != null) { + self.webManager.pushWebLoginCredentials(); + } + }); mainPlugin .getNeedsPasswordChange() .remove(name); p.sendMessage(config.msg( "password_changed")); + if ("temp".equals(state.ticketTitle)) { + p.sendMessage("§7(本次是用临时密码改的,临时密码已作废)"); + } reset(p); return; } @@ -361,17 +374,33 @@ public class ChatInputManager { case ADMIN_DELETE_CONFIRM: { if (msg.equals( state.targetPlayer)) { - db.deleteUser( + // ★ 精准匹配:TEST 与 test 是两个人,只有名字完全相等才删 + if (db.findExactUserName(state.targetPlayer) == null) { + String twin = db.findSimilarUserNameIgnoreCase( + state.targetPlayer); + p.sendMessage("§c账号 " + state.targetPlayer + + " 不存在" + + (twin != null + ? "(仅存在大小写不同的账号 §e" + twin + + "§c,已按精准匹配跳过)" : "") + + ",未执行删除"); + reset(p); + return; + } + int deleted = db.deleteUser( state.targetPlayer); - if (plugin.webManager != null) { + if (plugin.webManager != null && deleted > 0) { plugin.webManager.deleteWebUser(state.targetPlayer); } - p.sendMessage("§a已删除 " - + state.targetPlayer); - Player tgt = - Bukkit.getPlayer( - state.targetPlayer); - if (tgt != null + p.sendMessage(deleted > 0 + ? "§a已删除 " + state.targetPlayer + : "§c删除失败:账号 " + + state.targetPlayer + " 不存在"); + // ★ getPlayerExact:只踢名字完全相同的在线玩家, + // 避免把 TEST 误当成 test 踢下线 + Player tgt = Bukkit.getPlayerExact( + state.targetPlayer); + if (deleted > 0 && tgt != null && tgt.isOnline()) tgt.kickPlayer( "§c账号已被删除"); diff --git a/src/main/java/Sdf1_login/DatabaseManager.java b/src/main/java/Sdf1_login/DatabaseManager.java index 8c7bc17..338d565 100644 --- a/src/main/java/Sdf1_login/DatabaseManager.java +++ b/src/main/java/Sdf1_login/DatabaseManager.java @@ -728,6 +728,55 @@ public class DatabaseManager { } return null; } + + /** + * ★ 修改密码专用校验:同时校验【真实密码】与【临时密码】。 + * + * 与登录校验 (checkPasswordWithFallback) 的区别: + * 临时密码的有效期只对"登录"生效,对"改密"不生效。 + * 原因:玩家用临时密码登录成功后,若忘记真实密码, + * 一旦临时密码 5 分钟过期,就再也没有任何途径改密, + * 账号被永久锁死。改密本身就是"持有旧凭据即可换新凭据", + * 因此这里只要求临时密码【存在且未被使用过】, + * 不再要求它仍在登录有效期内。 + * + * @return "main" = 匹配真实密码 | "temp" = 匹配临时密码 | null = 都不匹配 + */ + public String checkPasswordForChange(String name, String hash) { + // 1. 先校验真实密码 + if (checkPassword(name, hash)) { + return "main"; + } + // 2. 再校验临时密码(不要求未过期,但必须未被消耗) + try { + PreparedStatement ps = db.prepareStatement( + "SELECT temp_password, " + + "temp_pw_used " + + "FROM users " + + "WHERE player_name=?"); + ps.setString(1, name); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + String tempHash = rs.getString("temp_password"); + int used = rs.getInt("temp_pw_used"); + rs.close(); + ps.close(); + if (tempHash != null + && !tempHash.isEmpty() + && hash.equals(tempHash) + && used != 1) { + return "temp"; + } + } else { + rs.close(); + ps.close(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + public Map getUser(String name) { Map r = new LinkedHashMap<>(); @@ -911,17 +960,94 @@ public class DatabaseManager { return list; } - public void deleteUser(String name) { + /** + * ★ 精准大小写匹配:TEST 与 test 是两个独立账号, + * 所有"按名字查/删"的入口都必须区分大小写。 + * @return 完全相等(区分大小写)时返回该玩家名,否则返回 null + */ + public String findExactUserName(String name) { + if (name == null || name.isEmpty()) return null; + try { + PreparedStatement ps = db.prepareStatement( + "SELECT player_name FROM users " + + "WHERE player_name=?"); + ps.setString(1, name); + ResultSet rs = ps.executeQuery(); + String found = null; + while (rs.next()) { + String row = rs.getString(1); + if (name.equals(row)) { // 精准匹配,拒绝大小写兜底 + found = row; + break; + } + } + rs.close(); + ps.close(); + return found; + } catch (SQLException e) { + return null; + } + } + + /** + * 查找"只差大小写"的同名账号(仅用于提示/告警,绝不用于删除)。 + * @return 找到则返回库里的真实玩家名,否则返回 null + */ + public String findSimilarUserNameIgnoreCase(String name) { + if (name == null || name.isEmpty()) return null; + try { + PreparedStatement ps = db.prepareStatement( + "SELECT player_name FROM users " + + "WHERE LOWER(player_name)=LOWER(?)"); + ps.setString(1, name); + ResultSet rs = ps.executeQuery(); + String found = null; + while (rs.next()) { + String row = rs.getString(1); + if (!name.equals(row)) { // 只要大小写不同 + found = row; + break; + } + } + rs.close(); + ps.close(); + return found; + } catch (SQLException e) { + return null; + } + } + + /** + * 精准删除账号(区分大小写)。 + *

+ * TEST 与 test 是两个人:只删 player_name 完全相等的那一条; + * 若只有大小写不同的账号存在,则拒绝删除并记录告警。 + * + * @return 实际删除的行数(0 = 未删除) + */ + public int deleteUser(String name) { + if (name == null || name.isEmpty()) return 0; + if (findExactUserName(name) == null) { + String twin = findSimilarUserNameIgnoreCase(name); + if (twin != null) { + System.out.println("[Sdf1_login] 删除账号拒绝:" + + name + " 不存在,但存在大小写不同的账号 " + + twin + "(已按精准匹配跳过,未误删)"); + } + return 0; + } + int deleted = 0; try { PreparedStatement ps = db.prepareStatement( "DELETE FROM users " + "WHERE player_name=?"); ps.setString(1, name); - ps.executeUpdate(); + deleted = ps.executeUpdate(); ps.close(); } catch (SQLException e) { e.printStackTrace(); } + return deleted; } public void recordIP(String name, String ip) { diff --git a/src/main/java/Sdf1_login/LandRecordManager.java b/src/main/java/Sdf1_login/LandRecordManager.java index 2782a0a..ba51048 100644 --- a/src/main/java/Sdf1_login/LandRecordManager.java +++ b/src/main/java/Sdf1_login/LandRecordManager.java @@ -305,19 +305,71 @@ public class LandRecordManager implements Listener { queryAndPrint(p, loc, 0); } + /** + * ★ 收集查询坐标组:大箱子(double chest)由两个方块组成, + * 记录写入时用"被点击的那一半"坐标,查询时用"准心对准的那一半"坐标, + * 两边坐标不一致就会查不到记录。这里沿同层同类型箱子扩展, + * 把整只大箱子的所有坐标一起查出来再合并。 + */ + private List collectQueryLocations(Location loc) { + List result = new ArrayList<>(); + result.add(loc); + Block start; + try { + start = loc.getBlock(); + } catch (Exception ex) { + return result; // 世界未加载等异常,退回单点查询 + } + Material type = start.getType(); + // 只有能拼成大箱子的类型才需要扩展 + if (type != Material.CHEST && type != Material.TRAPPED_CHEST) { + return result; + } + Set seen = new LinkedHashSet<>(); + seen.add(keyOf(start)); + Deque queue = new ArrayDeque<>(); + queue.add(start); + int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + while (!queue.isEmpty()) { + Block cur = queue.poll(); + for (int[] d : dirs) { + Block n = cur.getRelative(d[0], 0, d[1]); + if (n.getType() != type) continue; + if (seen.add(keyOf(n))) { + result.add(n.getLocation()); + queue.add(n); + } + } + } + return result; + } + + private String keyOf(Block b) { + return b.getX() + "," + b.getY() + "," + b.getZ(); + } + private void queryAndPrint(Player p, Location loc, int page) { String world = loc.getWorld().getName(); int x = loc.getBlockX(), y = loc.getBlockY(), z = loc.getBlockZ(); + // ★ 大箱子:把整只箱子的所有半边坐标都查一遍 + List group = collectQueryLocations(loc); + // 按天分页:page=0今日,1昨日,2前日... - List> blocks = - plugin.getDb().getLandBlockLogAt( - world, x, y, z, QUERY_LIMIT, page); - // 按天分页:page=0今日,1昨日,2前日... - List> containers = - plugin.getDb().getLandContainerLogAt( - world, x, y, z, QUERY_LIMIT, page); + List> rawBlocks = new ArrayList<>(); + List> rawContainers = new ArrayList<>(); + for (Location g : group) { + int gx = g.getBlockX(), gy = g.getBlockY(), gz = g.getBlockZ(); + rawBlocks.addAll(plugin.getDb().getLandBlockLogAt( + world, gx, gy, gz, QUERY_LIMIT, page)); + rawContainers.addAll(plugin.getDb().getLandContainerLogAt( + world, gx, gy, gz, QUERY_LIMIT, page)); + } + sortByTimeDesc(rawBlocks); + sortByTimeDesc(rawContainers); + List> blocks = truncate(rawBlocks); + List> containers = truncate(rawContainers); if (blocks.isEmpty() && containers.isEmpty()) { if (page > 0) { @@ -347,8 +399,17 @@ public class LandRecordManager implements Listener { // 检查下一页是否有数据(决定是否显示"下一页"链接) boolean hasNext = false; - if (!plugin.getDb().getLandBlockLogAt(world, x, y, z, 1, page + 1).isEmpty()) hasNext = true; - if (!plugin.getDb().getLandContainerLogAt(world, x, y, z, 1, page + 1).isEmpty()) hasNext = true; + for (Location g : group) { + int gx = g.getBlockX(), gy = g.getBlockY(), gz = g.getBlockZ(); + if (!plugin.getDb().getLandBlockLogAt(world, gx, gy, gz, 1, page + 1).isEmpty()) { + hasNext = true; + break; + } + if (!plugin.getDb().getLandContainerLogAt(world, gx, gy, gz, 1, page + 1).isEmpty()) { + hasNext = true; + break; + } + } final int currentPage = page; final boolean hasNextPage = hasNext; @@ -361,6 +422,22 @@ public class LandRecordManager implements Listener { }); } + /** 按 time 倒序(新→旧),兼容 Long/Number 两种取值 */ + private void sortByTimeDesc(List> list) { + list.sort((a, b) -> Long.compare(timeOf(b), timeOf(a))); + } + + private long timeOf(Map r) { + Object v = r.get("time"); + return v instanceof Number ? ((Number) v).longValue() : 0L; + } + + /** 截断到 QUERY_LIMIT 条,返回新列表 */ + private List> truncate(List> list) { + if (list.size() <= QUERY_LIMIT) return list; + return new ArrayList<>(list.subList(0, QUERY_LIMIT)); + } + private void printResults(Player p, int x, int y, int z, List> blocks, List> containers, diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index e2995a0..469b0a1 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -102,15 +102,18 @@ public class Main extends JavaPlugin // ★ 自身发起的封禁标记(防止PlayerBanEvent双重广播) private final Set selfInitiatedBans = ConcurrentHashMap.newKeySet(); - private final Set loggedIn = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); + // ★ 登录态按"精确玩家名"记录(大小写敏感): + // 登录必须绑定到当前会话的玩家名。原先用大小写不敏感集合, + // 会导致大小写变体的两个会话共享登录态——一人登录另一人免登录, + // 且一方下线会把另一方"突然冻结"(现象B根因之一)。 + private final Set loggedIn = new HashSet<>(); // ★ 已通过Microsoft OAuth验证的正版玩家(服务器重启前有效) // 格式: playerName -> [mcUuid, mcUsername, verifiedAt] private final ConcurrentHashMap verifiedPremiumPlayers = new ConcurrentHashMap<>(); - private final Set needsPasswordChange = - new TreeSet<>( - String.CASE_INSENSITIVE_ORDER); + // ★ 强制改密标记:同样按精确玩家名记录,避免跨大小写会话串扰 + private final Set needsPasswordChange = new HashSet<>(); + // ★ 冻结态"请先登录"提示节流(玩家UUID -> 上次提示时间) + private final ConcurrentHashMap lastFrozenHint = new ConcurrentHashMap<>(); private final Map joinTime = new ConcurrentHashMap<>(); private final Map joinLoc = @@ -1074,6 +1077,62 @@ public class Main extends JavaPlugin return sb.toString(); } + // ==================== 注册/改密后的"保存账号密码"actionbar 提示 ==================== + /** 玩家当前正在播放的提示任务(重复触发先取消旧的) */ + private final java.util.Map + credentialSaveTasks = new java.util.concurrent.ConcurrentHashMap<>(); + /** 提示持续时长:1 分钟 */ + private static final long CREDENTIAL_SAVE_MS = 60_000L; + + /** + * 注册 / 改密码成功后调用:actionbar 连续显示 60 秒 + * "请尽快保存刚才的账号和密码",1 分钟后自动清除提示。 + * + * @param account 刚注册/刚改密的账号名 + * @param password 刚设置的明文密码(仅本次会话内存中使用) + */ + public void showCredentialSaveReminder(Player p, + String account, + String password) { + if (p == null || !p.isOnline() || account == null) return; + java.util.UUID uid = p.getUniqueId(); + + // 同一玩家重复触发:先掐掉上一个提示任务 + org.bukkit.scheduler.BukkitTask old = + credentialSaveTasks.remove(uid); + if (old != null) old.cancel(); + + final String line = "§e§l[账号安全] §f请尽快保存 §e" + + account + " §7/ §e" + password + + " §7· §860秒后自动消失"; + final long endAt = System.currentTimeMillis() + CREDENTIAL_SAVE_MS; + + org.bukkit.scheduler.BukkitTask task = + getServer().getScheduler().runTaskTimer(this, () -> { + Player pl = Bukkit.getPlayer(uid); + boolean expired = + System.currentTimeMillis() >= endAt; + if (pl == null || !pl.isOnline() || expired) { + if (pl != null && pl.isOnline()) { + // 1 分钟到:清空 actionbar 小标题 + pl.spigot().sendMessage( + net.md_5.bungee.api.ChatMessageType.ACTION_BAR, + net.md_5.bungee.api.chat.TextComponent + .fromLegacyText("")); + } + org.bukkit.scheduler.BukkitTask cur = + credentialSaveTasks.remove(uid); + if (cur != null) cur.cancel(); + return; + } + pl.spigot().sendMessage( + net.md_5.bungee.api.ChatMessageType.ACTION_BAR, + net.md_5.bungee.api.chat.TextComponent + .fromLegacyText(line)); + }, 0L, 20L); + credentialSaveTasks.put(uid, task); + } + private boolean isAdmin(CommandSender sender) { if (sender instanceof Player) return ((Player) sender) @@ -2870,6 +2929,14 @@ public class Main extends JavaPlugin } e.setCancelled(true); p.setAllowFlight(true); + // ★ 提示冻结原因(5秒节流):让未登录玩家立刻知道自己为什么不能动, + // 避免"莫名其妙被冻结"的体感(现象B) + long hintNow = System.currentTimeMillis(); + Long hintLast = lastFrozenHint.get(p.getUniqueId()); + if (hintLast == null || hintNow - hintLast > 5000L) { + lastFrozenHint.put(p.getUniqueId(), hintNow); + p.sendMessage("§c§l[登录] §f你还未登录,无法移动。请使用 §e/l <密码> §f登录(新玩家请先 §e/reg <密码> 注册)"); + } } // PVP竞技场世界检测 @@ -3029,6 +3096,32 @@ public class Main extends JavaPlugin } } + // ★ 未登录(冻结)玩家只允许登录相关命令: + // 1) 堵住"没登录却能跑 /protect 等管理命令"的越权口子; + // 2) 让未登录的管理员立刻收到"请先登录"反馈,而不是等下线/换人才莫名被冻结(现象B) + private static final Set FROZEN_ALLOWED_CMDS = new HashSet<>(java.util.Arrays.asList( + "l", "login", "reg", "sdf1_login", + "注册", "登录", "改密码", "找回密码", + "mslogin", "正版", "email", "绑定", "绑定邮箱", + "删除账号", "通过", "拒绝", "web", "控制台", + "help")); + + @EventHandler(priority = EventPriority.HIGHEST) + public void onFrozenCommandGuard(org.bukkit.event.player.PlayerCommandPreprocessEvent e) { + if (e.isCancelled()) return; + Player p = e.getPlayer(); + if (!isFrozen(p)) return; + String msg = e.getMessage().trim(); + if (msg.isEmpty() || msg.charAt(0) != '/') return; + String body = msg.substring(1).trim().toLowerCase(); + if (body.isEmpty()) return; + String cmd = body.split(" ", 2)[0]; + if (FROZEN_ALLOWED_CMDS.contains(cmd) + || body.startsWith("minecraft:help")) return; + e.setCancelled(true); + p.sendMessage("§c§l[登录] §f你还未登录,无法使用该命令。请先使用 §e/l <密码> §f登录"); + } + // ★ 拦截原版 /ban、/ban-ip 命令,补充广播封禁警告(覆盖永久/临时封禁) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerCommandPreprocess(org.bukkit.event.player.PlayerCommandPreprocessEvent e) { @@ -6477,8 +6570,12 @@ public class Main extends JavaPlugin } String target = args[1]; if (!db.userExists(target)) { + String twin = db.findSimilarUserNameIgnoreCase(target); sender.sendMessage( - "§c玩家 " + target + " 不存在"); + "§c玩家 " + target + " 不存在" + + (twin != null + ? "(仅存在大小写不同的账号 §e" + twin + + "§c,精准匹配下不可删)" : "")); return true; } if (pendingDeleteTask != null) { @@ -6497,13 +6594,23 @@ public class Main extends JavaPlugin pendingDeleteName = null; pendingDeleteTask = null; if (name == null) return; - db.deleteUser(name); - if (webManager != null) { + // ★ 精准匹配:TEST 与 test 是两个人,只删完全相等的那条 + int deleted = db.deleteUser(name); + if (webManager != null && deleted > 0) { webManager.deleteWebUser(name); } + if (deleted <= 0) { + String twin = db.findSimilarUserNameIgnoreCase(name); + getLogger().warning( + "[Sdf1_login] 未删除(精准匹配): " + name + + (twin != null + ? ",仅存在大小写不同的账号 " + twin : ",账号不存在")); + return; + } getLogger().info( "[Sdf1_login] 已删除: " + name); - Player tp = Bukkit.getPlayer(name); + // ★ 只踢名字完全相同的在线玩家,避免误伤大小写不同的账号 + Player tp = Bukkit.getPlayerExact(name); if (tp != null && tp.isOnline()) tp.kickPlayer( "§c账号已被管理员删除"); @@ -6535,12 +6642,21 @@ public class Main extends JavaPlugin String name = pendingDeleteName; pendingDeleteName = null; pendingDeleteTask = null; - db.deleteUser(name); - if (webManager != null) { + int deleted = db.deleteUser(name); + if (webManager != null && deleted > 0) { webManager.deleteWebUser(name); } + if (deleted <= 0) { + String twin = db.findSimilarUserNameIgnoreCase(name); + sender.sendMessage("§c未删除:账号 " + name + " 不存在" + + (twin != null + ? "(仅存在大小写不同的账号 §e" + twin + + "§c,已按精准匹配跳过)" : "")); + return true; + } sender.sendMessage("§a已立即删除: " + name); - Player tp = Bukkit.getPlayer(name); + // ★ 只踢名字完全相同的在线玩家,避免误伤大小写不同的账号 + Player tp = Bukkit.getPlayerExact(name); if (tp != null && tp.isOnline()) tp.kickPlayer("§c账号已被管理员删除"); return true; @@ -6937,7 +7053,8 @@ public class Main extends JavaPlugin String salt = (String) db.getField( p2.getName(), "password_salt"); String hash = PasswordUtils.hash(oldPwd, salt); - String pwdResult = db.checkPasswordWithFallback( + // ★ 同时校验真实密码 + 临时密码(临时密码不再受5分钟有效期限制) + String pwdResult = db.checkPasswordForChange( p2.getName(), hash); if (pwdResult == null) { p2.sendMessage(config.msg("password_wrong")); @@ -6960,9 +7077,15 @@ public class Main extends JavaPlugin Bukkit.getScheduler().runTaskAsynchronously(this, () -> { webManager.pushWebLoginCredentials(); }); - if (isTemp) - db.clearTempPassword(p2.getName()); + // ★ 无论用真实密码还是临时密码改密成功,都作废旧的临时密码 + db.clearTempPassword(p2.getName()); needsPasswordChange.remove(p2.getName()); + p2.sendMessage(config.msg("password_changed")); + // ★ 改密成功:actionbar 提示1分钟保存账号密码 + showCredentialSaveReminder(p2, p2.getName(), newPwd); + if (isTemp) { + p2.sendMessage("§7(本次是用临时密码改的,临时密码已作废)"); + } return true; } @@ -6972,7 +7095,8 @@ public class Main extends JavaPlugin String salt = (String) db.getField( p2.getName(), "password_salt"); String hash = PasswordUtils.hash(oldPwd, salt); - String pwdResult = db.checkPasswordWithFallback( + // ★ 同时校验真实密码 + 临时密码(临时密码不再受5分钟有效期限制) + String pwdResult = db.checkPasswordForChange( p2.getName(), hash); if (pwdResult == null) { p2.sendMessage(config.msg("password_wrong")); diff --git a/src/main/java/Sdf1_login/TicketManager.java b/src/main/java/Sdf1_login/TicketManager.java index 039e8eb..16ea4d7 100644 --- a/src/main/java/Sdf1_login/TicketManager.java +++ b/src/main/java/Sdf1_login/TicketManager.java @@ -1411,7 +1411,7 @@ public class TicketManager { plugin.getDb().updateTicketField(tid, "admin_confirmed", 1); Player target = - Bukkit.getPlayer(requester); + Bukkit.getPlayerExact(requester); if (target != null && target.isOnline()) { plugin.backupInventory(target); target.sendMessage( @@ -1431,10 +1431,17 @@ public class TicketManager { final String name = requester; Bukkit.getScheduler().runTaskLater( plugin, () -> { - plugin.getDb().deleteUser(name); - plugin.getLogger().info( - "[Sdf1_login] " + name - + " 删号工单已执行"); + int deleted = + plugin.getDb().deleteUser(name); + if (deleted > 0) { + plugin.getLogger().info( + "[Sdf1_login] " + name + + " 删号工单已执行"); + } else { + plugin.getLogger().warning( + "[Sdf1_login] " + name + + " 删号工单未执行(精准匹配下账号不存在,未误删)"); + } }, 100L); } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 91e0d71..8d5274a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: Sdf1_login -version: 2.9 +version: 2.91 main: Sdf1_login.Main api-version: '1.21' commands: