From b29284bb1b42fb5d7879b3601b57289dd920b9ab Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Fri, 10 Jul 2026 10:58:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20Java=E7=AB=AF=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=AE=BF=E5=AE=A2=E6=9D=83=E9=99=90=E8=AF=AF=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E7=AE=A1=E7=90=86=E5=91=98+PVP/=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=9C=BA=E8=A3=85=E5=A4=87=E9=9A=94=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AreaProtection.updateVisitorPermFromWeb: 拦截PHP发来member角色时降级admin - Web/api/sync.php: syncPermissions ON CONFLICT替代INSERT OR REPLACE防角色降级 - Web/api/land_api.php: handleSyncPermissions同上 - PVPTestManager: respawn立即恢复背包, join时空降检测恢复 - PVPArenaManager: 入场立即备份清空背包, respawn时恢复+送出PVP世界 --- src/main/java/Sdf1_login/AreaProtection.java | 8 +- src/main/java/Sdf1_login/PVPArenaManager.java | 109 ++++++++++++------ src/main/java/Sdf1_login/PVPTestManager.java | 48 +++++++- 3 files changed, 128 insertions(+), 37 deletions(-) diff --git a/src/main/java/Sdf1_login/AreaProtection.java b/src/main/java/Sdf1_login/AreaProtection.java index e6ef889..d6c6185 100644 --- a/src/main/java/Sdf1_login/AreaProtection.java +++ b/src/main/java/Sdf1_login/AreaProtection.java @@ -8216,10 +8216,16 @@ public class AreaProtection implements Listener { String effectiveRole = "visitor"; if (role != null && !role.isEmpty()) { effectiveRole = role.toLowerCase(); + boolean isAdmin = isLandAdmin(lname, playerName); if ("admin".equalsIgnoreCase(role)) { setLandAdmin(lname, playerName, true); - } else if ("visitor".equalsIgnoreCase(role) && isLandAdmin(lname, playerName)) { + } else if ("visitor".equalsIgnoreCase(role) && isAdmin) { setLandAdmin(lname, playerName, false); + } else if ("member".equalsIgnoreCase(role) && isAdmin) { + // ★ PHP 发来 member 角色但玩家是管理员 — 不能降级,保留 admin + plugin.getLogger().warning("[防护][更新权限] 拦截角色降级: " + playerName + + " @ " + lname + " 尝试从 admin 降为 member,已保留 admin"); + effectiveRole = "admin"; } } else { // 未带 role 时,沿用本地已有角色;若本地无记录则默认 visitor diff --git a/src/main/java/Sdf1_login/PVPArenaManager.java b/src/main/java/Sdf1_login/PVPArenaManager.java index 074abd8..253d818 100644 --- a/src/main/java/Sdf1_login/PVPArenaManager.java +++ b/src/main/java/Sdf1_login/PVPArenaManager.java @@ -11,13 +11,13 @@ import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; import org.bukkit.scoreboard.*; +import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.io.BukkitObjectInputStream; import org.bukkit.util.io.BukkitObjectOutputStream; @@ -901,6 +901,9 @@ public class PVPArenaManager implements Listener { plugin.getLogger().info("[PVP] 玩家 " + player.getName() + " 未选择任何装备就关闭了PVP装备GUI,执行遣返"); + // 还原原背包(进入时已清空) + restoreInventory(player); + // 清理状态并取消超时定时器 inPVPArena.remove(player.getName()); cancelKickTimeout(player.getName()); @@ -944,21 +947,25 @@ public class PVPArenaManager implements Listener { registerPlayerStats(player); equipmentConfirmed.remove(playerName); - // ★ 关键变更:先选装备,不急着备份清空 + // ★ 进入竞技场立即备份并清空原背包,防止玩家用自带装备战斗 + backupInventory(player); + clearInventory(player); + + // 打开装备选择GUI openEquipmentSelection(player); // ★ 注册超时安全网:60秒内必须确认,否则自动遣返 scheduleKickTimeout(playerName); player.sendMessage("§a§l欢迎来到PVP竞技场!"); - player.sendMessage("§7请先选择你的装备套装,确认后将备份你的原背包"); + player.sendMessage("§7请先选择你的装备套装,确认后将发放PVP装备"); player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.0f); - plugin.getLogger().info("[PVP] 玩家 " + playerName + " 进入PVP竞技场(等待选装备)"); + plugin.getLogger().info("[PVP] 玩家 " + playerName + " 进入PVP竞技场(已备份清空原背包,等待选装备)"); } /** - * 执行"确认装备"步骤:备份原背包 → 清空 → 发放PVP装备 + * 执行"确认装备"步骤:发放PVP装备 * 由装备GUI的确认按钮和一键装备按钮触发 */ public void confirmEquipment(Player player) { @@ -966,13 +973,7 @@ public class PVPArenaManager implements Listener { if (!inPVPArena.contains(playerName)) return; if (equipmentConfirmed.contains(playerName)) return; - // ★ 第一步:备份原背包(在清空之前!) - backupInventory(player); - - // ★ 第二步:清空背包 - clearInventory(player); - - // ★ 第三步:发放PVP装备 + // 此时背包已在进入时清空,直接发放PVP装备 equipFullSet(player); // 标记已完成装备确认 + 取消超时定时器 @@ -1007,6 +1008,10 @@ public class PVPArenaManager implements Listener { plugin.getLogger().info("[PVP] 超时遣返: " + playerName + " 进入PVP超过 " + EQUIPMENT_SELECT_TIMEOUT_SECONDS + "秒未确认装备"); kickTimeoutTasks.remove(playerName); + // 超时未确认:还原原背包并送走 + if (p != null && p.isOnline()) { + restoreInventory(p); + } inPVPArena.remove(playerName); cleanupPlayerStats(p); guiReopening.remove(playerName); @@ -1185,20 +1190,42 @@ public class PVPArenaManager implements Listener { // 死亡时保留经验(避免丢失) event.setKeepLevel(true); event.setKeepInventory(true); // 不掉落PVP装备(因为要回收) + // 复活处理统一交给 onPlayerRespawn,避免1秒空窗期被利用 + } - // 延迟1秒后强制离开(等死亡事件处理完) - new BukkitRunnable() { - @Override - public void run() { - if (player.isOnline()) { - forceLeaveArena(player, false); - // 传回主世界 - World main = Bukkit.getWorlds().get(0); - player.teleport(main.getSpawnLocation()); - player.sendMessage("§c§l你在PVP中阵亡,已自动退出并还原背包"); - } - } - }.runTaskLater(plugin, 20L); // 1秒后 + /** + * 玩家复活事件 — 在PVP竞技场阵亡后立即还原背包并送回主世界 + */ + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + if (!inPVPArena.contains(player.getName())) return; + + World main = Bukkit.getWorlds().get(0); + event.setRespawnLocation(main.getSpawnLocation()); + + if (!equipmentConfirmed.contains(player.getName())) { + // 未确认装备:身上仍是原背包,只需清状态 + cleanupPlayerStats(player); + inPVPArena.remove(player.getName()); + equipmentConfirmed.remove(player.getName()); + guiReopening.remove(player.getName()); + guiOpenedMillis.remove(player.getName()); + graceReopenCount.remove(player.getName()); + selEnchant.remove(player.getName()); + selShield.remove(player.getName()); + selFood.remove(player.getName()); + selWeaponTier.remove(player.getName()); + selArmorTier.remove(player.getName()); + equipInteracted.remove(player.getName()); + cancelKickTimeout(player.getName()); + player.sendMessage("§c§l你在PVP中阵亡,已自动退出"); + return; + } + + // 已确认装备:回收PVP装备并还原原背包 + forceLeaveArena(player, false); + player.sendMessage("§c§l你在PVP中阵亡,已自动退出并还原背包"); } // ===== 全服击杀播报(移植自 PVPManager) ===== @@ -1237,7 +1264,7 @@ public class PVPArenaManager implements Listener { } /** - * 玩家加入事件 — 如果上次在PVP中断线,还原背包 + * 玩家加入事件 — 如果上次在PVP中断线,还原背包并送回主世界 */ @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { @@ -1250,6 +1277,7 @@ public class PVPArenaManager implements Listener { + " 上次在PVP中断线,正在还原背包..."); // 延迟2 tick 让玩家完全加载 plugin.getServer().getScheduler().runTaskLater(plugin, () -> { + if (!player.isOnline()) return; InventoryBackup backup = new InventoryBackup( deserializeItems(data[0]), deserializeItems(data[1]), @@ -1261,15 +1289,29 @@ public class PVPArenaManager implements Listener { restoreInventory(player); db.deletePvpInventoryBackup(player.getName()); player.sendMessage("§a§l检测到你上次在PVP中断线,背包已自动恢复"); + + // 若仍在竞技场世界,送回主世界 + if (player.getWorld().getName().equals(pvpWorldName)) { + World main = Bukkit.getWorlds().get(0); + player.teleport(main.getSpawnLocation()); + cleanupPlayerStats(player); + inPVPArena.remove(player.getName()); + player.sendMessage("§e[PVP] 已送你回主世界"); + } }, 2L); + return; } // 防止玩家登录时就在PVP世界(异常情况兜底) if (player.getWorld().getName().equals(pvpWorldName)) { - inPVPArena.add(player.getName()); - registerPlayerStats(player); - // 给个提示但不自动操作,让玩家自己决定 - player.sendMessage("§e[PVP] 你当前在PVP竞技场世界,输入 /pvp leave 离开"); + plugin.getServer().getScheduler().runTaskLater(plugin, () -> { + if (!player.isOnline()) return; + World main = Bukkit.getWorlds().get(0); + player.teleport(main.getSpawnLocation()); + cleanupPlayerStats(player); + inPVPArena.remove(player.getName()); + player.sendMessage("§e[PVP] 检测到你在竞技场非正常断线,已送你回主世界"); + }, 5L); } } @@ -1283,10 +1325,11 @@ public class PVPArenaManager implements Listener { // 断线前最后备份一次当前状态 if (equipmentConfirmed.contains(player.getName())) { // 已装备确认过 → 当前身上是PVP装备,需要用之前的备份来还原 - // 备份已经在confirmEquipment时做过,这里只需标记 + // 备份已经在进入时做过,这里只需标记 forceLeaveArena(player, true); // isDisconnect=true } else { - // 还没确认装备 → 身上还是原背包,不需要特殊处理 + // 还没确认装备 → 进入时已清空,必须还原原背包 + restoreInventory(player); inPVPArena.remove(player.getName()); equipmentConfirmed.remove(player.getName()); guiReopening.remove(player.getName()); diff --git a/src/main/java/Sdf1_login/PVPTestManager.java b/src/main/java/Sdf1_login/PVPTestManager.java index 8ee92dc..72b2cb9 100644 --- a/src/main/java/Sdf1_login/PVPTestManager.java +++ b/src/main/java/Sdf1_login/PVPTestManager.java @@ -10,6 +10,7 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.event.entity.EntityTargetLivingEntityEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.inventory.Inventory; @@ -543,6 +544,22 @@ public class PVPTestManager implements Listener { return; } + // 已经在测试场则不允许重复进入(防止备份被覆盖) + if (inTest.contains(p.getUniqueId())) { + p.sendMessage("§e§l[PVP测试] 你已经在测试场中,无需重复进入"); + return; + } + + // 兜底:死亡/断线后未正常恢复则先恢复,防止把场外/场内残留装备再带进去 + if (inventoryBackups.containsKey(p.getUniqueId())) { + p.sendMessage("§e§l[PVP测试] 检测到未恢复背包,正在恢复..."); + restoreInventory(p); + removePlayerMobs(p); + chosenDiff.remove(p.getUniqueId()); + chosenEquip.remove(p.getUniqueId()); + pendingRespawn.remove(p.getUniqueId()); + } + boolean enchant = testEnchant.getOrDefault(p.getUniqueId(), false); boolean shield = testShield.getOrDefault(p.getUniqueId(), true); int wt = testWeaponTier.getOrDefault(p.getUniqueId(), 0); @@ -987,9 +1004,8 @@ public class PVPTestManager implements Listener { pendingRespawn.remove(p.getUniqueId()); World main = Bukkit.getWorlds().get(0); e.setRespawnLocation(main.getSpawnLocation()); - // 回到主世界后再还原背包,避免死亡瞬间覆盖 - final Player fp = p; - Bukkit.getScheduler().runTaskLater(plugin, () -> restoreInventory(fp), 1L); + // 复活瞬间立即还原原背包,杜绝利用快速重进保留/携带测试场装备 + restoreInventory(p); checkAllLeft(); } @@ -1007,6 +1023,32 @@ public class PVPTestManager implements Listener { checkAllLeft(); } + /** 登录:若上次在测试场断线(或非正常离开),强制还原背包并送回主世界 */ + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + Player p = e.getPlayer(); + // 情况1:测试场世界里有记录或身上有测试场装备残留 + boolean inTestWorld = isTestWorld(p.getWorld()); + boolean hasBackup = inventoryBackups.containsKey(p.getUniqueId()); + if (!inTestWorld && !hasBackup && !inTest.contains(p.getUniqueId())) return; + + if (hasBackup) { + restoreInventory(p); + } + removePlayerMobs(p); + inTest.remove(p.getUniqueId()); + chosenDiff.remove(p.getUniqueId()); + chosenEquip.remove(p.getUniqueId()); + pendingRespawn.remove(p.getUniqueId()); + + if (inTestWorld) { + World main = Bukkit.getWorlds().get(0); + p.teleport(main.getSpawnLocation()); + p.sendMessage("§c§l[PVP测试] 检测到你在测试场非正常断线,已送你回主世界并恢复背包"); + } + checkAllLeft(); + } + /** 通过其他方式离开测试世界(如指令传送)→ 视为离开 */ @EventHandler public void onChangedWorld(PlayerChangedWorldEvent e) {