修复: Java端调整访客权限误降级管理员+PVP/测试场装备隔离

- 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世界
This commit is contained in:
2026-07-10 10:58:55 +08:00
parent 97d96d747d
commit b29284bb1b
3 changed files with 128 additions and 37 deletions
+7 -1
View File
@@ -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
+76 -33
View File
@@ -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());
+45 -3
View File
@@ -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) {