From 4297e0e02dc2b62d99e0d9cbbfa42830dd158bab Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Tue, 7 Jul 2026 21:25:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9APVP=E4=B8=96?= =?UTF-8?q?=E7=95=8C=E6=94=B9=E4=B8=BA=E7=BA=AFNORMAL=E4=B8=BB=E4=B8=96?= =?UTF-8?q?=E7=95=8C(=E6=8F=92=E4=BB=B6=E9=9B=B6=E5=B9=B2=E9=A2=84?= =?UTF-8?q?=E5=9C=B0=E5=BD=A2=EF=BC=8C=E4=BB=85=E5=8F=AA=E8=AF=BB=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=E5=87=BA=E7=94=9F=E7=82=B9)=EF=BC=9B=E5=95=86?= =?UTF-8?q?=E5=BA=97=E4=BF=9D=E5=AD=98=E6=94=B9=E4=B8=BA=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E6=84=9F=E7=9F=A5=EF=BC=8C=E9=81=BF=E5=85=8D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E5=85=B3=E9=97=AD=E6=97=B6=E8=A6=86=E7=9B=96=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E6=89=8B=E5=B7=A5=E6=96=B0=E5=A2=9E=E7=9A=84?= =?UTF-8?q?=E5=95=86=E5=93=81/=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Sdf1_login/PVPArenaManager.java | 68 +++++++++++-------- src/main/java/Sdf1_login/ShopManager.java | 45 ++++++++++++ 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/main/java/Sdf1_login/PVPArenaManager.java b/src/main/java/Sdf1_login/PVPArenaManager.java index 00ecbdd..6da4ebd 100644 --- a/src/main/java/Sdf1_login/PVPArenaManager.java +++ b/src/main/java/Sdf1_login/PVPArenaManager.java @@ -246,36 +246,33 @@ public class PVPArenaManager implements Listener { } /** - * 创建全新PVP世界 — 自然主世界地形(完整主世界:山丘/水域/洞穴/生物群系) + * 创建全新PVP世界 — 纯 NORMAL 主世界地形,插件全程不插手地形生成。 * - * ★ 2026-07-07 第四次改进:种子重试机制 - * NORMAL 地形生成器是随机的,(0,0) 可能落在海洋中(findSafeSpawn score=1), - * 导致玩家出生在孤岛/小平台上。现改为: - * - 最多用 3 个不同种子尝试,只要找到一个出生点周围有正常陆地(score≥2)就接受 - * - 全部 3 次都是海洋 → 在最后一个世界上强制改造出生点区域为陆地平台 - */ - /** - * 创建全新PVP世界 — FLAT 世界 + 手写噪声地形(圆形自然竞技岛,无四方形切割) - * - * ★ 2026-07-07 重构:抛弃 NORMAL 随机地形(易出海洋/虚空导致小平台) - * 与 forceTerraformSpawnArea 的 81x81 四方形草地岛(明显的方形切割边界)。 - * 改用 FLAT 世界(基础为实心连续地形,杜绝虚空与区块丢失), - * 再用手写噪声生成器 generatePVPTerrain 生成半径 48 的圆形起伏竞技岛, - * 边缘自然衰减入周围平地,观感自然、加载迅速、永远有合格陆地。 + * ★ 2026-07-07 终版(按用户明确要求): + * 用户要求"完整主世界,插件全程不插手,由MC自己决定自己生成"。 + * 因此抛弃之前所有自定义地形方案: + * - 不用 FLAT 世界; + * - 不调用 generatePVPTerrain 噪声造岛; + * - 不调用 forceTerraformSpawnArea 强制造陆; + * - 不设置种子(让 MC 每次随机决定全新主世界)。 + * 插件只做三件"非地形"的事: + * 1) 设游戏规则(常昼/无天气/PVP 开启); + * 2) 预生成出生点周边区块(确保地形已就绪); + * 3) 只读方式调用 findSafeSpawn 选附近优质固体地面做出生点 + * (不改任何方块;仅当极端异常(理论上 NORMAL 必有陆地)才造保底平台)。 */ private void createPVPWorld() { - long seed = worldSeedRandom.nextLong(); - plugin.getLogger().info("[PVP] 创建PVP世界 (FLAT+噪声地形 seed=" + seed + ")"); + plugin.getLogger().info("[PVP] 创建PVP世界 (纯NORMAL主世界,地形由MC自行生成)"); // 清理上次残留世界,确保是全新世界 File existing = new File(Bukkit.getWorldContainer(), PVP_WORLD_NAME); if (existing.exists()) moveWorldToTrash(existing); - // FLAT 世界:基础为实心连续地形,彻底消除虚空/区块丢失 + // ★ 纯 NORMAL 主世界:完全交给 Minecraft 自己生成(山丘/水域/洞穴/生物群系)。 + // 不调用 creator.seed(...) —— 由 MC 自行随机决定地形种子,插件对地形零干预。 WorldCreator creator = new WorldCreator(PVP_WORLD_NAME); creator.environment(World.Environment.NORMAL); - creator.type(WorldType.FLAT); - creator.seed(seed); + creator.type(WorldType.NORMAL); World pvpWorld = creator.createWorld(); if (pvpWorld == null) { @@ -289,16 +286,29 @@ public class PVPArenaManager implements Listener { pvpWorld.setTime(6000); pvpWorld.setKeepSpawnInMemory(true); - // ★ 手写噪声地形:在原点周边生成圆形竞技岛(半径 48 格) - generatePVPTerrain(pvpWorld, seed); - // 立即落盘,确保手写地形持久化、不被后续自动保存覆盖 - try { pvpWorld.save(); } catch (Exception ignored) {} + // 先把出生点锚定在原点,使后续预生成与出生点搜索都以原点为中心 + pvpWorld.setSpawnLocation(0, 64, 0); - // 出生点固定在世界原点上方地形顶部 - int groundY = getTopSolidY(pvpWorld, 0, 0); - pvpWorld.setSpawnLocation(0, groundY + 1, 0); - plugin.getLogger().info("[PVP] ✅ 已创建PVP竞技场世界(噪声地形 seed=" + seed - + ") 出生点=(0," + (groundY + 1) + ",0)"); + // 预生成出生点周边区块(半径 8 个区块),确保地形已就绪、findSafeSpawn 不误判虚空 + preGenerateSpawnChunks(pvpWorld, 8); + + // ★ 只读定位安全出生点(绝不改造地形) + Location safe = findSafeSpawn(pvpWorld); + if (safe != null) { + pvpWorld.setSpawnLocation( + safe.getBlockX(), safe.getBlockY(), safe.getBlockZ()); + plugin.getLogger().info("[PVP] ✅ 已创建PVP竞技场世界(NORMAL主世界) 出生点=(" + + safe.getBlockX() + "," + safe.getBlockY() + "," + safe.getBlockZ() + + ") 陆地质量分=" + lastSpawnLandScore); + } else { + // ★ 极端兜底:理论上 NORMAL 主世界必有陆地,仅当异常时才造保底平台 + plugin.getLogger().warning("[PVP] 未找到安全陆地,启用保底出生平台"); + buildFallbackPlatform(pvpWorld); + pvpWorld.setSpawnLocation(0, 121, 0); + } + + // 落盘已生成的区块 + try { pvpWorld.save(); } catch (Exception ignored) {} } /** diff --git a/src/main/java/Sdf1_login/ShopManager.java b/src/main/java/Sdf1_login/ShopManager.java index 02b8a25..24bb89b 100644 --- a/src/main/java/Sdf1_login/ShopManager.java +++ b/src/main/java/Sdf1_login/ShopManager.java @@ -1175,11 +1175,54 @@ public class ShopManager implements Listener { // ===== 保存为 md ===== + /** + * 保存单个分类到 .md 文件(合并感知,杜绝覆盖管理员手工新增内容)。 + * + * ★ 2026-07-07 修复(严重缺陷):原实现用 PrintWriter 对 .md 做整文件覆盖重写, + * 仅写出内存 categories 列表中的商品。若管理员在服务器运行期间于磁盘上手工新增了 + * 商品(或修改了某个在内存中未被加载的分类文件),则整文件重写会把这些磁盘新增内容 + * 抹掉;服务器 stop 时 onDisable→saveAll 全量重写会进一步把新增分类/商品“回滚”, + * 表现为“数据不同步 / 新商品被自动删除”。 + * + * 新方案:写文件前先读取磁盘上已有的同文件,把“磁盘中存在但内存中不存在”的商品行原样 + * 保留(追加在内存商品之后),从而减少整文件覆盖带来的内容丢失。 + * 说明:整体新增的“独立分类 .md 文件”本来就不在内存 categories 中,saveAll 不会触碰它, + * 因此是天然安全的;本修复主要堵住“在已有分类里手工加商品被覆盖”的漏洞。 + */ public void saveCategory(ShopCategory cat) { File dir = new File( plugin.getDataFolder(), "shop"); if (!dir.exists()) dir.mkdirs(); File f = new File(dir, cat.getFileName()); + + // ★ 合并感知:收集磁盘上“内存中不存在”的商品行(保留管理员手工新增) + List diskOnlyRows = new ArrayList<>(); + if (f.exists()) { + Set memIds = new HashSet<>(); + for (ShopItem it : cat.getItems()) memIds.add(it.getId()); + try (BufferedReader br = new BufferedReader( + new InputStreamReader( + new FileInputStream(f), + StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + if (!line.startsWith("|")) continue; // 跳过标题(#)行 + // 数据行形如: | ID | 品名 | 材质 | ... + String[] cols = line.split("\\|"); + if (cols.length < 3) continue; + String id = cols[1].trim(); + // 跳过表头行(ID)与分隔行(---) + if (id.isEmpty() || id.equals("ID") || id.equals("---")) + continue; + if (!memIds.contains(id)) { + diskOnlyRows.add(line); // 磁盘独有商品,原样保留 + } + } + } catch (Exception ignored) { + // 读取失败则放弃合并(退化为仅写内存内容,与旧行为一致) + } + } + try { PrintWriter pw = new PrintWriter( new OutputStreamWriter( @@ -1206,6 +1249,8 @@ public class ShopManager implements Listener { + " | " + item.getTotalSales() + " |"); } + // ★ 追加磁盘独有商品行(管理员手工新增,避免被覆盖删除) + for (String row : diskOnlyRows) pw.println(row); pw.flush(); pw.close(); } catch (Exception e) {