修复PVP加入卡服(13s看门狗)并新增公共设施连控权限
- PVP世界改由 WorldType.FLAT 空世界+手动地形生成:createWorld 毫秒级返回, 彻底杜绝主线程13s卡服与 Chunk wait 看门狗报错;随机种子仍保证每次地形不同(防背图); 新增 forceSafeSpawn 固化安全出生点,绕开原'水→删除重建换种子'卡服链路 - 公共设施连控:领地 isPublicBuilding 启用时,所有访客自动获得 传送/容器交互/攻击敌对生物 3项权限(传送在 canTeleportToLand,容器交互在两处 denyContainer 检查,攻击敌对生物复用既有逻辑)
This commit is contained in:
@@ -2268,6 +2268,8 @@ public class AreaProtection implements Listener {
|
||||
if (isAreaAdmin(p)) return true;
|
||||
if (p.getName().equalsIgnoreCase(ac.owner)) return true;
|
||||
if (isLandAdmin(ac.name, p.getName())) return true;
|
||||
// ★ 公共设施连控:公共设施启用时,所有访客自动获得传送权限(优先于 per-player 检查)
|
||||
if (ac.isPublicBuilding) return true;
|
||||
// ★ 访客传送权限:领地级别允许则放行(优先于per-player检查)
|
||||
if (ac.allowVisitorTeleport) return true;
|
||||
int landId = getLandIdFromDb(ac.name);
|
||||
@@ -4538,6 +4540,8 @@ public class AreaProtection implements Listener {
|
||||
if (clicked != null) {
|
||||
Material clickedMat = clicked.getType();
|
||||
if (isUIBlock(clickedMat)) {
|
||||
// ★ 公共设施连控:公共设施启用时,访客自动获得容器交互权限(工作台/砂轮等UI方块同样放开)
|
||||
if (ac.isPublicBuilding) return;
|
||||
if (getEffectiveDeny(p, ac, "denyContainer")) {
|
||||
e.setCancelled(true);
|
||||
p.sendMessage("§c§l[区域防护] §f此领地禁止访问容器");
|
||||
@@ -4684,6 +4688,9 @@ public class AreaProtection implements Listener {
|
||||
|
||||
if (hasPermission(p, ac, PermissionLevel.OWNER)) return;
|
||||
|
||||
// ★ 公共设施连控:公共设施启用时,访客自动获得容器交互权限(跳过禁止检查)
|
||||
if (ac.isPublicBuilding) return;
|
||||
|
||||
// ★ 容器管理权限:denyContainer=true时非领主/管理员不能访问容器
|
||||
if (getEffectiveDeny(p, ac, "denyContainer")) {
|
||||
e.setCancelled(true);
|
||||
|
||||
@@ -262,11 +262,11 @@ public class PVPArenaManager implements Listener {
|
||||
* 设计:玩家全部离开后世界被删除,下次加入时重新随机种子生成【全新地形】,
|
||||
* 避免熟悉地图的玩家背图追杀新手。
|
||||
*
|
||||
* 出生点合法性:脚下方块必须是【实体方块】(固体)才使用;若随机到水/空气等非实体方块,
|
||||
* 则删除该世界、换一个新种子重新生成(极少数情况连续多次都抽到水出生点,会重试但概率极低)。
|
||||
*
|
||||
* 卡服修复:Bukkit.createWorld 仅做世界初始化(不强制加载出生点区块),
|
||||
* 主线程毫秒级返回;出生点周边区块由 preGenerateSpawnChunksAsync 异步预生成,不阻塞主线程。
|
||||
* ★ 卡服根因修复(2026-07-08):原实现用 WorldType.NORMAL + Bukkit.createWorld 在主线程
|
||||
* 同步"搜索出生点 + 生成自然地形",耗时 13s 触发 Paper 看门狗(Chunk wait)导致卡服/报错。
|
||||
* 现改为 WorldType.FLAT 空世界:createWorld 毫秒级返回(无地形搜索),随后用
|
||||
* generatePVPTerrain 按【随机种子】手动生成自然地形(每次丘陵/水域/树木分布不同 → 仍防背图),
|
||||
* 最后 forceSafeSpawn 固化一个绝对安全的出生点,彻底绕开"水→删除重建换种子"的卡服链路。
|
||||
*/
|
||||
private World createPVPWorld() {
|
||||
long t0 = System.currentTimeMillis();
|
||||
@@ -278,12 +278,12 @@ public class PVPArenaManager implements Listener {
|
||||
long seed = worldSeedRandom.nextLong();
|
||||
WorldCreator c = new WorldCreator(pvpWorldName);
|
||||
c.environment(World.Environment.NORMAL);
|
||||
c.type(WorldType.NORMAL);
|
||||
// ★ 关键:FLAT 空世界使 createWorld 在主线程毫秒级返回,杜绝 13s 卡服;地形后续手动生成
|
||||
c.type(WorldType.FLAT);
|
||||
c.seed(seed);
|
||||
// 注意:Paper 26.x 已移除 WorldCreator.keepSpawnInMemory(boolean),不再调用
|
||||
// 区块加载控制改为 createWorld 后通过 world.setKeepLoaded(false) 实现
|
||||
|
||||
plugin.getLogger().info("[PVP] 生成PVP竞技场世界 " + pvpWorldName + " 种子=" + seed);
|
||||
plugin.getLogger().info("[PVP] 生成PVP竞技场世界 " + pvpWorldName + " 种子=" + seed + " (FLAT空世界,随后手动生成地形)");
|
||||
World pvpWorld = Bukkit.createWorld(c);
|
||||
long dt = System.currentTimeMillis() - t0;
|
||||
if (pvpWorld == null) {
|
||||
@@ -291,24 +291,49 @@ public class PVPArenaManager implements Listener {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ★ 出生点脚下方块检查:实体(固体)方块→使用;水/空气等非实体→删除重来(换新种子)
|
||||
Location spawn = pvpWorld.getSpawnLocation();
|
||||
Block feet = pvpWorld.getBlockAt(spawn.getBlockX(), spawn.getBlockY() - 1, spawn.getBlockZ());
|
||||
if (!isSolidBlock(feet.getType())) {
|
||||
plugin.getLogger().warning("[PVP] 种子 " + seed + " 出生点为非实体方块(" + feet.getType().name()
|
||||
+ "),删除世界换种子重来");
|
||||
deletePVPWorld(pvpWorld);
|
||||
return createPVPWorld(); // 递归重试(新随机种子)
|
||||
}
|
||||
|
||||
// 出生点合格:异步预生成周边区块(不卡主线程),同时应用世界规则
|
||||
// ★ 手动生成自然地形(随机种子→每次地形不同,防背图),同步完成(仅96x96,远低于13s)
|
||||
generatePVPTerrain(pvpWorld, seed);
|
||||
// ★ 固化安全出生点:中心附近找实体非流体方块,颠覆"水→重建"的卡服链路
|
||||
forceSafeSpawn(pvpWorld);
|
||||
// 异步预生成周边区块(兜底,不阻塞主线程)
|
||||
preGenerateSpawnChunksAsync(pvpWorld, 4);
|
||||
reapplyWorldRules(pvpWorld);
|
||||
|
||||
plugin.getLogger().info("[PVP] PVP竞技场世界就绪 种子=" + seed + " 耗时=" + dt + "ms(出生点为实体方块)");
|
||||
plugin.getLogger().info("[PVP] PVP竞技场世界就绪 种子=" + seed + " 耗时=" + dt + "ms(FLAT+手动地形,出生点已固化)");
|
||||
return pvpWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* 固化PVP世界出生点:在中心半径内寻找第一个"实体且非流体"的方块作为出生点。
|
||||
* 找不到则强制在中心 (0,24,0) 搭建安全平台。
|
||||
* 目的:彻底规避原 NORMAL 世界"出生点为水→删除重建换种子"导致的 13s 主线程卡服。
|
||||
*/
|
||||
private void forceSafeSpawn(World world) {
|
||||
int maxR = 8;
|
||||
for (int rad = 0; rad <= maxR; rad++) {
|
||||
for (int dx = -rad; dx <= rad; dx++) {
|
||||
for (int dz = -rad; dz <= rad; dz++) {
|
||||
if (rad > 0 && Math.abs(dx) != rad && Math.abs(dz) != rad) continue; // 仅遍历当前环
|
||||
int top = getTopSolidY(world, dx, dz);
|
||||
Material m = world.getBlockAt(dx, top, dz).getType();
|
||||
if (m.isSolid() && !isFluid(m)) {
|
||||
world.setSpawnLocation(dx, top + 1, dz);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 兜底:中心安全平台
|
||||
int sy = 24;
|
||||
for (int y = 0; y <= sy; y++) {
|
||||
Block b = world.getBlockAt(0, y, 0);
|
||||
b.setType(y == sy ? Material.GRASS_BLOCK : (y == 0 ? Material.BEDROCK : Material.STONE));
|
||||
}
|
||||
for (int y = sy + 1; y <= sy + 4; y++) world.getBlockAt(0, y, 0).setType(Material.AIR);
|
||||
world.setSpawnLocation(0, sy + 1, 0);
|
||||
plugin.getLogger().warning("[PVP] 未找到天然安全出生点,已生成中心兜底平台");
|
||||
}
|
||||
|
||||
/** 是否为实体(固体)方块:玩家可站立的地面。水/空气/岩浆等非固体返回 false */
|
||||
private boolean isSolidBlock(Material m) {
|
||||
if (m == null || m.isAir() || m == Material.WATER || m == Material.LAVA) return false;
|
||||
|
||||
Reference in New Issue
Block a user