[1.21-26.2小游戏]陈·村庄保卫战 - PraxVillageDefense

原创 付费 闭源 娱乐 [1.21-26.2小游戏]陈·村庄保卫战 - PraxVillageDefense 2.4.0fix1.0

请登录后获取
版权类型
原创
插件中文名称
陈·村庄保卫战
插件英文名称
PraxVillageDefense
原帖地址
https://gitee.com/RootpiXiaocheng
支持的核心(服务端)
  1. Spigot
  2. Paper
  3. Folia
  4. Purpur
语言支持
中文(简体)
前置组件
Vault:https://modrinth.com/plugin/vault-2.0-economy-plugins(推荐)
MultiVerse-Core:https://modrinth.com/plugin/multiverse-core
DecentHolograms:https://modrinth.com/plugin/decentholograms
(非必需)Mythicmobs:https://modrinth.com/plugin/mythicmobs
(非必需)QualityArmory:https://modrinth.com/plugin/qualityarmory
适配版本(Java)
  1. 最新版本
  2. 26.x
  3. 1.21
[MD] # PraxVillageDefense - 陈·村庄保卫战 ## 目录 1. [功能总览](#1-功能总览) 2. [服务端兼容性](#2-服务端兼容性) 3. [软依赖](#3-软依赖) 4. [命令列表](#4-命令列表) 5. [管理员 GUI 指南](#5-管理员-gui-指南) 6. [选定套装系统](#6-选定套装系统) 7. [自定义套装系统](#7-自定义套装系统) 8. [API 参考](#8-api-参考) 9. [ChestCommands 集成](#9-chestcommands-集成) 10. [配置文件参考](#10-配置文件参考) 11. [构建指南](#11-构建指南) 12. [开发者扩展指南](#12-开发者扩展指南) --- ## 1. 功能总览 Villager Defense 是一款多模块 Minecraft Spigot/Paper 插件,支持版本 1.21.x 与 26.x 全版本(Minecraft 1.21.1 ~ 26.1.x)。 ### 核心系统 | 系统 | 说明 | |------|------| | 竞技场管理 | 创建/编辑/管理多个独立竞技场 | | 套装系统 | 24 个预定义套装(技能/赠礼/效果)+ 自定义套装 | | 怪物系统 | 22 种怪物类型 + MythicMobs 联动 | | 商店系统 | 默认/自定义/附魔/消耗品商店 | | 挑战系统 | 局内挑战选项 | | 成就系统 | 基于统计的成就追踪 | | 难度系统 | 动态难度调整 | | 水晶货币 | 全局水晶(`crystalBalance`),用于购买自定义套装等 | | 玩家数据 | 玩家统计、平衡、套装等级、签到状态持久化 | | **选定套装** | **玩家可预设默认套装,加入时自动装备** | | **自定义套装** | **管理员可通过 GUI 创建自定义套装(水晶购买),支持 KitType 分类、标签、权限、槽位属性与装备部位配置** | | **每日签到** | **`/vd sign` 每日领取水晶奖励(config 可配)** | | **MythicMobs** | **与 MythicMobs 自定义怪物联动** | | **怪物 AI** | **可配置的怪物行为系统** | | **礼包码** | **`/kit <礼包码>` 兑换钻石/套装;支持每日礼包、限时礼包、全服公告** | | **积分商店** | **击杀怪物获得积分,可在 `/vd shop` 兑换钻石/套装/物品** | | **怪物词缀** | **精英怪随机携带词缀(急速/护盾/爆炸/传送/吸血/燃烧)** | | **观战模式** | **死亡/淘汰后留在场内以旁观者模式观战(竞技场可配)** | | **成就解锁套装** | **达成指定成就自动解锁自定义套装(config 可配)** | | **防护塔系统** | **6 种防御塔(箭/闪电/爆炸/火焰/冰霜/治疗),手持绿宝石右键方块直接建造,塔实体可视化(盔甲架+图标+名称)** | | **塔链协同** | **相邻同类型塔互相叠加加成;减速怪被火焰塔命中触发熔毁联动(伤害提升)** | | **塔升级** | **塔主右键塔消耗绿宝石升级,等级线性提升伤害/射程并缩短冷却,塔名显示 Lv.N** | | **村民职业体系** | **村民随机获得职业,存活期间为周围玩家提供职业光环** | | **好奇村民** | **按概率生成的村民带【好奇】标签,主动跟随附近玩家;可右键驱离(有冷却与代价),驱离致死重罚民心** | | **肉鸽模式** | **每 N 回合玩家投票选择增益+敌方增强组合(3 选 1,卡池 18 张),多数胜出/平票随机,5 秒投票窗口** | | **波次惩罚** | **村民死亡叠加村庄绝望层数,后续怪物属性等比提升** | | **村庄民心** | **完美防守提升民心、村民死亡降低民心,民心越高波次/胜利奖励倍率越高(正向机制)** | | **幸存者奖励** | **完美防守 / 胜利幸存村民的绿宝石奖励** | | **怪物适应性进化** | **持续用同种伤害击杀后,新怪物获得该伤害抗性,逼迫混合塔型/武器** | | **悬赏精英波** | **每隔 N 波出现悬赏目标(高血+★标记),击杀后全队获得绿宝石大奖与增益** | | **回收站** | **原公共箱子改为回收站:放入物品供其他玩家取用,每波结束自动清空** | | **村民内鬼** | **每只村民小概率为隐藏内鬼:持续给附近玩家施加减益,可被攻击,击杀提升民心+全队增益** | | **村庄节日** | **随机波次触发节日波:全场奖励加成 + 村民烟花粒子** | | **怪物进化线** | **塔型级进化:同一塔型杀怪越多,怪物对该塔型抗性越高** | | **股票式投资** | **`/vd bank` 波间存取宝石,存款按波计息,利率随民心波动** | | **多房间支持** | **强制依赖 Multiverse-core:右键传送门打开房间 GUI(箱子图标、分页、最多 16 房间可配置),显示【已开始】【等待中】【结束中】三态(绿色可加入;关闭中途加入时已开始房间红色"无法加入");每房间复制全新世界、出生点/刷怪点/村民点等一比一复制,房间名 {竞技场名}-{房间序号}(空位补号),游戏结束删除世界并保留竞技场记录** | | **独立聊天频道** | **每竞技场每房间独立频道 {竞技场名}-{房间号}:局内聊天只发给同房间玩家并带频道前缀;回大厅/离开竞技场/重连服务器自动回到公共频道,互不串台** | | **自定义附魔商店** | **附魔商店 26 种附魔(含 7 种新增:火焰保护/摔落保护/深海探索者/效率/灵魂疾行/冰霜行者/抢夺),价格在 `config.yml > enchantShop` 中可配置,升级按钮与消耗 XP 分级升级** | | **补给包概率池** | **补给包按等级权重抽取小/中/大/特大四档,权重在 `config.yml > carePackage.tierWeights` 中可配置(波次越高大/特大档概率越高)** | | **最终攻势模式** | **竞技场级开关:开启后每局村民数量固定(默认 5,`config.yml > villagers.finalAssaultVillagerCount` 可配,竞技场设置可单独覆盖),只补足到固定上限、不随回合增加,防守压力稳定** | | **肉鸽词条扩充** | **肉鸽卡池 18 张(9 内置 + 9 新增:急速修筑/幸运眷顾/玄武护体/冰霜狂潮/暗夜庇护/钢铁壁垒/工兵特训/血色狂热/深暗守望),每波投票仍 3 选 1** | | **竞技场导出/导入指令** | **`/vd export <竞技场>|all` 导出到 `arenas/*.yml`;`/vd import <文件名> [new]` 从文件导入(需 vd.admin)** | ### 多房间系统与独立聊天频道 - **多房间模式(强制 Multiverse-core)**: ooms.enabled(默认 rue)开启后,玩家右键竞技场传送门不再直接加入,而是打开**房间选择 GUI**: - 每个竞技场最多 ooms.maxPerArena(1-16,可配置)个房间,房间图标为**箱子**,支持**分页**(45 个/页); - 房间显示 **【等待中】(绿色·可加入)/【已开始】(绿色·可中途加入;管理员关闭中途加入 lateArrival 时显示红色"无法加入")/【结束中】(红色·无法加入)** 三态,并显示人数与"点击加入"; - 点击 **"开启新房间"**:从模板世界(= 该竞技场配置的世界,playerSpawn/corner1/config.spawn.world 均可定位)**异步复制一个全新世界**(排除 playerdata/stats/session.lock/uid.dat),出生点/刷怪点/村民点/商店/传送门等配置**一比一复制**(写入 renaData.yml 的 rena.{房间ArenaId} 段),房间名自动命名 **{竞技场名}-{房间序号}**(序号取最小空缺,空位补号); - **游戏结束后**(结算 +14 秒):删除房间世界文件夹与配置段,把房间战绩合并回模板竞技场记录,**保留竞技场记录**; - 服务端**未安装 Multiverse-core** 时启动日志醒目警告,房间世界复制自动回退**内置实现兜底**(房间功能仍可用;推荐安装 Multiverse-core 以获得完整支持)。 - **独立聊天频道**:房间内玩家聊天只发送给**同一房间**的玩家,并带频道前缀 **[竞技场名-房间号]**;**离开竞技场 / 回大厅 / 重连服务器**自动回到公共频道,不会串台冲突。 - **自定义附魔商店**:附魔商店扩展至 **26 种**附魔,新增 **火焰保护 / 摔落保护 / 深海探索者 / 效率 / 灵魂疾行 / 冰霜行者 / 抢夺**(含原 19 种);每种附魔价格可在 config.yml > enchantShop.<key> 配置,附魔书兑换 + 升级按钮 + 消耗经验值分级升级。 - **补给包概率池**:补给包不再固定档次,按等级从 config.yml > carePackage.tierWeights.<level> 权重随机抽取 **小/中/大/特大** 四档(波次越高,大/特大档概率越高;默认第 4 波起 35% 大 + 65% 特大)。 ### 已实现的增强功能 - **Leaf 服务端兼容** — 经过测试的兼容性 - **Folia 支持** — `folia-supported: true`,调度器适配层(全局/区域/异步) - **ChestCommands 集成** — 通过 API 和命令集成 - **怪物自定义属性** — 全局/逐怪属性倍率、难度缩放(22 种怪物) - **怪物 AI 设置** — 目标更新间隔、搜索范围、优先攻击村民、破门(地面挖掘/飞行撞门,独立 30 秒冷却) - **怪物词缀系统** — 波次越高精英怪携带词缀概率越高(急速/护盾/爆炸/传送/吸血/燃烧) - **精英词缀互动** — 爆炸怪死亡留下灼烧地面、剧毒怪死亡留下毒素云(AreaEffectCloud 自动消散) - **远程攻击触发词缀** — 骷髅射出的箭命中目标同样触发吸血/点燃等词缀(`Projectile` 攻击者分支) - **村民死亡复仇** — 村民倒下后激怒周围怪物(速度+力量狂暴) - **波次生成粒子特效** — 每只怪物生成时在出生点播放传送门/烟雾粒子 - **多语言支持** — en_US / zh_CN - **游戏结束自动清场** — 游戏结束瞬间清除竞技场内所有生物(怪物/村民/宠物/掉落物),防止残留到下一局 - **实时倒计时** — 开始游戏倒计时通过 ActionBar 实时显示,并修复重复倒计时 Bug - **全套 AI 寻路** — 原版与 MythicMobs 敌人都使用改进的寻路 AI(上楼梯、追踪村民 X/Z、飞行怪空中追击) - **绿宝石线程安全** — `VDPlayer.addGems` / `canAfford` 加 `synchronized` 锁,消除 Folia 多区域线程下的 read-modify-write 竞态 - **全员村民仇恨** — 所有竞技场怪物(含 MythicMobs 精英与词缀召唤的仆从)都会优先攻击村民;`monsterAI.enabled` 默认开启 - **幻翼固定伤害** — 幻翼攻击力基准值固定为 2(难度加成仍作为修饰符叠加) - **宠物击杀归属** — 铁傀儡/狼击杀怪物时,绿宝石与击杀数直接结算给其召唤者 - **中途入场补偿** — 非回归的迟到玩家获得场上所有玩家绿宝石余额平均值的 75%(`economy.lateJoinGemPercent`);回归(掉线保护恢复)玩家不获得 - **入场选套装** — 中途加入的玩家进入前可打开套装选择菜单选择套装;回归玩家自动跳过选择、恢复掉线前装备 - **难度水晶加成** — 竞技场难度标签(简单~噩梦)使最终水晶结算额外 +10%/级(噩梦 +40%) - **假死防护** — 修复部分怪物/村民血量归零后"假死抽搐、无法强制杀死"(吸收/恢复类词缀复活、死亡事件异常兜底;村民另设实体级假死守护任务) - **禁用合成表** — 竞技场内玩家禁止合成任何物品(`disableCrafting.enabled`),防止合成非法物品 - **QA 枪械防护** — QA 枪械攻击蜜蜂(召唤物)等无伤害目标不再刷钱(击杀结算前校验取消状态 + 排除召唤物) - **召唤物伤害修复** — 召唤物被怪物攻击恢复正常掉血(环境伤害处理器不再误拦截实体攻击) - **信息板(InfoBoard)** — 竞技场入口处的浮字公告栏,仅展示简要玩法介绍,`/vd admin infoBoard` 管理 - **绿宝石建造塔** — 手持绿宝石右键方块打开建造菜单,6 种塔直接消耗绿宝石建造(原为商店购买塔物品,现为直接建造) - **难度缩放怪物数量** — 竞技场难度系数现在也会缩放每波怪物生成数量(简单 ×0.6、普通 ×1.0、困难 ×1.15、噩梦 ×1.3),难度 1 不再与难度 3 生成一样多的怪物。此前怪物数量完全不受难度影响,导致低难度显得过于拥挤。 - **入场合管理面板空指针防护** — 玩家在(延迟执行的)入场流程中掉线时,跳过发放选择物品而非抛出 `NullPointerException`;`/vd admin` 不再崩溃(也不再误报为"语言文件"问题)。 - **64 位数值溢出修复** — 所有持久累积统计(`crystalBalance`、`totalKills`、`totalGems`、`topBalance`、`topKills`、`topWave`、`bpXp`、`reputation`、`points`、`disconnectGems`)及局内宝石/击杀计数(`VDPlayer.gems`/`kills`/`bankBalance`)现以 64 位 `long` 存储、用 `getLong()`/`Long` 读取,取代原 32 位 `int`/`getInt()`。此前数值超过约 21 亿会被 `getInt()` 静默截断或回绕,导致榜单、水晶余额、成就校验数据损坏。 - **自定义套装效果编辑器兼容** — 原 `PotionEffectType.getKeyOrThrow()` 在 Leaf 26.1.2 不存在,保存/移除效果时刷 `NoSuchMethodError`。回退 `getKey().getKey()`,并支持原版命名空间格式 `minecraft:<类型> <时长> <等级>`,时长可用 `infinite` / `inf` 表示永久。 - **VIP 护送目标完成修复** — 原 VIP 村民仅依赖 Pathfinder 寻路,村民 AI 反应迟缓甚至完全静止,到达村庄中心结算不了。增加每 tick 速度向量兜底,平地/直线路径上 VIP 持续前进,到达村庄中心正常结算奖励。 - **移除【好奇】村民机制** — 不再有村民主动跟随玩家 / 右键驱离 / 喂食提升亲密度 / 好感度挡刀,玩家反馈的"村民莫名跟随"问题随机制整体移除。`villagers.curious*` / `villagers.affinity*` / `villagers.scare*` 等配置项保留代码骨架但不生效。需要村民"跟着玩家"的护送类需求请使用目标波次的 escort(VIP 村民护送)。 --- ## 2. 服务端兼容性 ### 支持的服务器软件 | 服务器 | 版本范围 | 状态 | |--------|---------|------| | Spigot | 1.21.x - 26.x | 完全支持 | | Paper | 1.21.x - 26.x | 完全支持 | | **Leaf** | 1.21.x - 26.x | **已验证兼容** | | Purpur | 1.21.x - 26.x | 完全支持(Paper 分支) | | **Folia** | 1.21.x+ | **受支持**(`folia-supported: true`) | ### Folia 兼容性说明 插件声明 `folia-supported: true`,并通过 `SchedulerUtils` 调度器适配层支持 Folia: 1. **全局调度** — 基于 `GlobalRegionScheduler` 2. **区域调度** — 基于 `RegionScheduler`(`scheduleRegionTask`,延迟 ≥1 tick 用 `runDelayed`,0 tick 用 `run`) 3. **异步调度** — 基于 `AsyncScheduler` 4. 普通服务端自动回退到 Bukkit `Scheduler` ### Leaf 兼容性说明 Leaf 是基于 Paper 的性能优化分支。插件通过以下方式确保兼容: 1. **软依赖声明** — plugin.yml 中声明 `Leaf` 为可选的软依赖 2. **API 级别 API** — 仅使用 Bukkit/Spigot 公开 API,不依赖私有实现 3. **NMS 版本检测** — 通过 `Bukkit.getBukkitVersion()` 解析版本,而非依赖服务端包名 4. **反射加载** — NMS 管理器通过反射创建,避免直接类引用 支持的 Minecraft 版本 → NMS 模块映射(1.21.x 与 26.x 全版本): - 1.21.0 / 1.21.1 → `v1_21_R1`(`nms/v1_21_r1`) - 1.21.2 / 1.21.3 → `v1_21_R2`(`nms/v1_21_r2`) - 1.21.4 → `v1_21_R3`(`nms/v1_21_r3`) - 1.21.5 / 1.21.6 → `v1_21_R4`(`nms/v1_21_r4`) - 1.21.7 / 1.21.8 → `v1_21_R5`(`nms/v1_21_r5`) - 26.x(26.0 ~ 26.1.x)→ `v1_26_R1`(`nms/v1_26_r1`,纯 Mojmap 无版本包结构) > 跨版本 API 兼容:`Attribute` 常量在 1.21.2+ 去掉了 `GENERIC_` 前缀(如 `MAX_HEALTH` / `GENERIC_MAX_HEALTH`),插件通过 `Attributes` 工具类在运行时按服务端版本回退解析,同一份 JAR 兼容全部 1.21.x 与 26.x。 --- ## 3. 软依赖 插件支持以下可选的软依赖: | 插件 | 用途 | 必需 | |------|------|------| | PlaceholderAPI | PAPI 变量扩展 | 否 | | MythicMobs | 自定义怪物联动 | 否 | | WorldGuard | 世界区域保护 | 否 | | WorldEdit | 建筑工具 | 否 | | Multiverse-Core | 多房间模式强制依赖(房间世界复制);未安装时房间模式禁用并回退单竞技场 | 是(多房间) | | **ChestCommands** | **菜单联动,提供 [vd] 动作** | 否 | | **Leaf** | **性能优化服务端兼容** | 否 | --- ## 4. 命令列表 ### 玩家命令 | 命令 | 描述 | 权限 | |------|------|------| | `/vd admin` | 打开管理面板 | `vd.use` | | `/vd help` | 查看帮助信息 | 无 | | `/vd leave` | 离开当前游戏 | 无 | | `/vd stats [player]` | 查看玩家统计 | 无 | | `/vd kits` | 查看玩家套装 | 无 | | `/vd defaultkit` | 打开默认套装选择菜单 | 无 | | `/vd join` | 以 Phantom 套装加入满人竞技场 | 无 | | `/vd achievements` | 查看成就 | 无 | | `/vd sign` | 领取每日签到奖励(水晶 + 连续签到加成,config 可配) | 无 | | `/vd crystals <player> <amount>` | 修改玩家水晶余额(**不能给自己**) | `vd.crystals` | | `/vd die` | 在游戏中自杀 | 无 | | `/vd shop` | 打开积分商店(用击杀获得的积分兑换奖励) | 无 | | `/vd bank [deposit\|withdraw] <数量>` | 股票式投资:波间存取绿宝石,存款按波计息(利率随民心) | 无 | | `/kit <礼包码>` | 兑换礼包码(钻石/套装) | 无 | | `/kit daily` | 领取每日礼包(每天一次,奖励由管理员配置的 DAILY 礼包决定) | 无 | ### 管理命令 | 命令 | 描述 | 权限 | |------|------|------| | `/vd admin lobby` | 管理大厅 | `vd.admin` | | `/vd admin infoBoard` | 管理信息板 | `vd.admin` | | `/vd admin leaderboard` | 管理排行榜 | `vd.admin` | | `/vd admin arena` | 管理竞技场 | `vd.admin` | | `/vd start [arena]` | 强制开始游戏 | `vd.start` | | `/vd end [arena]` | 强制结束游戏 | `vd.admin` | | `/vd delay [arena]` | 重置等待倒计时 | `vd.start` | | `/vd fix` | 修复/更新配置文件 | `vd.admin` | | `/vd debug <level>` | 设置调试级别 | `vd.admin` | | `/vd reload` | 重载插件数据 | `vd.admin` | | `/vd export <竞技场名>|all` | 导出竞技场配置到 `arenas/<地图名>.yml`(all = 全部导出) | `vd.admin` | | `/vd import <文件名> [new]` | 从 `arenas/` 文件夹导入竞技场配置(`new` 强制新 id) | `vd.admin` | --- ## 5. 管理员 GUI 指南 ### 主菜单导航结构 ``` MAIN_MENU ├── Lobby Settings ├── Info Boards ├── Leaderboards │ ├── Total Kills / Top Kills / Total Gems │ ├── Top Balance / Top Wave │ └── Crystals (水晶排行榜,DIAMOND 按钮) ← 新增 ├── Arenas │ └── [Arena Name] │ ├── Game Settings │ │ ├── Allowed Kits │ │ ├── Player Settings │ │ └── ... │ ├── Mob Settings │ │ ├── Monster Customization ← 新增 │ │ │ ├── Global Health/Damage/Speed │ │ │ ├── Difficulty Scaling │ │ │ └── Per-Mob Settings (22种怪物, 翻页) │ │ ├── Monster AI ← 新增 │ │ │ ├── Target Update Interval │ │ │ ├── Villager Search Range │ │ │ └── Prioritize Villagers Toggle │ │ └── MythicMobs Integration ← 新增 │ │ └── Enable/Disable Toggle │ └── ... ├── Server Settings (COMMAND_BLOCK) ← 新增 ├── Keep Inventory Toggle ├── Auto Open Arenas Toggle └── Reload Plugin Button ├── 礼包设置 (GOLD_INGOT) ← 新增 │ ├── 新建礼包 │ └── [礼包码] 编辑 │ ├── 礼包码重命名 │ ├── 钻石数量 │ ├── 给予套装(原版或自定义,含未开放的) │ ├── 总使用次数(-1 无限) │ ├── 每人限用 │ ├── 使用统计 │ ├── 过期时间(0 永久 / N 天后过期) │ ├── 领取全服公告(开关) │ ├── 每日礼包(开关,/kit daily 领取) │ └── 删除礼包 └── 积分商店设置 (EMERALD) ← 新增 ├── 新建兑换项 └── [兑换项] 编辑 ├── 兑换类型(钻石 / 套装 / 物品) ├── 价值(数量或套装名,含未开放的自定义套装) ├── 所需积分 ├── 物品类型(仅物品型,Material 名称) └── 删除兑换项 ``` ### 自定义套装管理入口 ``` MAIN_MENU → Arenas → [Arena] → Game Settings → Custom Kits (新增) ``` 或在 `Mob Settings` 页面的导航栏中也提供了快捷入口。 ### 玩家默认套装选择 在游戏中,玩家可以: 1. 输入 `/vd defaultkit` 打开默认套装菜单 2. 点击任意已拥有的套装将其设为默认 3. 点击"无套装"按钮清除默认选择 4. 下次加入任意竞技场时自动装备该套装 ### 防护塔建造(游戏内) 玩家在游戏中可以直接消耗**绿宝石余额**建造防御塔,无需先购买物品: 1. 手持绿宝石(`EMERALD`)右键任意方块(需在竞技场边界内) 2. 弹出**建造菜单**(`TURRET_BUILD_MENU`),展示全部 6 种塔的属性与造价 3. 点击选中的塔 → 扣除对应绿宝石并立即生成塔 | 塔类型 | 图标 | 行为 | 造价 | |--------|------|------|------| | 箭塔(ARROW) | DISPENSER | 自动射击附近怪物 | 400 | | 闪电塔(LIGHTNING) | LIGHTNING_ROD | 对最近怪物降下闪电,无视护甲的高额单体伤害 | 500 | | 爆炸陷阱(BOMB) | TNT | 怪物靠近后自毁范围伤害 | 600 | | 火焰塔(FLAME) | BLAZE_POWDER | 周期性点燃并灼烧 | 700 | | 冰霜塔(FROST) | ICE | 强烈减速(冻结) | 800 | | 治疗塔(HEALING) | GOLDEN_APPLE | 为附近村民/玩家/铁傀儡回血 | 900 | 行为细节: - 每座塔生成一个**可视化盔甲架标记**:头顶佩戴类型图标、显示金色塔名,一眼可辨 - 余额不足时提示"买不起"并**自动关闭菜单** - 每名玩家最多同时放置 `turrets.capPerPlayer`(默认 3)座塔 - 绿宝石不足/塔数上限的扣费顺序保证:**先检查、后放置、成功才扣费**,不会误扣 - `VDPlayer.addGems` / `canAfford` 已加 `synchronized` 锁,快速双击或跨区域线程并发时不会重复扣费、余额不会变负 ### 逐怪设置(Per-Mob Settings) `Arenas → [Arena] → Mob Settings → Monster Customization → Per-Mob Settings` 支持对 **22 种怪物** 单独配置生命/伤害/速度倍率等(翻页浏览): | 怪物代码 | 名称 | 怪物代码 | 名称 | |---------|------|---------|------| | `zomb` | Zombie | `blze` | Blaze | | `husk` | Husk | `ghst` | Ghast | | `wskl` | Wither Skeleton | `pill` | Pillager | | `brut` | Piglin Brute | `slim` | Slime | | `vind` | Vindicator | `mslm` | Magma Cube | | `spid` | Spider | `crpr` | Creeper | | `cspd` | Cave Spider | `phtm` | Phantom | | `wtch` | Witch | `evok` | Evoker | | `skel` | Skeleton | `hgln` | Zoglin | | `stry` | Stray | `rvgr` | Ravager | | `drwd` | Drowned | `w` | Wither | 每个怪物的编辑界面顶部会显示 **"正在编辑"(NAME_TAG)指示按钮**,标明当前编辑的怪物名称与代码,避免混淆(例如 `正在编辑: Zombie (zomb)`)。怪物代码与 `spawnTables/*.yml` 中使用的代码一致,逐怪修改可正确生效。 --- ## 6. 选定套装系统 ### 工作原理 选定套装系统允许玩家预先设定一个"默认套装": 1. 玩家使用 `/vd defaultkit` 或 ChestCommands 菜单打开选择界面 2. 选择已拥有的套装作为默认 3. 数据持久化到 `playerData.yml`(`<UUID>.selectedKit` 路径) 4. 玩家加入竞技场时自动装备选定的套装 ### 数据存储 选定套装数据存储在 `playerData.yml`: ```yaml <player-uuid>: selectedKit: "Mage" # 套装名称 kits: ... ``` ### 相关类 | 类 | 说明 | |------|------| | `VDPlayer` | `selectedKitName` 字段 + getter/setter | | `PlayerManager` | `saveSelectedKit()` / `loadSelectedKit()` | | `Inventories.createPlayerDefaultKitsMenu(Player)` | 默认套装选择 GUI | | `VillagerDefenseAPI.openDefaultKitMenu(Player)` | API 入口 | --- ## 7. 自定义套装系统 ### 功能概述 管理员可以在管理 GUI 中创建和管理自定义套装: - 自定义名称和图标 - **水晶价格**(0 = 免费;购买扣除玩家 `crystalBalance`,非绿宝石) - 自定义武器(最多 36 个槽位,多页 GUI) - 自定义护甲(靴子、护腿、胸甲、头盔) - 自定义药水效果(类型、持续时间、等级) - 启用/禁用切换 - 多页管理界面 ### 可见性与购买 - 自定义套装在所有套装界面中均可见:`/vd kits` 玩家套装界面、`/vd defaultkit` 默认套装界面、竞技场大厅/局内选择界面、管理员界面 - 管理员编辑界面中价格以 **水晶(DIAMOND 图标)** 为单位显示与设置 - 玩家购买时扣除 `playerData.yml` 中的 `crystalBalance`(水晶余额),不足则提示无法购买 - 价格持久化在 `custom_kits.yml` 的 `price` 字段,重载/保存不会丢失 ### 数据存储 自定义套装存储在独立的 `custom_kits.yml` 文件中: ```yaml kits: <kit-id>: name: "My Custom Kit" price: 500 # 水晶价格(0 = 免费) icon: <base64 encoded ItemStack> description: - "Line 1" - "Line 2" armor: - <boots> - <leggings> - <chestplate> - <helmet> items: - <item 0> # 热键栏槽位 0 - <item 1> ... effects: - type: "SPEED" duration: 600 amplifier: 1 ambient: true particles: true icon: true enabled: true ``` ### 相关类 | 类 | 说明 | |------|------| | `CustomKit` | 自定义套装数据模型 | | `CustomKitManager` | 自定义套装管理器(CRUD + 持久化) | | `Inventories.createAdminCustomKitsMenu(int page)` | 管理员套装列表 GUI(分页) | | `Inventories.createAdminCustomKitEditMenu(String kitId)` | 编辑单一套装 GUI | | `Inventories.createAdminCustomKitWeaponsMenu(String kitId, int page)` | 武器编辑器(多页) | | `Inventories.createAdminCustomKitEffectsMenu(String kitId, int page)` | 效果编辑器 | ### GUI 操作流程 1. **套装列表** — `ADMIN_CUSTOM_KITS_MENU` - 每页显示 18 个套装 - 点击套装进入编辑 - "创建"按钮生成新的空套装 2. **套装编辑** — `ADMIN_CUSTOM_KIT_EDIT_MENU` - 图标预览(点击更换提示) - 名称显示(点击重命名提示) - **价格设置(水晶)**(点击输入价格) - 启用/禁用切换 - 三个子编辑器入口:武器、护甲、效果 3. **武器编辑器** — `ADMIN_CUSTOM_KIT_WEAPONS_MENU` - 每页 12 个槽位,3 页共 36 槽位 - 将物品从鼠标拖放到槽位设置 - 右键点击清除槽位 4. **效果编辑器** — `ADMIN_CUSTOM_KIT_EFFECTS_MENU` - 显示已有效果列表 - 点击效果移除 - 添加效果通过命令 --- ## 8. API 参考 ### `VillagerDefenseAPI` **包路径**: `me.prax.villagerdefense.plugin.api.VillagerDefenseAPI` ```java // 打开默认套装选择菜单 public static void openDefaultKitMenu(Player player) // 设置玩家默认套装 public static boolean setPlayerDefaultKit(Player player, String kitName) // 获取自定义套装数量 public static int getCustomKitCount() // 打开管理自定义套装列表菜单 public static void openAdminCustomKitsMenu(Player player, int page) // 检查自定义套装是否存在 public static boolean customKitExists(String kitId) // 重载自定义套装配置 public static void reloadCustomKits() // 保存自定义套装配置 public static void saveCustomKits() // 打开主管理菜单 public static void openAdminMenu(Player player) ``` ### `CustomKitManager` **包路径**: `me.prax.villagerdefense.plugin.game.models.kits.CustomKitManager` ```java // 初始化(插件启动时自动调用) public static void init(JavaPlugin plugin) // 从文件重载所有自定义套装 public static void reload() // 保存所有自定义套装到文件 public static void save() // 根据 ID 获取自定义套装 public static CustomKit getKit(String id) // 获取所有自定义套装 public static Collection<CustomKit> getAllKits() // 添加或更新自定义套装 public static void addKit(CustomKit kit) // 删除自定义套装 public static void removeKit(String id) // 检查套装是否存在 public static boolean kitExists(String id) // 获取套装数量 public static int getKitCount() ``` ### `CustomKit` **包路径**: `me.prax.villagerdefense.plugin.game.models.kits.CustomKit` ```java // 构造方法 public CustomKit(String name, ItemStack icon) // Getter/Setter public String getId() public String getName() public void setName(String name) public ItemStack getIcon() public void setIcon(ItemStack icon) public List<String> getDescription() public void setDescription(List<String> desc) public ItemStack[] getArmor() // [boots, leggings, chestplate, helmet] public void setArmor(ItemStack[] armor) public List<ItemStack> getItems() public void setItem(int slot, ItemStack item) public List<PotionEffect> getEffects() public void setEffects(List<PotionEffect> effects) public boolean isEnabled() public void setEnabled(boolean enabled) public int getPrice() // 水晶价格(0 = 免费) public void setPrice(int price) // 序列化 public Map<String, Object> serialize() // 反序列化 public static CustomKit deserialize(Map<String, Object> data) ``` ### `ChestCommandsHook` **包路径**: `me.prax.villagerdefense.plugin.api.ChestCommandsHook` ```java // 初始化钩子(插件启动时自动调用) public static void init() // 检查 ChestCommands 是否已连接 public static boolean isEnabled() // 执行 VD 动作 public static void executeAction(Player player, String action) // 获取 ChestCommands 插件实例 public static Plugin getChestCommands() ``` ### `PlayerManager` (新增持久化方法) ```java // 保存玩家选定套装 public static void saveSelectedKit(VDPlayer vdPlayer) // 加载玩家选定套装 public static String loadSelectedKit(Player player) ``` ### `GiftCodeManager` **包路径**: `me.prax.villagerdefense.plugin.tools.GiftCodeManager` ```java // 初始化(插件启动时自动调用) public static void init() // 查询 public static List<String> getAllCodes() public static boolean codeExists(String code) public static int getDiamonds(String code) public static String getKitName(String code) public static int getMaxUses(String code) public static int getUses(String code) public static int getPerPlayer(String code) public static long getExpiry(String code) // 毫秒时间戳,0 = 永久 public static boolean isExpired(String code) public static boolean hasBroadcast(String code) public static boolean isDailyCode(String code) // 管理 public static void createCode(String code, int diamonds, String kitName, int maxUses, int perPlayer) public static void deleteCode(String code) public static void renameCode(String oldCode, String newCode) public static void setDiamonds(String code, int value) public static void setKit(String code, String kitName) public static void setMaxUses(String code, int value) public static void setPerPlayer(String code, int value) public static void setExpiry(String code, long expiry) public static void setBroadcast(String code, boolean broadcast) public static void setDaily(String code, boolean daily) // 领取 public static boolean redeem(Player player, String code) public static int redeemDaily(Player player) // /kit daily,返回状态码 ``` ### `PointsManager` **包路径**: `me.prax.villagerdefense.plugin.tools.PointsManager` ```java public static int getPoints(Player player) public static int getPoints(String uuid) public static void addPoints(Player player, int amount) public static void setPoints(Player player, int amount) public static boolean spendPoints(Player player, int cost) // 余额不足返回 false 且不扣款 ``` ### `PointsShopManager` **包路径**: `me.prax.villagerdefense.plugin.tools.PointsShopManager` ```java // 查询 public static List<Integer> getAllItems() public static boolean itemExists(int id) public static String getType(int id) // diamond / kit / item public static int getValue(int id) // 数量(或套装名存储于 material) public static int getCost(int id) // 所需积分 public static String getMaterial(int id) // item: Material 名;kit: 套装名 // 管理 public static int createItem(String type, int value, int cost, String material) public static void updateItem(int id, String type, int value, int cost, String material) public static void deleteItem(int id) // 兑换(1 成功 / 0 失败) public static int redeem(Player player, int id) ``` ### `MonsterAffix` **包路径**: `me.prax.villagerdefense.plugin.game.models.MonsterAffix` ```java public static boolean isEnabled() public static List<String> getAffixes(LivingEntity ent) // 读取实体词缀(metadata VD_AFFIX) public static boolean hasAffix(LivingEntity ent, String name) public static void applyRandomAffixes(LivingEntity entity, Arena arena) public static void tickAffix(Mob mob, Arena arena) // 护盾恢复 / 传送,实体级任务驱动 public static void onAttack(Mob mob, LivingEntity victim) // 吸血 / 点燃 public static void onDeath(LivingEntity entity) // 死亡爆炸 + 死亡地面效果 ``` 支持词缀:`speed`(急速)、`shield`(护盾)、`explosive`(爆炸)、`teleport`(传送)、`lifesteal`(吸血)、`fire`(燃烧)。 **精英词缀互动(死亡地面效果)**:`explosive` 怪死亡后在原地留下灼烧地面(`fire` 类型,火焰粒子 + 伤害),`poison` 怪死亡后留下毒素云(绿色粒子 + 剧毒效果),通过 `AreaEffectCloud` 实现(自动消散,无需清理任务)。同一区域最多同时存在 3 片,配置见 `affixes.groundEffects`。 ### `VillagerProfession` **包路径**: `me.prax.villagerdefense.plugin.game.models.VillagerProfession` ```java // 为村民按 config 概率随机分配职业,并注册实体级光环任务(村民死亡自动失效) public static void assign(Arena arena, Villager villager) // 在名称/血条后追加职业标签(供血条刷新时保留职业显示) public static String withProfessionTag(LivingEntity entity, String baseName) ``` 支持职业(`villagerProfession.pool`): | 职业代码 | 名称 | 光环效果 | |---------|------|---------| | `farmer` | 农夫 | 饱和度 | | `cleric` | 牧师 | 生命恢复 | | `blacksmith` | 铁匠 | 抗性提升 | | `librarian` | 图书管理员 | 速度提升 | 职业村民存活期间持续为周围玩家(`villagerProfession.radius`)施加光环,村民倒下后光环立即失效。 ### `Mobs`(村民生成) **包路径**: `me.prax.villagerdefense.plugin.game.models.Mobs` ```java // 生成村民:赋职业(VillagerProfession.assign)。 // 【好奇】村民机制已整体移除——不再按概率打 VD_CURIOUS 标记、不再注册跟随任务。 public static void setVillager(Arena arena, Villager villager) // 兼容保留:永远原样返回 baseName,不再追加【好奇】标签 public static String withCuriousTag(LivingEntity entity, String baseName) ``` **【好奇】村民机制 — 已移除** 好奇 / 亲密度 / 驱离机制已整体移除。原因是村民级别的跟随任务一旦被打上标签后又去掉标签便无法干净拆除,会留下"村民莫名跟着玩家却没有好奇标签"的残留行为;玩家反馈的这个问题只能通过移除整个机制根除。 具体变更: - `setVillager` 不再按 `villagers.curiousChance` 概率赋 `VD_CURIOUS` 标记; - 不再有 `VD_CURIOUS` / `VD_AFFINITY` / `VD_SCARED` 元数据生命周期; - 不再有右键驱离 / 喂食绿宝石提升亲密度 / 好感度挡刀; - `withCuriousTag`、`getAffinity`、`feedAffinity` 保留为兼容空实现,编译期不再依赖; - config 的 `villagers.curious*` / `villagers.affinity*` / `villagers.scare*` / `villagers.expelCooldownSeconds` / `morale.expelKillPenalty` 保留代码骨架以兼容旧配置但不生效。 需要"村民跟着玩家"的护送类玩法请使用目标波次的 **escort(VIP 村民护送)**——其走完整的目标完成/失败结算路径。 ### `TurretManager` / `TurretType` **包路径**: `me.prax.villagerdefense.plugin.game.models.turrets.TurretManager` / `...turrets.TurretType` ```java // 每名玩家可同时放置的塔数量上限(turrets.capPerPlayer) public static int getCap() // 塔可升级到的最高等级(turrets.maxLevel) public static int getMaxLevel() // 将塔从当前等级升级到下一级所需绿宝石(turrets.upgradeBaseCost * level) public static int getUpgradeCost(TurretType type, int currentLevel) // 尝试升级塔:校验余额并扣费、提升等级、更新可视化标记(成功返回 true) public static boolean upgradeTurret(Arena arena, Player owner, Turret turret) // 根据物品显示名识别塔类型(覆盖全部 6 种塔) public static TurretType getTurretType(ItemStack item) // 放置塔:生成可视化盔甲架标记 + 注册区域驱动任务,上限已满返回 false public static boolean placeTurret(Arena arena, Player owner, TurretType type, Location loc) ``` `TurretType` 枚举:`ARROW` / `LIGHTNING` / `BOMB` / `FLAME` / `FROST` / `HEALING`。 **直接建造流程**:手持绿宝石右键方块(`GameListener`,需在竞技场边界内)→ 记录目标位置到玩家 metadata `VD_BUILD_LOC` → `Inventories.createTurretBuildMenu(Arena)` 打开建造菜单 → 点击塔(`InventoryListener.TURRET_BUILD_MENU` 分支)→ `canAfford` 检查 → `placeTurret` → 成功才 `addGems(-cost)` 扣费。余额不足提示后自动关闭菜单;塔数上限失败不扣费。 **塔升级流程**:塔主右键塔(`GameListener.onTurretInteract`,检测 `PlayerInteractAtEntityEvent` + ArmorStand 上的 `turret`/`VD` metadata)→ 满级/余额不足分别提示 → `upgradeTurret` 扣费升级。 **塔链协同**(塔 tick 内自动生效): - `countSameTypeNearby`:统计相邻 `turrets.synergy.range` 格内同类型塔 - 同类型协同:伤害/治疗 × `(1 + 相邻塔数 × bonusPerLevel)`;减速等级 +1/级 - **熔毁联动**:带 SLOWNESS 效果的怪物被火焰塔命中,伤害 × `meltdownMultiplier` - 等级缩放:伤害/治疗 ×1.2^(level-1),射程 ×1.1^(level-1),冷却 ×0.9^(level-1) ### `Arena`(波次惩罚 / 幸存者奖励相关) **包路径**: `me.prax.villagerdefense.plugin.game.models.arenas.Arena` ```java // 村庄绝望(波次惩罚)状态 public int getVillagersKilledThisWave() // 本波已死亡村民数 public void incrementVillagersKilledThisWave() // 村民死亡时 +1 public void resetVillagersKilledThisWave() // 新波开始清零 public int getDespairStacks() // 当前绝望层数 public int addDespair() // 叠加绝望(上限 despair.maxStacks) public void clearDespair() // 游戏重置时清零 public double getDespairMultiplier() // 1 + 层数 * despair.perStackMultiplier ``` - 村民每死 1 名:绝望层数 +1,全场广播(`messages.villageDespair`) - 新生成的原版与 MythicMobs 怪物按 `getDespairMultiplier()` 等比放大 MaxHealth/AttackDamage(`Mobs.applyDespairScaling`) - 新波开始 `resetVillagersKilledThisWave()`,用于"完美防守"判定(本波死亡数为 0) - 完美防守奖励 `survivorReward.perfectDefenseBonus`;游戏胜利按幸存村民数奖励 `survivorReward.winVillagerBonus` ### `Arena`(村庄民心 / 适应性进化 / 悬赏波相关) **包路径**: `me.prax.villagerdefense.plugin.game.models.arenas.Arena` ```java // 村庄民心(正向机制,0~100) public int getMorale() // 当前民心值 public void addMorale(int change) // 调整民心(上限 morale.maxMorale) public double getMoraleMultiplier() // 1 + (民心/上限) * morale.rewardMultiplier // 怪物适应性进化(伤害类型: fire/arrow/explosion/magic) public void countAdaptation(String type) // 记录一次该类型击杀 public int getAdaptationLevel(String type) // 当前抗性等级(击杀数/阈值,封顶 maxLevel) public double getAdaptationResistance(String type) // 抗性减免比例(level * resistancePerLevel) public void resetAdaptation() // 新局清零 // 悬赏精英波 public boolean isBountyWave() public void setBountyWave(boolean bountyWave) public boolean consumeBounty() // 悬赏波第一只普通怪消耗名额(成为悬赏目标) ``` - **民心**:村民死亡 -`morale.villagerDeathPenalty`(`GameListener`),完美防守 +`morale.perfectWaveGain`(`ArenaListener`);波次清怪奖励与胜利幸存奖励按 `getMoraleMultiplier()` 缩放 - **适应性**:`GameListener.onMobKill` 按 `getLastDamageCause()` 计数;`Mobs.applyAdaptationResistance` 给新怪打 `VD_ADAPT_<TYPE>` metadata;`GameListener.onHurt(EntityDamageEvent)` 按等级减免伤害(上限 80%) - **悬赏波**:`ArenaListener.onWaveStart` 在 `currentWave % bounty.everyNWaves == 0` 时开启并广播;`Mobs.setMinion` 中 `consumeBounty()` 命中时标记 `VD_BOUNTY`(血量 ×1.5、名字加 `messages.bountyTag`);击杀奖励见 `GameListener.onMobKill` ### `VDPlayer`(线程安全) **包路径**: `me.prax.villagerdefense.plugin.game.models.players.VDPlayer` ```java // 以下两个方法已声明为 synchronized,对同一实例的余额读写互斥, // 消除 Folia 多区域线程(怪物击杀线程 vs 玩家点击线程)下的 read-modify-write 竞态 public synchronized void addGems(int change) // 正数增加、负数扣除(Vault 绑定则同步到 Vault) public synchronized boolean canAfford(int cost) // 余额是否足够(Vault 绑定则读取 Vault 余额) ``` 建造/购买扣费顺序保证:先 `canAfford` → 再放置/校验 → **成功才 `addGems(-cost)`**,快速双击不会重复扣费、余额不会变负。 --- ## 9. ChestCommands 集成 ### 概述 插件通过 `ChestCommandsHook` 类提供与 ChestCommands 的集成。当检测到 ChestCommands 插件时,自动注册 [vd] 动作类型。 ### 在 ChestCommands 菜单中使用 在 ChestCommands 菜单配置中,使用 `[vd] <action>` 动作: ```yaml # 打开默认套装选择菜单 example_item: material: DIAMOND name: "&b选择默认套装" lore: - "&7点击选择你的默认套装" actions: - "[vd] defaultkit" # 打开管理面板 admin_item: material: COMMAND_BLOCK name: "&c管理面板" lore: - "&7点击打开管理面板" actions: - "[vd] admin" # 打开自定义套装管理 custom_kits_item: material: CHEST name: "&6管理自定义套装" lore: - "&7点击管理自定义套装" actions: - "[vd] customkits" ``` ### 可用动作列表 | 动作 | 说明 | |------|------| | `[vd] defaultkit` | 打开默认套装选择菜单 | | `[vd] admin` | 打开管理面板主菜单 | | `[vd] customkits` | 打开自定义套装管理菜单 | ### 通过 PlaceholderAPI 集成 插件提供以下 PAPI 变量(可用于 ChestCommands 菜单显示): | 变量 | 说明 | |------|------| | `%villagerdefense_selectedkit%` | 玩家当前选定的默认套装名 | | `%villagerdefense_arena_<name>_status%` | 竞技场状态 | | `%villagerdefense_arena_<name>_players%` | 竞技场玩家数 | | `%villagerdefense_crystals%` | 玩家水晶余额 | | `%villagerdefense_kills%` | 玩家总击杀数 | | `%villagerdefense_topwave%` | 玩家最高波次 | > 2.3.0 新增(标识符 `vd`,需安装 PlaceholderAPI): > > | 变量 | 说明 | > |------|------| > | `%vd_channel%` | 玩家当前聊天频道对应的竞技场名(公共频道为空字符串,可用于频道前缀) | > | `%vd_room_count%` | 玩家所在竞技场已开启的房间数量(房间内取所属模板) | > | `%vd_room_max%` | 房间数量上限(`rooms.maxPerArena`) | > | `%vd_crystalBalance%` / `%vd_topBalance%` / `%vd_totalKills%` 等 | 玩家持久统计数据(`_<玩家名>` 后缀可查离线玩家) | > > > 2026-09-08 扩充(无参数、基于当前玩家,可直接用于 Tab 栏 / 聊天格式 / 计分板): > > > > | 变量 | 说明 | > > |------|------| > > | `%vd_arena%` / `%vd_arena_id%` | 玩家所在竞技场名 / id(不在游戏中为空) | > > | `%vd_room%` / `%vd_room_name%` / `%vd_template_arena%` | 所在房间序号 / 显示名 `{竞技场}-{序号}` / 模板竞技场 id | > > | `%vd_wave%` / `%vd_max_wave%` | 当前波次 / 最大波次 | > > | `%vd_gems%` / `%vd_kills%` / `%vd_deaths%` / `%vd_killstreak%` | 本局宝石 / 击杀 / 死亡 / 连杀 | > > | `%vd_villagers%` / `%vd_enemies%` | 当前竞技场存活村民数 / 怪物数 | > > | `%vd_player_count%` / `%vd_max_players%` | 当前竞技场活跃玩家数 / 最大玩家数 | > > | `%vd_status%` | 当前竞技场状态中文(已关闭 / 等待中 / 进行中 / 结束中) | > > | `%vd_in_game%` / `%vd_spectating%` | 是否在游戏中 / 是否旁观(1/0) | > > | `%vd_online%` / `%vd_loaded_arenas%` | 在线玩家数 / 已加载竞技场数 | --- ## 10. 配置文件参考 ### `config.yml` 主要配置文件,新增的配置段: #### 聊天系统(2026-09-08 新增) 聊天消息显示格式可在 config.yml 的 `chat:` 段自定义,支持插入 PAPI 变量(如 `%vd_arena%`、`%vd_wave%`、`%vd_gems%` 等,需安装 PlaceholderAPI)。格式模板支持内置 占位符:`{player}`(发言玩家名)、`{message}`(消息内容)、`{channel}` / `{prefix}` (频道前缀,即玩家所在竞技场/房间名,含颜色;不在游戏中为空)。 ```yaml # 聊天系统设置 chat: # 是否启用插件聊天格式(频道内消息、全服广播按下方格式发送) enabled: true # 是否也接管公共频道(玩家不在竞技场/房间时)的聊天显示。 # true = 公共消息也按 publicFormat 格式显示(支持 PAPI 变量); # false = 公共频道保持服务器默认聊天逻辑(仅频道内消息使用本插件格式)。 interceptPublic: false # 公共频道消息格式(interceptPublic: true 时生效) publicFormat: "{player}&7: &f{message}" # 频道内消息格式(玩家在竞技场/房间中) channelFormat: "{prefix} {player}&7: &f{message}" # 全服广播格式(消息以 ! 或 ! 开头时发送给所有玩家) globalFormat: "&6[全服] &r{player}&7: &f{message}" # 是否用 PlaceholderAPI 解析格式中的 %xxx% 变量(未安装 PAPI 时自动跳过) usePlaceholders: true ``` > 提示:频道内消息默认就由插件接管发送(多房间独立频道),因此 `channelFormat` 无需 > 额外开关即可生效;公共频道默认保持服务器原样,开启 `interceptPublic: true` 后同样 > 支持自定义格式与 PAPI 变量。旧 config.yml 会自动增量合并缺失的 `chat` 段(不覆盖 > 已有设置)。 #### 怪物设置 ```yaml # 怪物自定义属性设置 monsterSettings: enabled: false globalHealthMultiplier: 1.0 globalDamageMultiplier: 1.0 globalSpeedMultiplier: 1.0 difficultyScalingMultiplier: 1.0 perMob: zomb: health: 1.0 damage: 1.0 speed: 1.0 babyChance: 1.0 equipmentBoost: 0 # 怪物 AI 设置 monsterAI: enabled: true targetUpdateInterval: 3 villagerSearchRange: 30 prioritizeVillagers: true doorBreaking: true # 怪物会破坏门(地面挖掘 / 飞行撞门) doorBreakCooldown: 30 # 破门冷却(秒),每只怪独立计时 # 怪物词缀设置 affixes: enabled: true # 是否启用词缀 chance: 0.15 # 精英怪携带词缀概率(0.0-1.0) maxPerMob: 2 # 每只怪最多词缀数 blindDurationTicks: 50 # 致盲词缀失明时长(tick,上限 200) # 精英词缀互动:携带特定词缀的怪物死亡后留下地面效果区域 groundEffects: enabled: true # 是否启用死亡地面效果 fireRadius: 3 # 爆炸怪灼烧地面半径(方块) fireDurationTicks: 200 # 灼烧地面持续时长(tick) poisonRadius: 3 # 剧毒怪毒素云半径(方块) poisonDurationTicks: 200 # 毒素云持续时长(tick) # 波次惩罚(村庄绝望):村民死亡叠加绝望层数,后续生成怪物属性等比提升 despair: enabled: true # 是否启用波次惩罚 maxStacks: 10 # 绝望层数上限 perStackMultiplier: 0.05 # 每层绝望的怪物属性加成(1 + 层数 * 系数) # 幸存者奖励 survivorReward: perfectDefense: true # 完美防守:本波无村民死亡则奖励 perfectDefenseBonus: 50 # 完美防守奖励绿宝石数 winVillagerBonus: 15 # 游戏胜利时每名幸存村民奖励绿宝石数 # 村庄民心系统(正向机制) morale: enabled: true # 是否启用 initialMorale: 50 # 每局初始民心 maxMorale: 100 # 民心上限 perfectWaveGain: 5 # 完美防守时民心提升量 villagerDeathPenalty: 8 # 村民死亡时民心下降量 expelKillPenalty: 10 # 玩家驱离【好奇】村民致死的额外民心惩罚 rewardMultiplier: 0.5 # 满民心时奖励倍率加成(1 ~ 1+0.5) # 怪物适应性进化 adaptation: enabled: true # 是否启用 threshold: 6 # 每击杀 N 只同来源怪物升一级抗性 maxLevel: 2 # 抗性等级上限 resistancePerLevel: 0.2 # 每级抗性的伤害减免比例 # 悬赏精英波 bounty: everyNWaves: 5 # 每隔 N 波出现悬赏目标 reward: 150 # 击杀悬赏目标的全队绿宝石奖励 # 村庄节日 festival: enabled: true # 是否启用节日波 chance: 0.15 # 每波触发概率(0.0-1.0) gemMultiplier: 0.5 # 节日波奖励额外加成(0.5 = +50%) # 村民生成节奏与特殊属性(好奇村民) villagers: # 每隔多少回合生成一批村民(2 = 每两回合一批) spawnIntervalWaves: 6 # 每批生成的村民数量 perInterval: 4 # 村民带【好奇】属性的概率(0.0 - 1.0,0.1 = 10%) curiousChance: 0.1 # 【好奇】村民刷新跟随目标玩家的间隔(tick,1200 = 每 60 秒一次,其余时间自由闲逛) curiousIntervalTicks: 1200 # 【好奇】村民跟随玩家的移动速度倍率(1.0 = 正常速度,0.8 = 悠闲半速) curiousFollowSpeed: 0.8 # 右键驱离【好奇】村民:驱离后指定秒数内不再跟随玩家,但会对该村民造成伤害 # (0 表示不启用驱离功能) scareDurationSeconds: 120 # 每玩家驱离【好奇】村民的冷却秒数(0 = 无冷却) expelCooldownSeconds: 240 # 驱离【好奇】村民对其造成的伤害(伤害可能致死村民) scareDamage: 5 # 村民死亡后的深红色腐蚀云:踏入的玩家受到缓慢 I、虚弱 II、中毒 I deathCloud: enabled: true radius: 3 durationTicks: 60 # 村民内鬼:隐藏内鬼持续给附近玩家施加减益,可被攻击,击杀提升民心+全队增益 mole: enabled: true # 是否启用内鬼机制 chance: 0.05 # 每只村民成为内鬼的概率(0.05 = 5%) debuffIntervalTicks: 100 # 减益施加间隔(tick) debuffRadius: 10 # 减益作用半径(方块) killMoraleBonus: 15 # 击杀内鬼的民心提升 killBuffDurationTicks: 300 # 击杀后全队增益时长(tick,300 = 15 秒) # 村民职业体系:村民随机获得职业,存活期间为周围玩家提供光环 villagerProfession: enabled: true # 是否启用职业体系 chance: 0.8 # 村民获得职业的概率(0.0-1.0) pool: # 职业池(farmer/cleric/blacksmith/librarian) - farmer - cleric - blacksmith - librarian radius: 30 # 光环作用半径(方块) intervalTicks: 60 # 光环刷新间隔(tick) amplifier: 0 # 光环药水效果等级(0 = 一级) # 防护塔系统设置 turrets: enabled: true # 是否启用防御塔/陷阱系统 capPerPlayer: 3 # 每名玩家同时最多放置的塔数量 tickInterval: 5 # 塔行为刷新周期(tick) # 箭塔 arrowRange: 12 arrowDamage: 4 arrowCooldownTicks: 30 arrowKnockback: 1 # 闪电塔 lightningRange: 10 # 射程 lightningDamage: 12 # 单体伤害(无视护甲) lightningCooldownTicks: 40 # 攻击间隔(tick) # 爆炸陷阱 bombTriggerRange: 2.5 bombPower: 2 bombDamage: 10 bombDamageRange: 6 # 火焰塔 flameRange: 7 # 射程 flameDamage: 2 # 每次灼烧伤害 flameFireTicks: 40 # 点燃时长(tick) flameIntervalTicks: 20 # 灼烧间隔(tick) # 冰霜塔 frostRange: 7 # 射程 frostDurationTicks: 80 # 冻结时长(tick) frostAmplifier: 3 # 减速等级 # 治疗塔 healRange: 8 # 作用半径 healAmount: 2 # 每次治疗量 healIntervalTicks: 20 # 治疗间隔(tick) # 塔升级系统 maxLevel: 3 # 塔最高等级 upgradeBaseCost: 300 # 升级基础花费(费用 = 基础价 × 当前等级) # 塔链协同 synergy: enabled: true # 是否启用 range: 3 # 相邻判定半径(方块) bonusPerLevel: 0.25 # 每座相邻同类型塔的加成(伤害/治疗倍率) meltdownMultiplier: 1.5 # 熔毁联动:减速怪被火焰塔命中的额外伤害倍率 # 成就解锁套装映射(达成成就自动解锁自定义套装) achievementKitRewards: # 成就ID: 自定义套装名称 # topKills9: "精英战士" # 游戏节奏 shopUpgradeWaves: 10 # 商店每 N 波升级 intermissionTime: 15 # 波次间休息秒数 # 经济设置 economy: waveInterestRate: 0.05 # 波次利息 killStreakInterval: 5 # 连杀加成间隔 killStreakBonus: 2 # 连杀奖励基础值 vaultBinding: true # 绿宝石与 Vault 经济绑定 lateJoinGemPercent: 0.75 # 中途入场补偿比例(原局内玩家平均值的百分比,0.75 = 75%) bank: # 股票式投资(/vd bank) enabled: true # 是否启用银行 baseRate: 0.03 # 每波基础利率(实际利率 = 基础利率 × 民心倍率) # 禁用合成表(竞技场内玩家禁止合成,防止合成非法物品) disableCrafting: enabled: true # 是否禁用竞技场内玩家的合成 # 目标波次(护送/据点) objectiveWaves: enabled: true # 是否启用目标波次系统 startWave: 4 # 从第几波开始可能触发 interval: 5 # 每隔多少波触发一次 escortReward: 200 # 护送目标奖励(绿宝石) escortHighlightCenter: true # 护送波高亮村庄中心点 escortArrivalRadius: 3.0 # VIP 村民到达中心多少格内视为完成 escortFailMoralePenalty: 15 # 护送失败(VIP 死亡)民心惩罚 escort: # 护送波委派行为(2.3.1 起) requireRightClick: true # 玩家需右键 VIP 村民认领护送者 allowTransfer: false # 是否允许后续玩家夺取已认领的护送权 disengageDurationMs: 3000 # VIP 护送途中受击后脱离跟随的毫秒数 # VIP 出生点:路径持久化为配置项 <arena>.vipSpawn(可用 /vd admin <arena> vipSpawn-set 设置) # MythicMobs 联动设置 mythicMobsIntegration: mode: 3 mixChance: 0.4 mobMappings: zomb: "CustomZombie" skel: "CustomSkeleton" bossMappings: w: "MythicWitherBoss" # 每日签到设置(/vd sign) signIn: enabled: true # 是否启用每日签到 reward: 50 # 每次签到奖励的水晶数量 ``` > 2.3.0 新增配置: ```yaml # 补给包池(/vd admin -> 补给包池 界面可视化编辑,也可直接改这里): # 开启补给包时除原有内容外,额外随机掉落池内的一件物品。 carePackage: pool: small: {} # 小型补给包物品池 medium: {} # 中型补给包物品池 large: {} # 大型补给包物品池 extra: {} # 豪华补给包物品池 # 武器词条:从商店购买武器随机附加 1-2 条(内置 27 条,可增删改;weight 越大越易抽到, # 设为 0 该词条永不抽到。老 config.yml 会自动补全新增内置词条,不覆盖管理员自定义值) weaponAffixes: sharp: { name: "锋利", lore: "+15% 伤害", type: DAMAGE, value: 0.15, weight: 10 } swift: { name: "迅捷", lore: "+10% 攻击速度", type: SPEED, value: 0.10, weight: 10 } leech: { name: "吸血", lore: "回复造成伤害的 8% 生命", type: LEECH, value: 0.08, weight: 8 } crit: { name: "暴击", lore: "15% 几率造成双倍伤害", type: CRIT, value: 0.15, weight: 8 } heavy: { name: "重击", lore: "击退 I", type: KNOCKBACK, value: 1, weight: 10 } flame: { name: "烈焰", lore: "点燃目标 2 秒", type: FIRE, value: 2, weight: 8 } frost: { name: "冰霜", lore: "减速目标 2 秒", type: FROST, value: 2, weight: 8 } poison: { name: "剧毒", lore: "使目标中毒 2 秒", type: POISON, value: 2, weight: 6 } execute: { name: "处决", lore: "对生命低于 30% 的目标 +50% 伤害", type: EXECUTE, value: 0.5, weight: 6 } rage: { name: "狂怒", lore: "生命低于 50% 时 +20% 伤害", type: RAGE, value: 0.2, weight: 8 } pierce: { name: "穿刺", lore: "远程攻击 +20% 伤害", type: PIERCE, value: 0.2, weight: 8 } block: { name: "格挡", lore: "10% 几率减免 50% 受到的伤害", type: BLOCK, value: 0.10, weight: 6 } wither: { name: "凋零", lore: "使目标凋零 2 秒", type: WITHER, value: 2, weight: 6 } thunder: { name: "雷霆", lore: "20% 几率附加闪电伤害", type: LIGHTNING, value: 0.20, weight: 5 } power: { name: "巨力", lore: "+30% 伤害", type: DAMAGE, value: 0.30, weight: 4 } vampire: { name: "血祭", lore: "回复造成伤害的 15% 生命", type: LEECH, value: 0.15, weight: 4 } deadly: { name: "致命", lore: "20% 几率造成三倍伤害", type: CRIT, value: 0.20, weight: 4 } frostbite:{ name: "冰封", lore: "减速目标 4 秒", type: FROST, value: 4, weight: 4 } venom: { name: "猛毒", lore: "使目标中毒 4 秒", type: POISON, value: 4, weight: 3 } guard: { name: "守护", lore: "20% 几率减免 60% 受到的伤害", type: BLOCK, value: 0.20, weight: 3 } executioner: { name: "斩首", lore: "对生命低于 30% 的目标 +100% 伤害", type: EXECUTE, value: 1.0, weight: 3 } berserk: { name: "狂暴", lore: "生命低于 50% 时 +40% 伤害", type: RAGE, value: 0.4, weight: 4 } plague: { name: "瘟疫", lore: "使目标中毒 6 秒", type: POISON, value: 6, weight: 2 } inferno: { name: "地狱火", lore: "点燃目标 5 秒", type: FIRE, value: 5, weight: 2 } storm: { name: "雷暴", lore: "35% 几率附加闪电伤害", type: LIGHTNING, value: 0.35, weight: 2 } assassin: { name: "暗杀", lore: "对生命低于 30% 的目标 +150% 伤害", type: EXECUTE, value: 1.5, weight: 2 } vampiric: { name: "血魔", lore: "回复造成伤害的 25% 生命", type: LEECH, value: 0.25, weight: 2 } # type 可选:DAMAGE 伤害 / SPEED 攻速 / LEECH 吸血 / CRIT 暴击 / KNOCKBACK 击退 # FIRE 点燃 / FROST 减速 / POISON 中毒 / EXECUTE 处决 / RAGE 狂怒 / PIERCE 穿刺 / BLOCK 格挡 # WITHER 凋零(秒) / LIGHTNING 雷霆(几率闪电,纯特效落雷+双倍伤害) ``` #### 2.3.0 更新内容 - **商店村民看向玩家**:商店村民每 10 tick 朝向 20 格内最近的活动玩家,不再呆站。 - **商店出生点粒子**:编辑状态(closed)下商店出生点显示环绕粒子(`particles.shop`,默认开启)。 - **补给包池 GUI**:`/vd admin -> 补给包池` 可为 小/中/大/豪华 四个等级维护随机掉落池(手持物品点击添加、点击移除、分页),开启补给包额外掉落池内物品(`carePackage.pool.<tier>`,默认空);右下角固定展示该档位**内置随机物资**预览(武器/护甲/消耗品/宝石,开包必定获得)。 - **武器词条**:商店购买的武器随机附加 1-2 条词条(12 条内置,`weaponAffixes` 可配置),含伤害/攻速/吸血/暴击/击退/点燃/减速/中毒/处决/狂怒/穿刺/格挡,命中与受击时结算。 - **入口传送门显示房间数**:多房间模式下入口村民头顶显示「x/y 房间」并即时刷新;房间内传送门右键直接回大厅。 - **FAWE 兼容**:WorldEdit 选区导入同时识别 FastAsyncWorldEdit。 - **PAPI 新变量**:`%vd_channel%`、`%vd_room_count%`、`%vd_room_max%`。 - **数值真无限**:宝石/存款/赛季经验等持久数据全部 long 化并钳制 `Long.MAX_VALUE`,修复中途入场平均、离场结算、波次利息等 int 溢出。 #### 2.3.1 更新内容 - **全服感叹号广播**:消息以 `!` 或 `!` 开头时变为全服全频道可见(公共频道与房间频道均可见,控制台同步输出);公共频道消息默认不取消,房间内玩家也能看到公共消息。 - **管理员词条编辑页**:`/vd admin -> 武器词条编辑` 分页展示全部内置词条,点击词条即可附加到手持物品(写入 lore 与 PDC,攻速/击退即时生效),已拥有词条时提示无法重复附加。 - **召唤物上限显示**:消耗品商店(雪傀儡/蜜蜂军团/猫)与自定义商店中的召唤物物品显示「上限 / 已召唤」;狼为个人上限(标注 `(个人)`),其余为竞技场全局;每次打开商店实时刷新,防止超限买入。 - **过时 API 清理**:修复 CustomKit `PotionType.getEffectType()`、RoomManager `World.getWorldType()`、15 个 NMS 类(v1_21_r1..r5)`MinecraftServer.getServer()` 的 deprecation 警告,构建输出不再提示「已过时的 API」。 #### 2.3.2 更新内容(修复) - **语言文件不再被覆盖重置**:修复 2.3.1 升级后管理员自定义的 `zh_CN.yml` / `en_US.yml` 被捆绑默认值覆盖(表现为"之前设定的数据丢失、需要重新配置")。语言文件升级机制改为**增量合并**:只追加捆绑包中缺失的新键,已有自定义翻译全部保留;语言文件版本号提升至 34,自动迁移一次后不再重复执行。 - **修复房间创建失败**:兼容现代 Paper/Leaf(1.20.5+ / 26.x)的 `<默认世界>/dimensions/minecraft/<名称>` 世界布局。房间创建改为"服务器标准位置创建空世界 -> 卸载 -> 异步用模板覆盖 -> 重新加载",不再把复制文件夹放到根目录触发 `Failed to migrate legacy world` 旧格式迁移;房间结束删除与启动孤儿清理同时兼容新旧布局,并自动清理旧版本遗留的根目录残留。 #### 2.3.3 更新内容(修复) - **修复房间世界"duplicate world"加载失败**:房间覆盖模板内容时不再复制模板的 `level.dat` / `paper-world.yml`(含模板世界 UUID),改为保留服务器为房间新生成的元数据(世界名与 UUID 均为房间独有),只覆盖 `region` / `entities` / `poi` / `data` 等世界内容,地形与模板完全一致,同时规避 Paper 的重复世界检测(`World minecraft:vdroom_* is a duplicate of another world`)。 #### 2.3.4 更新内容(修复) - **房间世界加载前兜底清理残留元数据**:重新加载前主动删除 `paper-world.yml` / `world.yml`,强制服务器基于房间自身 `level.dat` 重新生成唯一 UUID;即使旧版本遗留的模板元数据仍在磁盘上,也不再触发重复世界拒绝加载;加载失败时控制台输出房间文件夹路径与元数据状态,便于定位。 #### 2.3.5 更新内容(修复) - **房间世界改为"无元数据预置 + 单次加载"方案**:不再"创建空世界→卸载→覆盖→重载"(Windows 下元数据文件可能被占用导致删除失败、残留模板 UUID),改为先把模板内容复制到目标文件夹(**不含** `level.dat` / `paper-world.yml` 等任何元数据),再让服务器一次性创建世界——必然生成全新 UUID,机制上杜绝 `duplicate of another world`;同时排除 `dimensions` / `datapacks` 避免嵌套复制,并增加现代布局探测。 #### 2.3.6 更新内容(改进 + 修复) - **房间 = 一比一复制的地图**:复制前先保存模板世界(避免半截区块文件)、房间世界沿用模板种子(新区块完全一致)、复制游戏规则、文件复制带重试与大小校验。 - **入口不再叠加**:房间不再生成独立入口传送门(模板传送门设在大厅时原样复制会叠在一起),入口统一走模板传送门的房间选择界面;管理界面与命令 Tab 补全也不再列出房间竞技场。 - **武器词条编辑界面不再空白**:`weaponAffixes` 配置缺失时自动回退到内置 12 条词条。 #### 2.4.4 更新内容(补充:入口消失启动时序根治 · 世界收集补全 + 关闭状态误判自动纠正) - **用户 09-17 反馈:入口仍然"时不时消失",每次重启服务器后消失,玩家全部退出后重新进入也消失。** 排查确认 watchdog 之外还有三处根因(本轮一并修复): - **启动世界收集遗漏**:`Main` 启动校验此前只收集 lobby/waiting/spawn/corner1/corner2/ monsterSpawns/villagerSpawns 点位世界,**漏掉了 `arena.{id}.portal.world`(入口所在世界) 与商店点位世界**。Leaf/Folia 世界异步加载,重启后入口世界若晚于插件 enable 就绪, `refreshPortal()` 读到 `Bukkit.getWorld(入口世界)==null` 创建失败 → 入口对象缺失 → **重启后入口消失**;且 `unloadedWorlds` 不含入口世界,就绪重试机制不会为它等待/重建。 - **世界加载后的重建跳过 null 入口**:`refreshPortalsInWorld` 依赖入口对象取位置世界名, 入口为 null(重启后常见)时直接跳过——入口世界加载完成后**没有重建触发点**,入口长期缺失。 - **checkClose 误判关闭且无自动恢复**:启动时序下点位解析 null → `setClosed(true)` **持久化 closed=true**;之后世界加载完成只重建入口、**从不重跑 checkClose**——即使 `autoOpen: true`,竞技场也永远"已关闭",入口显示"已关闭"/不可用(等同消失)。 - **本轮修复(四层闭环)**: - `checkArenaNameAndGatherUnloadedWorlds` 补收集入口世界与商店点世界——入口世界未就绪时 插件等待初始化,就绪后全量重建入口并重新校验开启状态; - `refreshPortalsInWorld` 改为按**配置里的入口世界名**匹配(不依赖入口对象是否存在), 入口为 null 时也能正确命中并重建; - 新增 `GameManager.recheckArenasInWorld` + `Arena.recheckOpenState`:世界(重新)加载后 重建入口 + 若竞技场处于关闭、autoOpen 开启且点位世界现已完整解析,自动重跑 `checkClose()` 重新打开——启动时序误判的 closed=true 不再永久卡死; - watchdog 每 20 秒对所有竞技场兜底执行 `recheckOpenState()`(即使 WorldLoadEvent 错过/ 晚到也会自动纠正);`displayAllPortals` 自愈条件补上 `Bukkit.getWorld(名字)==null` (世界卸载→重载后旧 World 引用非 null 但已失效的判别)——"玩家全部退出后重新进入 入口消失"(空服世界卸载→重载场景)同样被覆盖。 - **自动纠正仅限启动时序误判**:`recheckOpenState()` 只纠正"构造时即处于关闭"的竞技场 (时序误判 / 上次运行的关闭意图,autoOpen=true 时重启本就会重开);**运行中管理员手动 关闭的竞技场绝不会被自动重新打开**。 #### 2.4.6 更新内容(补充:入口村民被带到竞技场世界 · 真正根因 = 入口世界被修复逻辑迁移到模板世界) - **用户 09-17 提供三个关键事实**:① 幽灵入口出现在**模板世界**(非房间世界);② `arena.{id}.portal.world` 的值大概为**模板地图世界名**;③ 测试服务器为 **Leaf 26.1.2**。 - **真正根因(配置世界污染,与发送范围无关)**:入口位置配置的 `portal.world` 指向了 **竞技场模板世界**,而玩家等待/出生在**大厅**(`lobby.world`,通常为主世界)。假实体 spawn 按世界名过滤只发给入口所在世界的玩家: - 大厅玩家收不到入口 spawn → **入口在大厅"消失"**(重启后/在大厅时永远看不到); - 玩家进入模板世界(竞技场)后世界名匹配 → 在**相同坐标**看到入口村民 → "入口村民被 带到了竞技场所在世界的相同位置"。 - **portal.world 为什么变成模板世界名**:`Main.repairRoomWorldPointRefs()`(启动时把所有 `vdroom_*` 的 `.world` 值改回模板世界)**没有区分 portal 键**——入口若曾被写进房间世界 (旧版本无写入拦截),重启后 repair 会把 `portal.world` 也统一改成模板世界名。但入口是 "玩家从大厅进入竞技场的门",应设在大厅;改到模板世界 = 入口"跑"到竞技场世界。 - **本轮修复**: - `repairRoomWorldPointRefs` 对 `portal.world` 使用**大厅世界名(`lobby.world`)**作为 `vdroom_*` 的修复目标(而非模板世界);`lobby.world` 未配置时保持原值并警告; - 启动时检测 `portal.world == arena.{id}.world`(模板世界)且 ≠ `lobby.world` → 级别 0 警告 + 修正指引(在大厅重新设置入口 / 手动改 portal.world),不再"悄悄迁移"。 - **现有污染配置修正(需管理员操作)**:把 `arena.{id}.portal.world` 改为大厅世界名;若 原入口是在房间里设置的,其坐标是房间=模板坐标,请直接在大厅**重新设置入口位置**更稳妥。 #### 2.4.5 更新内容(补充:入口村民被带到竞技场世界根治 · spawn 发送范围修正) - **用户 09-17 突破性发现:入口村民并没有消失,而是被带到了竞技场所在世界的相同位置** (房间世界/竞技场世界里出现大厅入口的村民与全息文字,坐标完全相同)。 - **根因(上一轮"发送范围对称化"引入的回归)**:`HoloLine`/`NPCVillager.displayForOnline` 改成跨世界广播(`sendPacketToOnline`)后,Minecraft 客户端把实体 spawn 包注册到玩家 **当前所在维度**的实体表——房间(vdroom_*)世界的玩家收到大厅入口的 spawn,客户端把 入口注册进房间维度 → 房间里渲染出大厅入口的幽灵;同时跨世界 spawn 没有解决"玩家回大厅后 大厅维度实体表为空"的问题,入口在大厅依然时隐时现,destroy 跨世界与之叠加还可能在切世界 窗口残留旧入口(回大厅后旧入口与新入口重叠)。 - **本轮修复(spawn 本世界 + destroy 跨世界,互补而非对称)**: - `displayForOnline` 改回 `sendLocationPacketToOnline`(按世界名过滤,只发入口所在世界) ——房间世界里不再出现幽灵入口; - `remove()` 保持跨世界广播(`sendPacketToOnline`)——清理所有"曾收到过 spawn"的玩家 客户端实体表(含切世界窗口残留的旧 ID);对从未收到过 spawn 的玩家删一个不存在的 ID 完全无害(EntityID 高位段全局唯一递减,不误删真实实体); - 回大厅补 spawn 三重保障(PlayerChangedWorldEvent / PlayerJoinEvent / watchdog 每秒 displayAllPortals),玩家回大厅后 ≤1 秒入口必然重新出现,不再依赖单一事件时序。 #### 2.4.3 更新内容(串房根治) - **串房(创建第二个房间后串房;"房间1的人退出房间,房间2的人也会被踢出、被拉回原地图")**:串房防护补成**加入 + 结束双向防线**—— - **加入路径**:玩家加入房间前若发现该房间的等待室/出生点世界不是**本房间世界**(配置被污染指向大厅/模板/别的房间),强制改写为房间世界并输出级别 0 诊断日志,杜绝"加入房间2却出生在房间1世界"; - **结束路径**:房间卸载世界前不再把 `world.getPlayers()` 全部踢回大厅(旧逻辑会把误入本房间世界的其他房间玩家连带踢回大厅)。现改为逐人核对归属:本房间玩家回大厅,属于其他房间/模板竞技场的玩家送回**他们自己竞技场的出生点**(不打断其所在对局)并输出 "cross-room detected" 日志。 - 若仍能复现,控制台级别 0 日志(`Join room ... world is ... should be ...` / `cross-room detected`)会直接指出哪一段世界名写错。 ##### 控制台日志清理(三类刷屏均非本插件输出) - **`Named entity Zombie[...] died: death.attack.starve.item`** → 服务器核心在记录"有自定义名字的实体死亡"。编辑服务器根目录 `spigot.yml`,将 `settings.log-named-deaths` 设为 `false`(村民相关还可将 `settings.log-villager-deaths` 设为 `false`),重启服务器即可。参考 [PaperMC spigot 配置文档](https://docs.papermc.io/paper/reference/spigot-configuration/)。 - **`[ProtocolLib] Disabling ProtocolLib ...` 反复刷** → ProtocolLib 与 Leaf 不兼容被反复禁用;本插件不使用 ProtocolLib,若服务器也不依赖它请从 plugins 移除。 - **`MythicMobs ... global-data.json AccessDeniedException`** → MythicMobs 在 Windows 上保存变量数据失败(文件被占用)。属 MythicMobs 自身问题:升级/检查占用/将服务器目录加入杀毒排除项。 #### 2.4.2 更新内容(修复 + 新增词条) - **修复开局领到两把木剑(用户反馈)**:此前除套装自带木剑外,`Tasks.giveItems` 还会**无条件再发一把**木剑(本意是保证"人人有近战武器",但所有内置套装——含 None——本身就带木剑,结果开局总是两把)。现已改为:先正常发放套装物品,仅当**背包/装备/副手完全没有近战武器**(剑/斧/三叉戟,覆盖自定义套装无武器的情况)时才补发一把基础木剑。 - **武器词条疑似不生效 + 词条扩展(用户反馈)**: - 词条附加在武器商店购买时执行,会先移除价格行再写入词条 lore 与 PDC。修复两处隐患:击退词条对弓/法杖等不兼容材料执行 `addEnchant` 可能抛异常、攻速词条重复附加属性修饰符可能抛异常——异常会让整次购买/附加中断,表现为"买了武器却没有任何词条"。现在词条附加(`applyAffix` / `applyRandomAffixes`)对附魔与属性修饰做了隔离容错,PDC/lore 必成功写入; - **新增 10 条内置词条(12 → 22 条)**:凋零(使目标凋零 2 秒)、雷霆(20% 几率落雷双倍伤害)、巨力(+30% 伤害)、血祭(回复 15% 伤害为生命)、致命(20% 几率三倍伤害)、冰封(减速 4 秒)、猛毒(中毒 4 秒)、守护(20% 几率减免 60% 伤害)、斩首(对低血目标 +100% 伤害)、狂暴(低血 +40% 伤害); - **老配置自动补全**:`weaponAffixes` 改为"内置词条打底 + config 同名覆盖 + 自定义保留"的增量合并——旧 config.yml 即使缺新词条也能自动获得全部新增内置词条,管理员改过的数值不会被覆盖(要停用某词条把 weight 设为 0); - **购买武器现在会明确提示获得的词条**("已附加词条 锋利"等),方便确认词条确实附加成功。 - **串房防御再加固(最后一道保险)**:房间创建收尾时若房间 Arena 的出生点世界名与本房间世界不一致(理论上 `writeRoomConfig` / `overrideRoomWorlds` 已保证一致,此处兜底防任何遗漏),强制改写为房间世界并输出级别 1 的纠正日志,杜绝"加入房间却出生在大厅/其他房间世界"的串房路径。 #### 2.4.0 更新内容(补充:入口消失根源闭环 · 配置文件编码乱码根治) - **用户 09-16 实测 watchdog 后入口仍消失,并指出"配置文件有乱码"**——排查后确认 **入口消失与配置乱码是同一个根因**:插件配置写入(arenaData.yml / playerData.yml / config.yml / 语言文件 / kit 文件 / 竞技场导出文件)此前用 **Bukkit `FileConfiguration.save(File)`**,它使用**平台默认编码**写盘——中文 Windows 服务器即 **GBK**;而读取一律按 UTF-8。写 GBK、读 UTF-8 → 重启后中文配置(竞技场名 / **世界名** / 自定义键)全部乱码;**portal 位置里的世界名一旦乱码**,`Bukkit.getWorld(乱码名)` 返回 null → 竞技场不满足开启条件 → **入口消失**。watchdog 每 1 秒重建入口时读到的仍是乱码, 永远重建失败——这就是"重启后入口仍然消失"的真正根源。 - **本轮修复**:所有配置写入路径统一改为显式 UTF-8(`saveToString()` + `Files.write(UTF_8)`): `DataManager.saveConfig()`、`ArenaDataIO.exportArena()`、`VanillaKitManager.save()`、 `CustomKitManager.save()`、`Main.saveConfig()`(override 原生方法)、语言文件合并写盘、 内置默认配置流读取编码(一并显式 UTF-8)。从此中文世界名/竞技场名写入磁盘都是标准 UTF-8,重启后读取一致,**配置乱码根源被永久根除**;配合 watchdog 兜底,入口彻底不会消失。 - **内置 config.yml 中文注释乱码根治**:仓库内置 config.yml 的 211 行 GBK-mojibake 中文 注释全部重写为正确中文(0 残留;功能键值原本无乱码,已确认不受影响)。 #### 2.4.0 更新内容(补充:"开启的入口消失、关闭的保留"根治 · 信息文本兜底 + 周期强制重建) - **用户 09-16 实测:"关服前处于开启的入口会消失,但处于关闭的入口不会消失;手动重新刷新 一次开关状态,入口又回到初始位置。"**——这条线索给出了决定性分水岭:开启 vs 关闭分支在 入口构造路径上的**唯一差异就是信息文本**。关闭分支是纯字符串拼接(永不抛异常);开启分支 调用 `String.format(roomCount,...)` + `RoomManager` 房间查询 + 多个状态 getter,任何一步 运行时抛异常(语言文件值损坏/格式串非法/房间数据异常)→ `new Portal` 抛 → `refreshPortal` catch **保留旧入口(重启后为 null)** → 入口永久消失(watchdog 每秒重建再次走到开启分支、 每秒失败);关闭分支永远成功 → 入口保留。手动刷新开关 = 无条件 `refreshPortal()` 重建 → "回到初始位置"。 - **修复①:`Portal` 构造信息文本整体 try-catch 兜底**——难度/状态/玩家数/房间数计算任何异常 都回退到最小安全文本(竞技场名 + 状态行),**开启分支与关闭分支一样永不因文本失败而消失**。 - **修复②:watchdog 周期强制重建**——每 20 秒对所有竞技场**无条件** `refreshPortal()` (先建后删、内部 try-catch、安全幂等):对象级自愈只覆盖"入口缺失"(portal==null),本层 覆盖**"入口对象存在但其显示已失效"**(位置世界引用错乱、EntityID/客户端实体表损坏、其他 插件干扰后对象看似正常实则不可见)——自动等价于管理员手动刷新开关状态,**"入口绝对不会 消失"的最强保险**。 #### 2.4.0 更新内容(补充:入口永不消失终极保险 · 持续守卫 watchdog) - **用户 09-16 提出终极要求:"确保入口绝对不会消失,即使是有其他插件强制清除也不可以消失!!!"**—— 入口是纯数据包假实体(只存在于客户端实体表),之前所有修复保证"正常流程下不消失", 但要满足"绝对不会消失"必须有一个**持续运行的守卫**:本轮新增 **`GameManager.startPortalWatchdog()`**(`GameManager.init()` 末尾启动,每 1 秒运行一次, 只注册一次): - **对象级自愈**:每个竞技场入口对象缺失 / location 世界引用失效 / 世界已卸载 (含 Leaf 同名世界双实例、其他插件 mv unload/load)→ 从配置 `refreshPortal()` 重建; 仅在真正缺失时重建(不浪费 EntityID),世界未加载时安全跳过、就绪后自动补建; - **幂等重发**:对所有在线玩家重新 spawn 所有入口(同一 EntityID+UUID 重复 spawn 是 客户端幂等更新,安全无害)——**无论入口被什么机制从客户端实体表清除(其他插件强制 清除、世界重载、数据包丢失、客户端实体表重置、未知 bug),最多 1 秒自动恢复**; - 玩家加入/切世界/世界加载的即时显示路径不变,watchdog 是覆盖一切未知场景的兜底自愈层。 - 与"wipe 不再持久化 closed=true"(上一轮)共同构成完整防线:入口对象永远存在、永远重发、 竞技场开放状态永远不被清理例程误标关闭。 #### 2.4.0 更新内容(补充:入口消失真正根因根治 · wipe 不再持久化 closed 状态) - **用户 09-16 实测:"昨天正常、今天重启后入口又消失",且明确大厅与入口在同一世界、 该世界不会被重新装载**——排除了世界重载方向。审计关闭/重启链路后找到真正根因: **`Arena.wipe()` 会把竞技场持久化为 `.closed = true`**(`config.set` + `saveArenaData`), 而 wipe 是清理例程,reload / onDisable / GameManager.init 都会调用——**每次服务器关闭/ 重启,所有竞技场都被写盘成"已关闭"**。重启后 `checkClose()` 只在 `autoOpen: true` 时才重新打开;autoOpen=false(管理 GUI 可切换)或点位校验临时不满足时,竞技场 保持"已关闭",入口显示"已关闭"/点击不可用——即用户看到的"入口消失"。 本轮修复:**`Arena.wipe()` 不再写 `.closed = true`**,开放状态只由 `setClosed()` (显式开关)、`checkClose()`(点位校验)、`openRoom()`(房间开放)决定。服务器 正常关闭/重启后竞技场保持关闭前的真实状态,入口不再被误标关闭。删除竞技场 (`Arena.remove()` → `config.set(path, null)` 整段删除)语义不变,不受影响。 #### 2.4.0 更新内容(补充:空服场景入口消失根治 · world 失效容错 + 显示自愈重建) - **空服/重启后无人时入口消失(用户 09-15 实测):根因是 world 引用失效 NPE**—— 入口/信息板/排行榜/竞技场板全部是**纯数据包假实体**,只在客户端实体表里。空服或 世界卸载→重载时,入口 location 的 `World` 引用失效(`getWorld()` 返回 null),而旧的 `displayForPlayer` 世界名比较写在 try 块外 → 玩家加入时 NPE → 中断 `displayAllPortals` 整批显示 → 玩家看到的入口永久消失;且入口对象缺失时无任何自愈重建路径。本轮修复: - **显示路径 world 判空容错**:`HoloLine.displayForPlayer` / `NPCVillager.displayForPlayer` 世界名比较与 spawn 全部包进 try,world 为 null 时安全跳过——不再 NPE 中断整批显示 (这两处是所有显示委托的公共发送点,一处修复全覆盖); - **玩家加入时入口自愈重建**:`GameManager.displayAllPortals` 显示前检查每个竞技场 入口:为 null 或 location 世界引用失效时先 `refreshPortal()` 从配置重建再 spawn; 每个竞技场独立 try-catch,单个入口异常不影响其他竞技场; - **批量显示分批隔离**:`GameManager.displayEverything` 内每个显示类别独立 try-catch, `JoinListener.onJoin` / `ArenaListener.onLeave` 的 `displayEverything` 调用点补 try-catch——空服/世界重载等地单个显示异常不再中断玩家加入/离开流程; - **世界重载后入口自动重建**:`WorldListener.onWorldLoadEvent` 在世界(重新)加载 完成后延迟 1 tick 调 `GameManager.refreshPortalsInWorld(worldName)`——世界卸载→重载 会让入口 location 的 World 引用指向旧实例,玩家加入时 spawn 落空/跳过 → "空服时 入口消失"。世界加载后入口用新 World 实例重建并重发,入口必然可见。 #### 2.4.0 更新内容(补充:入口消失终极防御 · 发送健壮性 + 自愈重发 + 切世界时序) - **入口消失系统性防御(用户 09-13 实测:spawn/destroy 对称化后两次操作正常、3 次及以上仍消失)**: 反编译 26.x `ClientboundAddEntityPacket` / `ClientboundRemoveEntitiesPacket` / `ClientboundSetEntityDataPacket` / `ClientboundRotateHeadPacket` 私有解码器,确认 全部包格式(1.20.5+ 的 `Vec3.LP_STREAM_CODEC`、`DataValue` 的 serializerId VarInt + codec 编码、0xFF 结束标记、5 字节高位段 EntityID 正数域)与 26.x **完全一致**, 排除包格式与 ID 分配问题。剩下的"3 次及以上消失"是**发送链路健壮性 + 时序竞态**, 本轮做三层系统性防御(覆盖入口/排行榜/信息板/竞技场板/出生点指示器,全部经由 HoloLine/NPCVillager 委托发送): - **广播级逐玩家异常隔离**:`sendPacketToOnline` / `sendLocationPacketToOnline` 每个玩家独立 try-catch——某玩家连接异常(退出/传送中)不再中断整轮广播,其余 玩家照常收到 spawn/destroy(此前单个玩家断线会让后续玩家收不到新入口 spawn 而 destroy 已发,入口对部分玩家"消失"); - **入口刷新后 1 tick 幂等自愈重发**:`Arena.refreshPortal` 刷新成功(spawn 新 + destroy 旧)后延迟 1 tick 强制重发当前入口 spawn。重复 spawn 同一组 EntityID+UUID 在客户端是幂等更新,安全无害——任何竞态/瞬时漏发(传送窗口、多线程交错、事件 时序)都会在下一 tick 自动补齐,"多次操作后入口消失"从机制上不可能再发生; - **切世界事件延迟 1 tick 再补显示**:`WorldListener.onPlayerChangeWorldEvent` 由 立即 `displayEverything` 改为延迟 1 tick——事件触发瞬间 `player.getWorld()` 可能 仍是旧世界引用,立即 spawn 会因世界名过滤落空(玩家回大厅后入口不可见);延迟 后玩家稳定在新世界,世界名过滤必然命中,入口/信息板/排行榜全部重新出现。 另给 `HoloLine.displayForPlayer` / `NPCVillager.displayForPlayer` 加单玩家 try-catch,单个玩家异常不再中断 `displayEverything` 批量循环。 #### 2.4.0 更新内容(补充:入口消失治本 · spawn/destroy 发送范围对称化) - **入口刷新后消失 —— 发送范围不对称根治(用户 09-10 实测仍复现,控制台贴图零报错)**: 反编译 26.x `ClientboundAddEntityPacket.STREAM_CODEC` 确认 spawn 包字节序**完全正确** (id、uuid、type、x/y/z、velocity、xRot/yRot/yHeadRot、data),排除包格式问题; 世界名比较 + 先建后删仍未根治。真正的根因是**发送范围不对称**:destroy 旧入口用 `sendPacketToOnline`(**跨世界发给所有在线玩家**),而 spawn 新入口用 `sendLocationPacketToOnline`(**只发给 portal 所在世界玩家**)。第二次刷新(创建 房间 2 / 退出房间)发生时玩家通常已身在房间世界(`vdroom_0_1`)——**收到了 destroy(旧入口消失)却收不到 spawn(新入口未出现)**,"入口消失"是该玩家客户端 的必然结果,且控制台零报错。现把 `HoloLine.displayForOnline` 与 `NPCVillager.displayForOnline` 改为 `sendPacketToOnline`(**跨世界发给所有在线玩家**, 与 destroy 完全对称):任何世界刷新入口时,每个玩家的客户端实体表都与服务端一致, "回大厅后补 spawn" 不再是唯一恢复路径(它此前在 Leaf 下依赖 PlayerChangedWorldEvent 时序,不可靠)。此修复同时覆盖入口 Portal、排行榜、信息板、 竞技场板、出生点指示器;`displayForPlayer`(玩家加入/切世界按需补显示)保留世界名 过滤,与全局刷新互不冲突、互为兜底。 #### 2.4.0 更新内容(补充:入口刷新后消失终修 · 世界双实例 + 刷新顺序 / 竞技场关闭禁开房 / UI 美化) - **入口刷新两次后消失 —— 世界双实例根治(用户反馈"刷新两次就会出问题")**:入口 spawn 只发给 portal 所在世界的玩家,原代码用 `player.getWorld().equals(world)` 按**对象引用**过滤;Leaf/Folia 同一世界名可能存在**多个 World 实例**,引用比较 必然失败 → 旧入口已被 destroy(跨世界发所有玩家)、新入口 spawn 无人收到 → "创建两个房间 / 创建后退出"都会触发入口消失,**控制台零报错**。第一次创建房间 碰巧单实例所以正常,房间世界加载后第二次刷新必现。三处世界过滤 (`PlayerManager.sendLocationPacketToOnline` / `HoloLine.displayForPlayer` / `NPCVillager.displayForPlayer`)全部改为**按世界名比较**。 - **入口刷新顺序加固**:`Arena.refreshPortal` 改为**先 spawn 新入口、成功后再 destroy 旧入口**;新入口 spawn 失败时恢复旧入口引用——任何一步失败入口都不再消失。 - **竞技场关闭禁止开启房间**:`RoomManager.createRoom` 增加 `template.isClosed()` 检查; 右键入口、房间选择菜单的"开启新房间"按钮(红色占位)、菜单点击处理均拦截已关闭 竞技场(提示"已关闭"),不再出现"入口显示已关闭却还能开房间游玩"。 - **UI 美化**:玩法信息板加分隔线边框 + 标题/内容装饰符号;竞技场入口房间数行加 ✦ 装饰;纪录板(ArenaBoard)标题加边框分隔线;房间选择菜单关闭状态显示红色占位。 #### 2.4.0 更新内容(补充:传送门并发损坏根治 + 补给包池显示内置随机物资) - **第二个玩家创建房间时入口消失 / 无法传送(根治,用户反馈)**:发包工具 `PacketSetter` (17 个 NMS 版本各自实现)原本是**全局单例**——每个版本一个静态实例,每次 `get()` 都 `clear()` 后复用同一个 Netty buffer。Leaf/Folia 是**多线程**服务端:主线程刷新 传送门、区域线程(房间世界加载后玩家切换世界触发 `displayEverything` 等)同时构造 spawn/destroy 包时**互踩同一个 buffer**,生成的包内容损坏但**不抛异常、控制台无任何 报错**,客户端收到损坏包后忽略 spawn → 入口消失、点击无反应。第一个房间创建时并发 窗口小所以正常;第二个房间创建时第一个房间的世界已加载、区域线程活跃,**必现**。 现已改为**每次调用 `new PacketSetter()` 返回独立实例**(`nms/*/PacketSetter.java`), 彻底线程安全;`EntityID` 惰性字段(`numericID` / `uuid`)同步改为 `volatile` + 同步初始化,保证 spawn 发送与点击匹配永远读到同一个 ID。 - **补给包池编辑器显示内置随机物资**:管理 GUI -> 补给包池 -> 具体档位的菜单左下角 固定展示该档位**原本就有的随机物资**(小型:武器 1级1 件 + 护甲 1级1 件 + 宝石 200; 中型:武器 2级1 + 护甲 2级1 + 消耗品 2级1 + 宝石 500;大型:武器 4级1 + 护甲 3级2 + 消耗品 3级1 + 宝石 1200;豪华:武器 5级1/4级1 + 护甲 5级1/4级1 + 消耗品 4级2 + 宝石 3000),并附说明"该档位开包时除自定义池物品外,必定随机获得以下内容"——编辑 自定义池时不再只能看到自定义物品,内置随机物资一目了然(纯信息展示,点击无操作)。 #### 2.4.0 更新内容(修复 + 新功能) - **2026-09-08 传送门实体 ID 冲突根治 + PAPI 变量扩充 + 聊天系统配置化**: - **竞技场入口消失根治(实体 ID 冲突)**:纯数据包假实体(传送门村民 NPC、全息文字行、排行榜、竞技场/信息公告板)的数字实体 ID 原先从 **1 开始全局自增**,而服务端真实实体 ID 也从 0/1 开始分配——两者**必然冲突**。客户端把假实体当作已存在实体:spawn 包被忽略(入口不显示)、metadata 包作用到错误实体、destroy 包误删真实实体(怪物/村民/玩家)。服务器实体越多冲突越频繁,这正是"入口消失"反复出现且上轮后更严重的根源。现改为从 `Integer.MAX_VALUE - 100_000` **高位段递减**分配:永远正数、进程内唯一、spawn/destroy 同一 ID 命中,且彻底避开真实实体 ID 空间(`nms/common/EntityID.java`)。 - **PAPI 变量大幅扩充(无参数、基于当前玩家)**:新增 `%vd_arena%` / `%vd_arena_id%`、`%vd_room%` / `%vd_room_name%` / `%vd_template_arena%`、`%vd_wave%` / `%vd_max_wave%`、`%vd_gems%` / `%vd_kills%` / `%vd_deaths%` / `%vd_killstreak%`、`%vd_villagers%` / `%vd_enemies%`、`%vd_player_count%` / `%vd_max_players%`、`%vd_status%`(中文状态)、`%vd_in_game%` / `%vd_spectating%`、`%vd_online%` / `%vd_loaded_arenas%`,可直接用于 Tab 栏、聊天格式、计分板、菜单显示。 - **聊天系统配置化(config.yml -> `chat:`)**:频道内消息格式(`channelFormat`)、公共频道格式(`publicFormat`,需 `interceptPublic: true`)、全服广播格式(`globalFormat`)均可自定义,支持内置占位符 `{player}` `{message}` `{channel}` `{prefix}` **并可插入任意 PAPI 变量**(`chat.usePlaceholders`)。默认值 = 原行为,旧 config.yml 自动增量合并缺失 `chat` 段。 - **2026-09-07 回归修复(null 竞技场 / 传送门消失 / 启动告警 / Tab 栏残留)**: - **残留房间段清理扩展到无标记残缺段**:异常退出残留的 `arena.1000002`(房间 Arena id 空间但缺 roomParent 标记)此前不会被 `cleanupStaleSections` 清理,启动校验只打日志不删除,随后被 `GameManager.init` 加载成无世界/无名的 **null 竞技场**。现房间段判定 = 带 roomParent **或 id ≥ 1_000_000**,残缺段同样清理(配置段 + 残留房间世界);init 加载前也跳过房间 id 空间段(双保险),校验遇到无法推断世界的房间空间段直接删除。 - **竞技场批量加载不再被单段中断**:`GameManager.init` 逐段 try-catch,一个损坏段不再导致其后的所有竞技场(含入口传送门)全部不加载 —— 修复"竞技场传送门消失"。 - **世界就绪重试上限 150 → 900 次(约 3 分钟)**:Leaf/Folia 异步加载下若主世界加载较慢,"worlds not loaded: [world]" 不再 30 秒即放弃;每 150 次打印进度,耗尽后明确提示检查 arenaData.yml 的 world 字段(不能指向 vdroom_* 房间副本)。 - **退出后 Tab 栏仍显示旧房间名**:`ArenaListener.onLeave` 的频道清理提前到 `getArena` 之前无条件执行 —— 房间被 endRoom 删除后兜底踢出的玩家(getArena 会抛异常走 return)也会先回公共频道,`%vd_channel%` 不再残留旧房间名。 - **多房间传送门消失(用户反馈 #1:创建第二个房间后入口消失)**:`Arena.refreshPortal` 改为**先构造新传送门、失败则保留旧传送门**(旧逻辑先删后建,构造失败会把入口删没,第二个房间创建触发模板刷新时必现)。 - **VIP 村民出生点可自定义(#2)**:管理 GUI「玩家设置」新增 **VIP出生点** 入口(与玩家出生点同级),支持**设置 / 传送 / 居中 / 删除**(需竞技场关闭);未设置 VIP 出生点时 VIP 村民仍回退到怪物出生点出生。 - **带效果套装死亡复活后效果丢失(#4)**:波末复活循环统一重施自定义套装药水效果(`applyCustomKitEffects`),复活后效果不再消失。 - **排行榜优化(#5)**:空榜显示占位行;前三名按 金/灰/金 着色;超大数值自动格式化为「万/亿」;默认标题不再为空(防空 NPE)。 - **数值溢出修复(#6)**:继续清查 `Tasks`/`Expansion`/`ArenaListener`/`Turret` 等处的 int→long 与钳制,宝石/经验/燃料等持久与局内数值一律防溢出。 - **塔停机后可右键充能(#8)**:重构塔交互——停机塔**空手右键**即可用余额充能,手持绿宝石也可充能;右键**塔底方块**同样生效(兜底点不中隐形盔甲架的问题)。 - **竞技场头顶实时战绩(#9)**:计分板 Team 前缀在每个玩家头顶显示「击杀:x 死亡:y」,随击杀/死亡即时刷新(合并节流),侧栏同步显示双统计。 - **塔消耗金额可视化(#9)**:塔名显示**下一次升级费用**(如 `↑600◆`),升级成功提示附带实际消耗。 - **管理员工具(#7)**:`/vd admintool` 发放「管理员工具」指南针,**右键任意位置即可打开管理员面板**(需 `vd.admin`)。 - **武器词条扩充 22 → 27 条(#10)**:新增 **瘟疫**(中毒 6 秒)、**地狱火**(点燃 5 秒)、**雷暴**(35% 几率闪电附加伤害)、**暗杀**(对低血目标 +150% 伤害)、**血魔**(回复 25% 伤害为生命),config 与内置双份回退。 - **肉鸽卡池扩充 15 → 18 张(#10)**:新增 **工兵特训**(挖掘加速 II+速度 I / 怪攻+20%、移速+10%)、**血色狂热**(力量 I+再生 II / 怪攻+45%)、**深暗守望**(抗性 I+夜视 II+速度 I / 怪生命+30%)。 - **修复"一个房间在对局中时创建新房间,两人串进同一房间、对局被强制停止,退出提示'你不在任何游戏中'" + 控制台刷 `MonsterAI` 距离计算异常**:根因是**房间世界在游戏进行中被卸载/重载,产生同名双 World 实例**(Leaf/Folia 下旧 World 对象仍被缓存的 Location/实体引用): - `MonsterAI.tickMonster` 玩家仇恨循环无世界校验,怪物(旧实例)对玩家(新实例)执行 `distanceSquared` → 每秒刷 `IllegalArgumentException: Cannot measure distance between minecraft:vdroom_0_1 and minecraft:vdroom_0_1`,实体级 AI 任务反复死亡; - `Arena.calibrate` 用缓存的**旧 World 实例**扫描实体 → 扫到 0 村民 → 误判"村民全灭"触发 `GameEndEvent` → 14 秒后 `endRoom` 卸载房间世界 → 玩家被强制移出、房间删除 → "未在游戏中"。 **修复**:`MonsterAI` 玩家仇恨循环/村民过滤/巡逻点/目标判断全部改为**世界实例引用比较**(不同实例直接跳过,不再测距);`Tasks.monsterTargeting` 同步改引用比较;`Arena.calibrate` 增加**世界实例一致性防护**(spawn 世界不是当前注册实例时重新解析并跳过本轮校准),从根上杜绝"旧实例扫描→误判结束→卸载房间"链条。 - **修复房间游戏开始失败(开局死档 / 退出提示"你不在任何游戏中")**:控制台报 `Arena 图书馆-1 failed to start: For input string: "a12"`。根因:商店物品 lore 行序被破坏——补给包池行(`&7补给包池: &a12`)与召唤数量行被**追加到价格行之后**,而商店 `sort()`/`modifyPrice()`/购买解析都假定**最后一行=价格行**并 `substring(6+gems.length())` 解析,从补给包池行截出 `"a12"` → 数字格式异常,开局建商店即崩。已修复:补给包池行/召唤数量行改为**插入到价格行之前**(价格行永远最后一行),`modifyPrice`/`sort` 改为**按格式查找价格行**(不再盲信最后一行),购买解析补 try/catch 防御;`Tasks.start` 失败时输出**完整堆栈**便于定位。 - **修复房间创建后是全新世界 / 没复制竞技场模板世界("只是创建了一个新世界"、"Loading 0 persistent chunks")**:房间世界文件夹位置**完全由 Leaf 决定,绝不猜测**。新流程为 **探测→卸载→复制→重载**:先让 Leaf 创建一次空房间世界拿到真实文件夹 → 卸载并清空其生成的空区块 → 把**模板世界根文件夹**整体 1:1 复制进去(不猜目录层数,跳过身份/运行时文件,房间保留 Leaf 生成的唯一 UUID)→ 复制后校验 `.mca` 区块数(为 0 拒绝创建并输出诊断)→ 重新加载,Leaf 从自己决定的文件夹必然读到模板区块。Multiverse 克隆已彻底移除;不再强制 `templateWorld.save()`、复制跳过模板 `data/`,消除 Windows 文件锁;全程输出级别 0 的必打路径日志便于核对。 - **修复 "Arena 0 world vdroom_0_1 not loaded yet" / "模板房间未加载,房间创建失败"**:若曾在**房间世界**里给模板竞技场设过点位,点位与 `world` 字段会被写成房间世界名,重启后插件永远等待一个不会加载的房间世界。现已启动自愈(识别 `vdroom_*` 房间世界名 → 自动清除并改回真实模板世界)、房间世界名不再阻塞初始化、并**源头拦截**(模板竞技场点位禁止写入房间世界,杜绝再犯);创建房间失败时控制台输出完整原因。 - **修复 Multiverse 控制台刷 "not a multiverse world" 报错**:删除房间时盲目执行 `mv unload`/`mv remove`,但房间世界由内置复制创建、从未注册进 Multiverse。现已改为:先通过 Multiverse API(反射 `isMVWorld`)**确认世界确实在 MV 注册表里**才执行 `mv remove`;不在注册表则直接手动删文件夹,绝不刷报错。模板世界不在 MV 注册表时克隆同样回退内置复制。 - **房间世界名与模板世界澄清**:模板世界名始终读取 `arenaData.yml` 的 `arena.{id}.world` 字段;`vdroom_{模板id}_{序号}` 只是房间副本的内部名称(克隆目标,避免与模板重名),不是模板世界名。设定出生点 / 导入 WE 选区时若竞技场缺 `world` 字段,**自动把该点所在世界写回 `world` 字段**(历史配置即时迁移)。 - **修复 WorldEdit 选区导入世界不一致**:导入前校验玩家当前世界与竞技场模板世界一致,不一致给出明确中文提示;竞技场缺 world 字段时自动以选区世界补写,杜绝 corner 世界与出生点世界不一致导致的选区/边界错乱。 - **修复商店位置居中后出现多个重叠**:`refreshShopSpawns` 先清列表再关粒子的顺序颠倒,导致旧全息标识永不关闭、每刷新叠一层。已改为先清理旧标识再重建点位。 - **出生点标识汉化**:玩家出生点 / 怪物出生点(空中/地面/全部)/ 村民出生点 / 商店位置 / VIP 出生点。 - **修复"重启后所有点位需要重新设定"**:Leaf/Folia 下世界异步加载,插件启动时主世界 `world` 可能还没加载完成,`GameManager.init` 把点位全读成 null;而点位刷新守卫会在世界加载后重跑 init 时**直接跳过重读**,点位永远丢失。现已改为:点位为 null / 列表为空时允许重读、收集未加载世界前先清理残留房间段并跳过房间段、新增**启动周期重试**(世界就绪后自动重新初始化)、老竞技场缺 `world` 字段时自动从出生点反推并写回(历史配置迁移)。 - **修复"第二次创建房间地图变成全新世界"**:结束房间后没有用 Multiverse 的 `mv remove` 清理世界注册表,旧的 `vdroom_*` 世界残留在 Multiverse `worlds.yml` 中,下次克隆同名世界失败后插件回退 `createWorld` 随机生成新世界。现已改为:删除房间时优先 `mv remove`(清注册表+删文件)、创建前清理同名残留、启动清理残留配置段时同步清 Multiverse 注册表。 - **竞技场配置导出/导入工具**:所有竞技场点位默认集中在 `arenaData.yml`,现可拆分为独立文件(`plugins/PraxVillageDefense/arenas/`),便于备份/迁移地图到另一台服务器: - `/vd admin arena export [地图名]` 导出单个竞技场;`/vd admin arena exportall` 一键拆分全部; - `/vd admin arena import-<文件名> [new]` 导入(默认沿用文件内 id,`new` 强分新 id); - 导出含点位/设置/records/自定义商店,迁移的是完整竞技场;导入后默认关闭/编辑态。 - **房间热度/统计面板**:局末自动记录每张地图的开局数、通关数、通关率、历史最高波次、累计击杀、最近游玩(`arenaData.yml` 的 `arena.{id}.stats`);竞技场信息菜单显示"⚡ 游玩统计"(烈焰棒),房间选择界面显示"⚡ 地图热度"。 #### 2.3.9 更新内容(修复) - **修复服务器重启后所有点位/配置丢失**:`DataManager` 异步保存存在竞态——连续设置多个点位会排起多个并发异步写盘任务,先提交的旧快照可能最后落盘覆盖新数据,磁盘上的 `arenaData.yml` 停留在早期状态,重启后所有点位需重新设定。现已改为**串行化合并写盘**:所有并发调用共享一个写盘循环、每次落盘前读取最新快照,旧快照永不覆盖新数据;关服同步兜底保存也增加"等待进行中的异步写盘结束",杜绝关服瞬间被旧快照覆盖。 #### 2.3.8 更新内容(修复与改进) - **修复房间创建后玩家加入时 `NullPointerException: arena.getPlayerSpawn() is null`**:根本原因包含配置复制全局替换破坏、Multiverse 异步克隆未等待、出生点未兜底三类。现已全面修复: - `writeRoomConfig` 仅当值恰好等于模板世界名时才替换,不再误伤其他字符串(如 `"village"` 不会被单字符 `"v"` 替换成乱码)。 - 房间创建收尾改为**轮询等待**(最多 15 秒)直到 Multiverse 克隆的世界注册到 Bukkit,避免抢跑创建空世界。 - 若房间 Arena 出生点仍为 null,则从模板复制出生点坐标并设置为房间世界,强制保证可用。 - **编译/导入修复**:`RoomManager` 补上 `Location` 导入,调用点同步适配新参数。 #### 2.3.7 更新内容(修复) - **修复 "Target world cannot be null" 传送崩溃**:配置的位置所属世界未加载/世界名不匹配时,`player.teleport` 不再抛异常崩溃事件。根因修复:`getConfigLocation` 在世界解析不到时返回 null(不再返回 world 为 null 的位置),所有位置判空检查一次生效;新增 `PlayerManager.safeTeleport` 双保险;传送相关的 `catch (NullPointerException)` 全部升级为 `catch (Exception)`;等待室/角1/角2 传送补世界判空。 ### 新增玩法机制(26.1.2) 以下机制不新增配置项,行为固定在代码中(部分受上表既有配置控制): #### 全员村民仇恨 - 所有竞技场怪物都会主动攻击村民,包括:原版怪物、MythicMobs 精英/Boss、以及词缀「召唤」产生的仆从。 - 机制:每只怪物生成时注册实体级 AI 任务(`MonsterAI.attachEntityAITask`),在怪物自身区域线程上周期性执行 `MonsterAI.tickMonster`;村民优先模式(`monsterAI.prioritizeVillagers: true`)下怪物会优先追击最近村民,仅玩家进入 5 格内才会临时转移仇恨。 - `monsterAI.enabled` 默认值为 `true`(缺省配置时也默认开启)。 #### 幻翼固定伤害 - `Mobs.setPhantom` 将幻翼攻击力基准值覆写为 2(原版为 6);波次/难度加成仍作为属性修饰符叠加。 #### 宠物击杀奖励归属 - 玩家召唤的狼(`Wolf`)或铁傀儡(`IronGolem`)击杀怪物时,绿宝石奖励、击杀计数、经验、击杀连击均直接结算给**召唤者**(而非宠物)。 - 实现:铁傀儡生成时记录 `VD_OWNER` 元数据(召唤者 UUID);狼通过原版 `getOwner()` 获取主人。宠物击杀统计在 `GameListener.onMobKillByPlayer` 中按召唤者结算。 - 注意:铁傀儡生成 API 签名已改为 `Mobs.setGolem(Main plugin, Arena arena, Player owner, IronGolem golem)`。 #### 中途入场补偿 - 游戏进行中(允许迟到加入的竞技场)加入的非回归玩家,在入场时获得**场上所有其他玩家当前绿宝石余额平均值的 75%**(`int` 向下取整,余额为 0 时不发放)。比例由 `economy.lateJoinGemPercent` 配置(默认 0.75,0 = 关闭,1 = 与原平均值相同)。 - 「回归玩家」(掉线保护生效、回到同一竞技场)**不获得**该补偿,改为恢复掉线前保存的绿宝石余额与全部物品(`disconnectProtection` 配置,见上)。 #### 入场选择套装 - 中途加入的非回归玩家进入竞技场后会**先打开套装选择菜单**,选择(或直接关闭)后自动发放该局装备(`Tasks.giveItems`)。 - 回归玩家跳过选择,直接恢复掉线前的装备。 - 实现:加入时给玩家打上 `VD_PENDING_LOADOUT` 元数据并打开 `SELECT_KITS_MENU`;`InventoryListener.onClose` 在菜单关闭时检测该标记并发放装备。 #### 难度水晶加成 - 游戏结束(胜利/失败)结算水晶时,按竞技场难度标签额外加成:每级难度 +10%,四档难度对应: | 难度标签 | 难度倍率 | 水晶加成 | |----------|---------|---------| | Easy(简单) | 1 | +10% | | Medium(普通) | 2 | +20% | | Hard(困难) | 3 | +30% | | Insane(噩梦) | 4 | +40% | - 加成基数 = 基础结算水晶(波次 + 击杀 + 绿宝石折算),与挑战加成(`bonus`)独立计算、一并发放,通知文案显示 `基础 (+挑战加成+难度加成)`。 #### 假死防护 - 修复「部分生物血量归零后进入假死状态不断抽搐、无法强制杀死」的问题,三层防护: 1. 死亡事件(`EntityDeathEvent`)处理器整体 try/catch 兜底,任何异常都不会中断实体正常死亡移除流程; 2. 受伤结算时若基础血量将被打到 0,立即解除吸收值(护盾/悬赏护盾),保证致死一击真正致死; 3. `MonsterAI.tickMonster` / `MonsterAffix.tickAffix` 检测到血量 ≤ 0 的存活实体时,解除吸收并补上致死伤害(不再被「护盾」「再生」词缀复活)。 - 后果:精英怪(含护盾/悬赏词缀)的致死一击不再被吸收值"无限拖延",战斗手感更稳定。 #### 村民假死防护 - 同样的「倒地抽搐、无法死亡」问题也会发生在村民身上。`Mobs.setVillager` 现在为每名村民注册实体级假死守护任务(每 20 tick 检查一次):一旦检测到血量 ≤ 0 却仍存活的村民,先解除吸收值并补上致死伤害,兜底直接 `remove()`,保证村民一定能被正常移除(死亡结算、绝望/民心惩罚不再卡住)。 #### 召唤物伤害规则修复 - 修复「部分召唤物被怪物攻击后不掉血」:`GameListener.onSummonEnvironmentDamage` 之前把**所有** `EntityDamageEvent`(含实体攻击子类)一律取消,导致怪物近战/弹射物伤害也被误拦截。 - 现在环境伤害处理器只处理环境来源(坠落/岩浆/火焰/溺水/窒息/仙人掌/雷击/中毒/凋零等);`EntityDamageByEntityEvent` 交给 `onFriendlyFire` 判定,怪物及怪物弹射物可正常伤害召唤物,玩家/友好误伤仍被拦截。 #### QA 枪械无伤刷钱防护 - 修复「QA 枪械攻击蜜蜂(召唤物)无伤害却刷钱」:`GameListener.onMobKillByPlayer` 现在(1)在结算击杀奖励前检查事件是否已被取消(召唤物保护会取消该伤害);(2)将村民与全部玩家召唤物(狼/铁傀儡/雪傀儡/蜜蜂/猫)排除出「可击杀怪物」判定,杜绝把召唤物当怪物结算绿宝石/击杀数。 #### 村民等级显示修复 - 修复村民升级后名字累积显示「Lv2 Lv3 Lv4 Lv5」:`Mobs.withGrowthTag` / `withCuriousTag` 在追加标签前先剥离旧有的同类标签(等级标签 /【好奇】标签与好感爱心),做到幂等,波次升级与喂食好感不再叠加重复标签。 #### 怪物词缀致盲时长上限 - 致盲词缀(`blind`)造成的失明时长改为 `affixes.blindDurationTicks` 配置(默认 50 tick = 2.5 秒),并硬性上限 200 tick(10 秒),避免数值异常导致失明「永久保留」。 #### 禁用合成表 - 新增 `CraftingListener`:竞技场内玩家(等待/进行中)禁止合成任何物品(工作台与背包合成均拦截),防止绕过绿宝石经济与套装系统合成非法物品。由 `disableCrafting.enabled`(默认 `true`)控制,仅作用于竞技场内玩家。 #### 护送波(VIP 村民)重做 - VIP 村民现在会**高亮村庄中心点**(粒子光束提示,`objectiveWaves.escortHighlightCenter`,默认开启)。 - VIP 村民会**指定锁定一名活跃玩家作为护送者**并单独提示该玩家;其余玩家收到含护送者名字的提示。 - VIP 村民会**一直跟随该护送者**(护送者离线/离开时退回朝村庄中心直行),直到被带到村庄中心后**消失**并结算全队奖励。到达判定半径由 `objectiveWaves.escortArrivalRadius` 配置(默认 3 格)。 ### `custom_kits.yml` 自定义套装数据文件(自动创建,无需手动编辑): - 路径:`plugins/VillagerDefense/custom_kits.yml` - 通过管理员 GUI 自动管理 - 所有 ItemStack 数据使用 Base64 编码 - 每个套装包含 `price` 字段(水晶价格,0 = 免费) ### `playerData.yml` 玩家数据文件: - `selectedKit` 键存储玩家默认套装名(通过 `/vd defaultkit` 自动管理) - `crystalBalance` 键存储玩家水晶余额(通过 `/vd crystals` / `/vd sign` / 购买自定义套装管理) - `lastSignIn` 键存储最近签到日期(`yyyy-MM-dd`,通过 `/vd sign` 管理) - `points` 键存储玩家积分(通过击杀怪物获得,`/vd shop` 消费) - `dailyGift` 键存储最近领取每日礼包日期(`yyyy-MM-dd`,通过 `/kit daily` 管理) - `customKits.<id>` 键存储已解锁的自定义套装(购买 / 礼包 / 成就 / 积分商店都会写入) ```yaml <player-uuid>: selectedKit: "Mage" # 默认套装名 crystalBalance: 500 # 水晶余额 lastSignIn: "2026-08-04" # 最近签到日期 points: 120 # 积分余额(/vd shop 消费) dailyGift: "2026-08-12" # 最近领取每日礼包日期 kits: ... customKits: my_custom_kit: true ``` ### `giftcodes.yml` 礼包码数据文件(自动创建,通过主菜单 → 礼包设置 管理): ```yaml codes: <code>: # 礼包码(大写) diamonds: 100 # 赠送钻石 kit: "精英战士" # 赠送套装名(原版或自定义,含未开放;可空) maxUses: -1 # 总使用次数(-1 = 无限) perPlayer: 1 # 每人限用次数 uses: 0 # 已使用次数 expires: 0 # 过期时间戳(毫秒,0 = 永久) broadcast: false # 领取时全服公告 daily: false # 每日礼包标记(启用后 /kit daily 可领取) ``` - 内置 `DAILY` 码用于 `/kit daily`:玩家每天可领取一次(按玩家记录,不占用总次数) ### `pointsShop.yml` 积分商店兑换项数据文件(自动创建,通过主菜单 → 积分商店设置 管理): ```yaml items: <id>: type: "diamond" # diamond / kit / item value: 10 # 数量(diamond/item)或套装名(kit) cost: 10 # 所需积分 material: "DIAMOND" # item 类型的 Material 名称 / kit 类型的套装名 ``` --- ## 11. 构建指南 ### 环境要求 - Java 25+(`maven.compiler.source/target = 25`) - Maven 3.6+ ### 构建命令 ```bash # 完整构建所有模块 mvn clean install # 仅构建插件模块 mvn clean install -pl plugin # 快速编译(不运行测试) mvn compile -pl plugin -q # 打包完整插件 JAR mvn clean package -pl plugin ``` ### 输出 JAR 构建输出位于 `plugin/target/VillagerDefense-2.2.0.jar` ### NMS 模块说明 插件支持 Minecraft 1.21.x 与 26.x 全版本,使用多 NMS 子模块(打包时全部 shade 进单一 JAR,运行时按服务端版本反射加载): - 1.21.1 → `nms/v1_21_r1`、1.21.2/3 → `nms/v1_21_r2`、1.21.4 → `nms/v1_21_r3` - 1.21.5/6 → `nms/v1_21_r4`、1.21.7/8 → `nms/v1_21_r5` - 26.x → `nms/v1_26_r1`(纯 Mojmap,无版本包结构,适配 `ClientboundAddEntityPacket` 与 `Vec3.LP_STREAM_CODEC`) - 版本选择逻辑位于 `NMSVersion.java`,通过 `Bukkit.getBukkitVersion()` 解析并映射到对应 NMS 管理器 --- ## 12. 开发者扩展指南 ### 包路径 - 全部 Java 包已迁移至 `me.prax.villagerdefense.*`,包括 `common`、`nms.common`、全部版本 NMS 模块和 `plugin`。 - `plugin.yml` 主类为 `me.prax.villagerdefense.plugin.Main`;外部 API 集成需要同步更新 import。 - 旧 `me.theguyhere.villagerdefense.*` 路径在 2.2.0 起彻底移除。 ### 难度 1 平衡精修 - 难度 1 的整体系统缩放从 `0.60` 调整为 `0.50`。 - 每波生成数量、怪物属性成长、词缀概率和村庄绝望惩罚共享该缩放,降低新手前期怪物密度与隐藏难度。 - 难度 2/3/4 的倍率保持不变,避免影响普通、困难和疯狂模式的既有节奏。 ### 开发者配置说明 - WorldEdit 为可选软依赖;管理员可在边界菜单点击「WorldEdit 选区」导入当前选区。 - `turrets.claimSeconds` 与 `turrets.claimRadius` 控制塔位争夺保护窗口。 - `shopPrices.waveBoostEnabled` 控制随波次增长的商店价格加成。 - 管理员语言资源对旧版 `no / none / on / off / yes` 键提供默认回退;长期统计值(声望、水晶、礼包奖励)使用 `long` 并带上限保护,避免数据溢出。 - `VDPlayer.getPlayer()` 对离线玩家安全返回 `null`,调用方应先检查在线状态。 - 自定义套装持久化于插件数据目录的 `custom_kits.yml`,启动时自动加载。 ### 添加新的套装类型 ```java // 1. 在 Kit.java 中添加静态工厂方法 public static Kit myNewKit() { // 遵循现有 Kit 模式 } // 2. 在 LanguageManager.Kits 中注册语言字段 // 3. 在 en_US.yml / zh_CN.yml 中添加语言字符串 // 4. 在 Inventories.createSelectKitsMenu() 中添加 GUI 按钮 ``` ### 集成自定义商店物品 ```java // 使用 ItemManager 创建物品 ItemStack myItem = ItemManager.createItem( Material.DIAMOND_SWORD, "&bMy Custom Sword", "&7A custom sword for VD" ); // 在 Inventories.createCustomShopMenu() 中添加 ``` ### 扩展自定义套装系统 ```java // 通过 API 创建自定义套装 CustomKit kit = new CustomKit("Special Kit", new ItemStack(Material.DIAMOND_CHESTPLATE)); kit.setItem(0, new ItemStack(Material.DIAMOND_SWORD)); // 热键栏槽 0 kit.setItem(1, new ItemStack(Material.COOKED_BEEF, 16)); // 热键栏槽 1 kit.setDescription(Arrays.asList("Line 1", "Line 2")); kit.setEnabled(true); CustomKitManager.addKit(kit); CustomKitManager.save(); ``` ### 注册新的 ChestCommands 动作 ```java // 在 ChestCommandsHook.executeAction() 中添加新 case case "myaction": // 执行自定义逻辑 break; ``` ### 事件监听 插件使用 Bukkit 事件系统。监听自定义事件: ```java @EventHandler public void onJoin(JoinArenaEvent event) { Arena arena = event.getArena(); Player player = event.getPlayer(); // 自定义逻辑 } ``` 可用的事件: - `JoinArenaEvent` — 玩家加入竞技场 - `LeaveArenaEvent` — 玩家离开竞技场 - `GameEndEvent` — 游戏结束 - `WaveEndEvent` — 波次结束 - `StartGameEvent` — 游戏开始 --- *文档版本: 2.2.0* *最后更新: 2026-08-26* [/MD]
  • 喜欢
反馈: PythonsFruit
作者
Xiaochengsweet233
查看
347
首次发布
最后更新

评分

0.00 星 0 次评分

最新更新

  1. 2026-09-19 2.4.0&fix3.0 bug修复

    陈·村庄保卫战 v2.4.0 fix3.0 问题修复更新 修复了排行榜相关信息出错的问题 修复了竞技场的入口在交互后消失一段时间的问题 优化了一部分逻辑
  2. 2026-09-14 2.4.0&fix2.0 bug修复

    陈·村庄保卫战 v2.4.0 fix2.0 问题修复更新 修复了进机场入口定点出现问题导致“消失”的问题
  3. 2026-09-14 2.4.0&fix1.0 bug修复

    陈·村庄保卫战 v2.4.0 fix1.0 问题修复更新 · 修复了多次进行操作导致入口消失的bug · 增加了管理员工具方便配置
后退
顶部 底部