Suite

地牢模板编写

abysses/<地牢ID>/ 下各 YAML 的字段规范

地牢模板编写

每个地牢是 plugins/ArcartX-Suite/data/abyss/abysses/<地牢ID>/ 下的一个目录,目录名即地牢 ID(建议小写英文+下划线)。模块首次启动会自动导出 abysses/example/ 示例地牢——直接复制改配置即可得到新副本。

目录结构

abysses/<地牢ID>/
├── config.yml       ← 地牢基本信息(必需)
├── monsters.yml     ← 怪物组/波次
├── scripts.yml      ← 生命周期脚本
├── tasks.yml        ← 副本内定时/事件任务
├── interacts.yml    ← 交互点
├── obstacles.yml    ← 障碍物
├── npcs.yml         ← 副本 NPC
├── quests.yml       ← 副本任务
├── rewards.yml      ← 奖励集/宝箱
├── shops.yml        ← 商店(open_shop_ui 调用)
├── floors.yml       ← 楼层定义(mode: floors 多层房间地牢)
├── rooms.yml        ← 房间模板池(floors 模式)
├── procgen.yml      ← 随机拼装地牢配置(存在即启用 ProcGen 模式)
├── regions/         ← 区域模板(/axs abyss region save 产出,paste_region 调用)
├── external/        ← 外部系统配套文件(可选;abyss 加载模板时自动部署)
│   ├── chemdah/conversation/*.yml   → plugins/Chemdah/core/conversation/
│   ├── chemdah/quest/*.yml          → plugins/Chemdah/core/quest/
│   └── questgps/*.yml               → plugins/ArcartXSuite/data/questgps/quests/
└── world/           ← 世界文件夹(可选,含 level.dat)

除 config.yml 外所有文件均可省略,缺失时使用空配置。external/ 是「这个副本需要哪些 Chemdah 会话/任务、questgps 呈现层」的随包清单——abyss 在模板加载(启动//axs abyss reload)时自动部署:目标插件缺席整组跳过、目标文件已存在不覆盖,写入后自动 chemdah reload 并重载 questgps 模块。目录结构与上表一致即可被部署器识别。

config.yml — 地牢主配置

name: "&6虚空试炼场"           # 显示名(支持 & 颜色码)
icon: "END_STONE"            # 浏览器卡片图标(Material 名)
difficulty: "简单"            # 难度标签(显示用)
description:                  # 描述(列表,逐行显示在详情面板)
  - "&7在虚空平台上击退两波怪物"
 
world:
  template: "void"           # 世界来源,见下表
  spawn: "0,80,0,90,0"       # 出生点 x,y,z,yaw,pitch(副本世界坐标)
  # 世界治理(可选):gamerule 覆盖 / 自然刷怪控制 / 副本级命令控制
  gamerules:                 # Bukkit GameRule 名 → 值(布尔或整数),叠加在模块默认规则之上
    keepInventory: true
    mobGriefing: false
  disable-natural-spawn: true        # 禁环境驱动刷怪(脚本刷怪走 CUSTOM 不受影响)
  disabled-spawn-reasons: []         # 额外禁用的 SpawnReason 名(可含 CUSTOM)
  command-control:                   # 副本级命令控制(覆盖全局 restrictions.command-mode)
    mode: "inherit"                  # inherit=沿用全局 / blacklist / whitelist
    commands: []                     # mode 非 inherit 时的黑/白名单(按主命令匹配)
 
time-limit: 600              # 限时(秒),超时触发 timeout-action
timeout-action: "FAIL"       # FAIL=判负 / KICK=踢出
max-instances: 0             # 该地牢最大并行实例数,0=不限
# cross-server: "dungeon-01"   # 可选:声明远程宿主节点(跨服副本,见 procgen 页「跨服副本」节)
 
requirements:
  min-players: 1             # 最小进本人数
  max-players: 4             # 最大进本人数
  party-required: false      # 是否必须组队
  party-mode: ""             # 要求队伍模式(空=不限;"guild"=仅公会模式队伍,自由组队被拒)
  min-level: 0               # 最低玩家等级(0=不限)
  max-level: 0               # 最高等级(0=不限)
  cooldown: 300              # 通关冷却(秒),按玩家+地牢独立计时
  money-cost: 0              # 货币消耗
  currency-id: "money"       # 货币 ID(宿主 currencies 定义)
  stamina-cost: 10           # 体力消耗
  required-items: []         # 进本消耗物品列表
  required-permissions: []   # 需要的权限节点
  conditions: []             # PAPI 条件表达式(如 "%player_level% >= 10")
  require-all-players: true  # false=物品/货币/权限/条件只校验队长(队长买单全队进)
  leader-conditions: []      # 仅队长需要满足的额外条件(PAPI 表达式)
  disabled-commands: [spawn] # 本副本额外禁用命令(绝对黑名单,叠加于命令控制之上)
  disabled-items: []         # 本副本额外禁用物品
 
links:                       # 跨模块联动(全部可选,模块缺席自动跳过)
  entrance: "world;100,64,-50"          # 地牢入口地图标记 "世界;x,y,z"(map 模块紫色外部目标+内置导航)
  announce-clear: "&6{party} 通关 {dungeon}&6,用时 &f{time}&6!"  # 通关全服播报(announcer 模块)
  announce-forward: false               # 播报是否跨服转发
  qq-notify-clear: false                # 通关推 QQ 群(qqbot 模块)
  qq-group: 0                           # 指定群号,<=0=全部已配置群
 
# 作战方案:进本前选择,影响本局怪组/脚本分支(详见「plans 作战方案」节)
plans:
  strong:
    name: "&c强攻·正面突破"
    lore: ["怪群密集,需火力压制", "掉落倾向:弹药/武器件"]
  sneak:
    name: "&b潜入·暗杀优先"
    lore: ["敌人分散警觉,需潜行", "掉落倾向:稀有材料/情报"]
 
# 世界入口点:主世界/场景里的物理入口,右键触发进本(详见「entrances 世界入口点」节)
entrances:
  - world: world                 # 入口所在世界(非副本世界)
    location: "100,64,-50"       # 方块坐标(与 region 二选一,可都填)
    name: "正门"
  - world: world
    region: "90,60,-60;95,70,-55"  # 立方区域(区域内任意方块右键都触发)
    plan: strong                 # 固定方案(可选;不配且 plans>1 → 弹方案选择菜单)
    name: "侧墙裂缝"

声明式联动——不写脚本即可接其他模块。announce-clear 支持占位 {party}(队伍名)、{dungeon}(地牢显示名)、{time}(用时)。如需更细粒度(首通播报、成员名单、自定义条件),在 scripts.yml 生命周期脚本里用 signal/announce_global/qq_group/bp_task 等联动动作(见 脚本系统)。

entrance 推送为地图紫色外部目标,玩家可在地图面板点"导航",由地图内置导航栈寻路(不占玩家路径点上限);入口指向主城等固定世界坐标,不要指向副本世界内部。

地图标记 vs 物理入口:links.entrance 只是地图上的导航标记(指路用);真正右键进本要配下面的 entrances 世界入口点。两者通常指向同一位置。

plans 作战方案

进本前选择的作战方案(如"强攻/潜入/破局"),影响本局怪组与脚本分支,不影响剧情走向。

  • 定义:plans 节每个 key 是方案 id,name 显示名(支持 &)、lore 描述行(菜单展示用)
  • 选择入口(三处都生效):
    • 命令:/abyss enter <地牢> <方案id>
    • 入口点 plan: 字段固定方案(适合"不同物理入口=不同打法")
    • 入口点不配 plan 且方案多于一个时,点击者收到可点击的方案选择菜单(聊天栏,点击自动执行进本命令)
  • 不选方案:模板定义了 plans 但没指定时,默认取第一个方案
  • 实例变量:进本后 {plan}=方案 id、{plan_name}=显示名,可在脚本/消息文本中引用
  • 事件:axs.abyss.enter / axs.abyss.player_enter 的 payload 附带 plan

方案怎么影响玩法(两条路,可混用):

  1. 怪组过滤——monsters.yml 和 rooms.yml 的怪物组加 plans: [方案id] 字段后,只有该方案进本才注册此组。不同方案刷完全不同的怪群:
    wave_assault:              # 只在"强攻"方案出现
      plans: [strong]
      spawn-timing: AUTO_START
      monsters: [...]
  2. 脚本分支——{plan} 变量配合 check 动作分流奖励/机制:
    on-end:
      - "check @trigger var:plan == strong"      # 仅强攻方案继续往下走
      - "grant_reward @trigger ammo_cache"        # 强攻掉弹药箱

entrances 世界入口点

主世界/场景里的物理进本点——玩家右键登记的方块(或区域内任意方块)触发进本,命中后交互事件自动取消(门/箱子不会真的打开)。

  • world:入口所在世界名(主世界场景,不是副本世界——副本世界名带 axs_abyss_ 前缀且是实例世界)
  • location:单点 "x,y,z";region:立方区域 "x1,y1,z1;x2,y2,z2"——两者可都填,同一入口多点命中
  • plan:可选,固定方案 id(对应 plans 键名);不配且模板多方案 → 弹方案菜单
  • name:可选显示名
  • 队伍带入:点击者是队长且有队员 → 自动带全队进本(沿用进本条件校验:体力/冷却/道具按 require-all-players 规则判定)
  • 进本失败(条件不满足/冷却中/实例满等)会给点击者发失败原因
  • 与 links.entrance 地图标记独立,建议配合使用:地图指路 + 到点右键进本

world.template(世界来源)

值说明
void生成虚空世界(全空气),用 init 脚本现场搭场景。零依赖最简方案
目录名abysses/<地牢ID>/world/ 下的完整世界存档文件夹(需含 level.dat),逐实例复制
maps/<名> 或 .schemWorldEdit/FAWE schematic,粘贴到虚空世界。需安装 WorldEdit 或 FAWE

world 世界治理

  • gamerules:按副本覆盖游戏规则(如 keepInventory、randomTickSpeed),未知规则名自动跳过;模块默认已内置一套副本规则(关日夜/天气/火焰蔓延/自然刷怪、keepInventory=true 等)
  • disable-natural-spawn:拦截自然/刷怪笼/巡逻/袭击等环境驱动生成;副本脚本刷怪走 CUSTOM 来源不受影响
  • disabled-spawn-reasons:按 CreatureSpawnEvent.SpawnReason 名精确禁用(含 CUSTOM,可彻底锁死生成)
  • command-control:副本级命令黑/白名单,mode: inherit 时沿用全局 restrictions.command-mode;whitelist 适合 PVP/硬核副本「只许用这几个命令」

monsters.yml — 怪物组/波次

顶层每个 key 是一个怪物组 ID,组内 monsters 为刷怪点列表:

wave_1:
  spawn-timing: AUTO_START            # 触发方式,见下表
  monsters:
    - mob-id: "vanilla:ZOMBIE"        # vanilla: 强制原版 / 无前缀走 MythicMobs
      location: "3,80,3"              # x,y,z
      amount: 3
  complete-when-alive-below: 0        # 存活数低于此值时组完成
  plans: []                           # 限定作战方案 id 列表(空=所有方案都刷;见 plans 作战方案)
  on-start:                           # 组开始时执行的脚本动作
    - "message @all &e第一波来袭!"
  on-end:
    - "message @all &a第一波已清剿!"
 
wave_2:
  spawn-timing: TRIGGERED             # 由 trigger-after 组清完自动触发(无需手写 spawn_group)
  trigger-after: ["wave_1"]
  delay-seconds: 6                    # 清完休整 6 秒再刷
  on-prespawn:                        # 预告段:触发后、休整期内立即跑
    - "title @all 5 40 5 &c第二波 &76 秒后来袭!"
    - "sound @all ENTITY_RAVAGER_ROAR 1 0.8"
  monsters:
    - mob-id: "vanilla:SKELETON"
      location: "4,80,0"
      amount: 2
spawn-timing触发时机
AUTO_STARTmonsters.yml 全局组=start 即刷;floors 房间内组=锁门即刷(delay-seconds 起延迟)
DELAYED全局组=进本延迟刷;房间内组=锁门 delay-seconds 后刷
TRIGGEREDtrigger-after 列出的组全部清完后自动触发(房间内同样生效)
MANUAL / SCRIPTED仅脚本动作 spawn_group 触发(锁门不刷——随机波次、条件波次用)

房间清判:combat/boss 房要求 groups 列出的全部组进入 COMPLETED。 MANUAL 组若本轮不刷,须 kill_group 跳过(PENDING 组被 kill 视同完成,照走完成链); remove_group 是静默移除,不会计入完成,慎用。

不要在 on-end 里手动 spawn_group——trigger-after 已自动触发,手写会双通道刷两遍。

mob-id 路由规则:vanilla: 前缀强制原版;其余尝试 MythicMobs,加载失败自动回退原版同类型实体。

波次休整(delay-seconds / rest-seconds / on-prespawn)

TRIGGERED 组前置清完后不立即刷,按休整规则延迟:

  • delay-seconds:组级覆盖——触发后延迟 N 秒刷(对 TRIGGERED 也生效)
  • rest-seconds:组级兜底——delay-seconds<=0 时用;floors 房间还可在房间级写 wave-rest 作为整房组的兜底
  • on-prespawn:预告段——组被触发、休整期开始瞬间执行(写「下一波来袭」title/音效/actionbar,给玩家喘息提示)

优先级:delay-seconds > 组 rest-seconds > 房间 wave-rest;三者都 ≤0 时照旧立即刷(向后兼容)。

tasks.yml — 副本内任务

tips:
  type: TIMED                        # 指定秒数触发
  times: [60, 300, 540]
  timed-scripts:
    60: ["message @all &7已进行 1 分钟"]
    300: ["message @all &e时间过半!"]
 
supply:
  type: INTERVAL                     # 循环任务
  interval: 120                      # 每 120 秒
  script:
    - "message @all &7补给已投放"
 
on_kill:
  type: EVENT                        # 事件任务
  event: monster_kill                # 怪物被击杀
  script:
    - "setvar last_killer {killer}"
type触发方式
TIMED副本进行到 times 列出的秒数时执行对应 timed-scripts
INTERVAL每 interval 秒循环执行 script
EVENT指定事件发生时执行(如 monster_kill,上下文含 {killer})

interacts.yml — 交互点

trial_chest:
  type: RIGHT_CLICK_BLOCK            # 见下表
  location: "5,80,5"                 # 方块坐标(方块型用)
  region: ["0,60,0", "20,80,20"]     # 区域对角(区域型用)
  entity-name: "宝箱守卫"             # 实体名(RIGHT_CLICK_ENTITY 用)
  required-block: "GOLD_BLOCK"       # 要求的目标方块
  once: true                         # 一次性触发
  conditions: []                     # PAPI 条件
  fail-message: "&c条件不满足"        # 条件失败提示(独立 5 秒节流)
  script:                            # 触发时执行
    - "open_chest_ui @trigger trial_chest"
type说明
RIGHT_CLICK_BLOCK / LEFT_CLICK_BLOCK右键/左键 location 处指定方块
BREAK_BLOCK / PLACE_BLOCK破坏/放置 location 处方块
STEP_ON站上 location 处方块
ENTER_REGION进入 region 区域(边沿触发,离开后再进可重触发)
RIGHT_CLICK_ENTITY右键名为 entity-name 的实体

防抖:同一玩家对同一交互点 500ms 内不重复触发;条件失败的提示消息独立节流 5 秒。

puzzles.yml — 机关解谜

每个地牢目录可放 puzzles.yml 定义实例级机关——状态随副本创建/销毁,不占主世界。两种类型:

  • sequence — 按顺序触发一组 interact 点名(压力阀/符文顺序/接线板)
  • input — 弹 ArcartX 输入面板,玩家键盘输入密码/口令(电梯通行码)
# 序列型:按 4-2-1-3 顺序点阀门
valve_sequence:
  name: "冷却阀序列"
  type: sequence
  steps: [valve_4, valve_2, valve_1, valve_3]   # interacts.yml 中的点名(可重复)
  reset-on-miss: true          # 点错顺序→清空进度重来;false=错序只惩罚不回退
  miss-limit: 5                # 累计错序上限(0=不限);超限→on-fail 并锁定
  on-step:                     # 每步正确执行
    - "sound @trigger ui.button.click 1 1.6"
    - "title @trigger &a阀门 {var:puzzle.valve_sequence}/4 已开启"
  on-miss:                     # 错序惩罚
    - "subtitle @all &c阀门顺序错误"
    - "spawn_group noise_lure"
  on-complete:
    - "delete_obstacle pump_door"
  on-fail:
    - "broadcast &c控制台已锁定"
 
# 输入型:加密电梯四位码
elevator_code:
  name: "加密电梯终端"
  type: input
  answer: "4312"               # 正确答案(仅服务端比对,永不下发客户端)
  case-sensitive: false        # 大小写敏感
  length: 4                    # 输入框最大长度(0=不限)
  input-pattern: "^[0-9]*$"    # 客户端字符过滤正则(可删)
  title: "加密电梯终端"         # 面板标题(空用 name)
  hint: "输入四位通行码"        # 输入框空提示
  masked: true                 # 掩码显示(*)
  max-attempts: 3              # 允许错误次数(0=不限;超限→on-fail+锁定)
  per-player: false            # true=每人独立计次;false=全队共享额度
  on-miss: ["title @trigger &c通行码错误"]
  on-complete: ["delete_obstacle elevator_gate"]
  on-fail: ["broadcast &c终端已锁定"]

联动方式

  • 步骤点名 = interacts.yml 键名。interact 触发成功即上报谜题系统——步骤点可以不挂 script(纯机关点),也可以同时挂脚本做额外反馈
  • input 面板入口:interact/NPC 脚本里用 puzzle_prompt <机关id> 打开;指定玩家用 puzzle_prompt_player <玩家|@all> <机关id>
  • 实例变量:{var:puzzle.<id>}=进度(sequence=步数/input=尝试次数)、{var:puzzle.<id>.done}="1"、{var:puzzle.<id>.locked}="1"
  • 事件:axs.abyss.puzzle_step / axs.abyss.puzzle_complete / axs.abyss.puzzle_fail(reason: miss_sequence/miss_input/locked)
  • 管理/脚本动作:puzzle_reset / puzzle_solve / puzzle_state <id> <变量> —— 跳关、动态重置、状态读取
  • 安全模型:答案只在服务端比对;UI 提交需 action_token + 玩家实例校验,伪造包/跨实例提交无效

管理员局内操作

/axs abyss puzzle                  # 列出当前实例全部机关状态
/axs abyss puzzle <id>             # 单机关详情(类型/进度/步数)
/axs abyss puzzle <id> reset       # 重置进度+解锁
/axs abyss puzzle <id> solve       # 直接判定完成(触发 on-complete)
/axs abyss puzzle <id> fail        # 强制锁定(触发 on-fail)
/axs abyss puzzle <id> prompt [玩家]  # 给玩家打开输入面板(测试用)

所有操作只改当前实例内存状态,不写回 puzzles.yml——局内调试完不影响下个副本实例。

buffs.yml — 增益选择池

roguelike 式"祝福"系统:清房/层间/神龛交互等时机,给玩家弹三选一增益卡片(或静默按权重直接生效)。触发不写在本文件——在任何生命周期段 / interact / 机关脚本里用 buff_offer <池id> 动作发起,时机完全由编排决定。

pools:
  room_blessing:
    name: "房间肃清祝福"          # 面板标题
    pick: 1                     # 每玩家选一个
    candidates: 3               # 候选数(<=0=全显;按 weight 加权抽取,不重复)
    presentation: panel         # panel=AX 卡片面板 / hud=不弹窗直接生效
    timeout: 20                 # 选择限时秒(0=不限)
    timeout-auto: random        # 超时兜底:first|random|none
    once-per-instance: false    # true=本池每实例只能 offer 一次
    options:
      - id: sharp_edge
        name: "&c利刃"
        icon: IRON_SWORD        # 物品材质名 → 卡片图标(可空)
        rarity: rare            # common|rare|epic|legendary → 描边色
        weight: 10              # 抽中权重
        desc: ["攻击伤害 +15%"]  # 面板最多显示前 3 行
        effects:                # 脚本行(全动作可用,@trigger=选择者)
          - "apply_attribute @trigger attack_damage 15 percent"
          - "message @trigger &d✦ &c利刃 &7已生效"

效果三通道

effects 是完整脚本行列表,落地路径全走现有机制:

  • 药水:apply_potion @trigger SPEED 1 9999
  • 属性加成:apply_attribute @trigger <属性> <值> [percent](Symphony/AttributePlus/MythicLib/Crane 统一属性桥,装了哪个走哪个)
  • 变量驱动:setvar buff_ember_mark 1 —— 伤害加成/特殊机制在怪物组/武器脚本里 check 变量分支实现,策划自由度最大

触发方式(脚本动作)

# rooms.yml 清房后
on-clear:
  - "buff_offer room_blessing"
 
# floors.yml 层进入 / interact 神龛点击 / 机关完成同理
on-enter:
  - "buff_offer floor_mend"          # presentation: hud —— 不弹窗,直接生效+提示

buff_grant <池id> <选择器> [optionId] 直接赋予不弹窗(剧情强制增益/调试);buff_clear <池id> 撤下未完成的待选面板。

联动

  • 实例变量:{var:buff.<poolId>}=最近所选 option id、{var:buff.<poolId>.count}=已发放人次
  • 事件:axs.abyss.buff_offer / axs.abyss.buff_pick(payload 含 pool/option)
  • 超时:timeout 到时按 timeout-auto 兜底,客户端面板自动关闭

obstacles.yml — 障碍物

障碍物是副本开始时自动放置的方块组,可用于封路/边界/装饰:

edge_wall:
  blocks:
    - "-7,79,-7,BEDROCK"             # x,y,z,Material
    - "-7,79,7,BEDROCK"
  auto-create: true                  # 副本开始时自动放置
  trigger-mode: NONE                 # NONE / ENTER / LEAVE / PERSIST
  on-delete: []                      # 障碍移除时执行的脚本
trigger-mode说明
NONE纯方块组,无触发
ENTER / LEAVE玩家进入/离开障碍区域时触发脚本
PERSIST玩家在区域内持续期间按周期触发

脚本可用 create_obstacle <名> / delete_obstacle <名> 动态控制。

npcs.yml — 副本 NPC

副本 NPC 由 Adyeshach 渲染:每个 npcs.yml 条目在副本实例里生成一个公共临时实体(EntityManagerPublicTemporary,随实例销毁,不落 Adyeshach 持久库),实体 id 固定取配置 key——Chemdah 会话按 npc id: 'adyeshach <key>' 精确绑定。

guide:
  name: "&e向导·艾琳"                # 显示名
  type: VILLAGER                     # PLAYER/VILLAGER/ZOMBIE/SKELETON/WITCH/CREEPER
                                     # /ENDERMAN/BLAZE/PILLAGER/WANDERING_TRADER/IRON_GOLEM
  location: "11,61,11,90,0"          # x,y,z,yaw,pitch(yaw 90=面向 -x 入口)
  skin: "玩家名或皮肤值"              # 仅 PLAYER 型
  look-at-player: true               # 注视附近玩家
  dialogs:                           # 兜底对话:该 NPC 未绑定 Chemdah 会话时逐句播放
    - "&e欢迎来到虚空试炼场!"
  dialog-interval: 40                # 兜底对话间隔(tick)
  click-script:                      # 右键时执行的副本脚本(对话之后照常执行)
    - "accept_quest slayer"
  once: false                        # true=每名玩家只触发一次

点击时的判定顺序:

  1. 该 NPC id 绑定了 Chemdah 会话且 conversation 模块在线 → 打开 ArcartX 真会话 UI(选项/回复走 Chemdah)
  2. 否则 → dialogs 聊天栏逐句播放兜底
  3. click-script 无论哪条路径都会执行

相关脚本动作:spawn_npc / remove_npc(动态生成/移除)、npc_dialog <npc> [sel](同点击判定)、open_conversation <npcId> [sel](强制开 Chemdah 会话,不要求 NPC 在 npcs.yml)、play_cinematic <序列> [sel](播 conversation 电影序列)、play_model_animation(Adyeshach 模型动画)。

实体类型注意:VILLAGER/WANDERING_TRADER 这类实体自带原版交易 GUI——Adyeshach 缺席时 NPC 降级为真实体,右键会先弹原版交易界面盖住自定义交互。接对话/商店的 NPC 推荐 WITCH(无交易界面,两条路径都安全);PLAYER 型仅在 Adyeshach 在场时可用(降级路径会变 ZOMBIE)。

Adyeshach 缺席时副本仍可进入,NPC 退化为原版实体 + 聊天栏对话;装 Adyeshach 未装 conversation 时对话同样走 dialogs 兜底。

quests.yml — 副本任务(Chemdah/QuestGPS 适配层)

任务语义(目标/进度/完成判定)100% 由 Chemdah 任务定义承担,abyss 不自建任务引擎。quests.yml 只是「副本编排面」:本地 id → Chemdah id 的映射 + 副本内钩子脚本。

slayer:                              # 副本本地任务 id(脚本动作都用这个)
  chemdah-id: "sk_slayer"            # Chemdah 任务 id;缺省=本 key
  name: "讨伐深渊魔物"                # HUD 任务行显示名
  description: "深入地牢击杀魔物"
  auto-track: true                   # 接取后自动 quest_track 追踪
  on-accept:                         # 接取成功时跑(@trigger=该玩家)
    - "message @trigger &a已接取委托"
  on-complete:                       # Chemdah 完成 → EventBus 路由到此发副本侧奖励
    - "grant_reward @trigger quest_pack"
  manual-claim: false                # true=完成不自动发奖,须 submit_quest 回 NPC 领取

脚本动作(均按本地 id 解析成 chemdah-id 后委托 questgps/Chemdah):

动作行为
accept_quest <任务> [sel]通过 QuestGpsNavigable.acceptQuest 接取;成功跑 on-accept,auto-track 时顺带追踪
submit_quest <任务> [sel]已完成的 Chemdah 任务 → 跑 on-complete 领副本奖励(manual-claim 场景)
quest_step <任务> [sel]未接取→接取;已完成未领→提交。NPC click-script 一站式用法
quest_track <任务> [步骤] [sel]QuestGPS 追踪卡片(整个任务或指定步骤)
quest_status <任务> <变量> [sel]写 NONE/ACTIVE/COMPLETED 到实例变量,配合 check 分支

完成路由:Chemdah 判定完成 → questgps 发 EventBus axs.questgps.quest_completed → abyss 反查 chemdah-id 命中本地任务 → manual-claim: false 直接跑 on-complete。

注意:Chemdah 任务是玩家级持久档案——副本任务应在 Chemdah 侧配置为可重复接取(agent: restart 类),否则玩家二刷副本时任务已 COMPLETED 无法再进状态机。QuestGPS 缺席时相关动作静默跳过,副本其余玩法不受影响。任务定义文件放 plugins/Chemdah/(questgps 呈现层放 data/questgps/quests/),示例见 example 模板 external/ 目录。

rewards.yml — 奖励集与宝箱

命名奖励集

grant_reward <集名> 脚本动作调用:

rewards:
  clear_pack:
    items:
      - "material:DIAMOND:3"         # material:<材质>:<数量>
      - "material:GOLDEN_APPLE:2"
    money: 100                       # 货币(走宿主 currencyManager)
    currency: "money"
    exp: 50
    commands: []                     # 控制台命令(%player% 占位)

物品写法 material:<Material>:<数量>;MythicMobs/NeigeItems 等外源物品经宿主 ItemSourceRegistry 生成(写法见 integration 页)。

随机奖励集:

  random_pack:
    type: random
    picks: 1                         # 抽取次数
    pool:
      - reward: clear_pack           # 引用其他集
        weight: 80
      - reward: quest_pack
        weight: 20

宝箱奖励

open_chest_ui @trigger <名> 打开宝箱 UI(无 ArcartX 客户端时直接发放):

chest-rewards:
  trial_chest:
    title: "试炼宝箱"
    rows: 3                          # UI 行数
    options:                         # 权重抽取的物品槽
      - item: "material:IRON_INGOT:5"
        weight: 50
      - item: "material:DIAMOND:2"
        weight: 20

宝箱 UI 支持单个/全部领取;玩家未领取关闭时服务端自动全发兜底,不吞奖励。

shops.yml — 副本商店

open_shop_ui <选择器> <商店id> 打开自定义商店 UI(替代台座购买):商品行列表展示图标/名称/描述/价格,顶部实时余额,金币不足自动置灰。

shops:
  caravan:                         # 商店 id
    title: "旅行商队"               # 面板标题
    currency-var: coins            # 结算变量(副本实例变量,击杀掉落攒的那种)
    goods:
      - id: heal
        name: "&a治疗药剂"
        icon: "material:SPLASH_POTION"   # material:<材质> 或物品描述
        desc: ["&7恢复 12 点生命", "&75 秒再生"]
        price: 8                   # 货币价格
        script:                    # 购买成功后执行(@trigger=买家)
          - "heal @trigger 12"
          - "apply_potion @trigger REGENERATION 5 2"
      - id: power
        name: "&c力量药剂"
        icon: "material:POTION"
        price: 12
        script:
          - "random shop_power_lvl 1 3"
          - "run_script shop_power_t1"   # 各档段首行 if_var 门控
          - "run_script shop_power_t2"
          - "run_script shop_power_t3"
  • 结算模型:服务端校验 token + 副本实例身份 + currency-var 余额 → 扣实例变量 → 跑 script → 回推余额刷新 UI;购买是原子扣费,伪造包/跨实例无效
  • NPC 入口:npcs.yml 的 click-script 里写 open_shop_ui @trigger caravan;同店多台座/多入口并存不冲突
  • 类型注意:接商店的 NPC 不要用 VILLAGER/WANDERING_TRADER——Adyeshach 缺席降级真实体时,右键会先弹原版交易 GUI 盖住交互;WITCH 无交易界面两条路径都安全
  • 商品 script 是完整动作行列表,随机档位/条件分支照常用 random+if_var+run_script 模式

scripts.yml — 生命周期脚本

段执行时机:

段时机
world-init世界就绪后、玩家传送之前——造平台/铺场景专用(void 世界必须用它建落脚处)
init玩家全部进图后、副本激活前(玩法对象尚未初始化)
start副本激活时(怪组已注册,AUTO_START 组已刷)
complete / fail / exit通关 / 失败 / 玩家离开
player-death / all-death单人阵亡 / 全员阵亡

虚空世界务必用 world-init 而不是 init 建平台——init 在玩家传送之后才跑,先传送会掉虚空。即使没写 world-init,系统也会检测出生点下方无支撑时自动垫一块 5×5 基岩兜底。

详见 脚本系统。

floors.yml + rooms.yml — 多层房间地牢

config.yml 设 mode: floors 后启用:地牢由若干层(floor)组成,每层是房间图——房间在世界里真实互连(走廊物理可走),玩家自由探索/回退,不必按 rooms 列表顺序访问(类元气骑士迷宫结构)。房间 region/doors 定义边界和物理门洞,拓扑由 world-init 的建筑布局决定——门洞后接走廊=真通路,挖死=假门/装饰。

floors.yml

defaults:
  lock-material: BARRIER     # 锁门填充材质
  border-pullback: true      # 战斗房锁门期间离开区域拉回(逃逸双保险)
  room-spacing: 200          # pool 模式相邻房锚点间距(格)
  paste-y: 64                # pool 模式 schematic 粘贴基准高度
  advance-timeout: 60        # portal 推进兜底秒数(0=不兜底)
 
floors:
  - id: f1
    name: "第 1 层"
    source: region              # region=固定区域 | pool=schematic 随机拼接
    spawn: "0,65,0,90,0"        # 层入口站位(region 绝对坐标)
    rooms: [entry, combat_a, altar_a, combat_b, boss_1, chest_a]
                                # region 模式:本层全部房间的引用集合(顺序不决定推进路线)
    boss-room: boss_1           # 终点房(显式指定!不配则退回"链尾房"的线性语义)
    advance: portal             # portal=终点房清完踩出口传送 | auto=清完自动传送
    advance-timeout: 90         # portal 兜底:终点清完 90s 无人踩出口自动推层
    on-enter: [...]             # 层开始脚本
    on-clear: [...]             # 层清完脚本
  - id: f2
    source: pool                # 随机拼接(world.template 建议 void + FAWE)
    pool: [combat_a, combat_b, shop_a, treasure_a]
    room-count: 3               # 池内随机抽取数(0=全用且打乱顺序)
    boss-room: boss_2
    direction: x                # 拼接排列轴 x|z
    advance: portal
字段说明
source: region房间已手工建在世界模板里,坐标写世界绝对值
source: pool虚空世界 + 房间 .schem 随机抽取粘贴(需 WorldEdit/FAWE,进本时校验)
advance: portal层/房清完激活出口区,玩家踩 exit 传送;advance-timeout 超时自动推
advance: auto清完 3 秒后自动传送下一目标

rooms.yml

rooms:
  corridor_a:
    type: combat                 # combat=锁门清怪 | safe/shop=不锁 | boss=战斗(语义标记)
    schematic: "rooms/a.schem"   # pool 模式必填(相对地牢目录);region 忽略
    origin: "0,0,0"              # region 房间原点(默认 0,0,0 = 坐标写绝对值)
    region: ["0,60,0", "29,80,29"]  # 房间 box 两角(相对 origin)
    spawn: "1,64,15,90,0"        # 进房站位(相对 origin)
    exit: ["29,63,13", "31,67,17"]  # 出口传送区(可选)
    doors:                        # 物理门盒(锁=填 lock-material,开=还原原方块)
      - region: ["30,64,14", "30,66,16"]
        material: BARRIER
    wave-rest: 4                  # 房内 TRIGGERED 组休整兜底(组自身 delay/rest 优先)
    groups:                       # 房内怪组(格式同 monsters.yml,坐标相对 origin)
      wave1:
        spawn-timing: AUTO_START  # 房内语义:AUTO_START=锁门即刷 / DELAYED=锁门后延迟 / TRIGGERED=组联动
        monsters:
          - mob-id: vanilla:zombie
            location: "15,64,15"
            amount: 4
    on-enter: [...]               # 玩家首次进房
    on-lock: [...]                # 锁门瞬间
    on-clear: [...]               # 房内组全清(无怪房进房即清)

运行规则

  • 房间图遍历:引擎每 tick 扫描层内全部房间做进出检测——进任意房触发 on-enter/锁门,可回退/绕支线/走回头路;rooms 列表只是集合而非路线
  • 终点语义:boss-room 显式指定层终点——终点房清完 → 层清 → 进下一层;非终点房清完只解锁自家门,不自动传送
  • 多门洞:doors 是列表,一房可配任意多个门洞(锁门时全部填塞、清完还原原方块);门洞是否通向真房间由 world-init 的建筑布局决定——封死=假门/装饰,通走廊=真通路
  • 房间物化:进本时按层生成房间链;pool 房沿轴粘贴,一切坐标(region/spawn/doors/groups/exit)自动按锚点偏移为绝对值
  • 怪组物化:房内组注册为 f{层}_r{房序}_{模板id}:{组名};trigger-after 引用房内短名自动加前缀
  • 事件:axs.abyss.room_enter / room_clear / floor_enter / floor_clear
  • 脚本变量:{floor} {floor_name} {floors_total} {room} 写入实例变量
  • 脚本动作:floor_next(强制推层)/ room_lock <房> / room_unlock <房> / room_status <房> <变量>
  • 虚空防护:玩家掉出所在房底部 8 格以下自动拉回该房中心
  • 无怪组的安全房进房即清(不广播"肃清");最终层终点房清完自动 complete_dungeon
  • Boss 之门模式:world-init 把终点房门洞封死 → 战斗房 on-clear 累加 fN_cleared 变量 → 首个肃清房触发 fN_gate 段 fillblocks AIR 开门(example 副本的实现方式,纯脚本无引擎依赖)

内置示例:元气试炼之地(example)

abysses/example/ 是一个零外部资源即可跑通的元气骑士式三层迷宫地牢——world.template: void 虚空世界,全部建筑由 scripts.yml 的 world-init 段用 fillblocks 程序化生成(每层 7 房×3 层,40×40 大房(内空 38×38)+ 16×8 走廊物理互连成图,祭坛/Boss 房挑高 22,无需 schematic)。

层拓扑演示点
f1 苔痕前哨入口—战斗A—战斗B—Boss / 入口—祭坛 / 战斗B—宝箱向导 NPC(Adyeshach+Chemdah 会话+委托)、随机波次包+休整预告、金币祭坛三选一、Boss 之门(肃清任一战斗房开门)
f2 熔岩矿道入口—战斗A—战斗B—谜题 / 入口—商店 / 战斗B—战斗C—Boss商店 UI+台座购买、彩窗提示序列拉杆机关、唤魔者+增援
f3 深渊王座入口—战斗A—Boss(环形)/ 祭坛—战斗B—宝箱厅 / 战斗B—精英灵魂沙+岩浆块陷阱带(obstacles)、精英卫队房、终层独立宝箱池、凋零+两阶段增援

贯穿机制:击杀掉全队金币(tasks.yml monster_kill 事件)、祭坛/商店 %axsabyss_var_coins% 条件扣费、每层 Boss 后 buff_offer 三选一天赋、guide_point 路标链(向导→东廊→首战)、portal 出口垫传送 + 走廊可回溯、房间可任意顺序探索。

外部配套(随包自动部署,详见 example/external/README.md):Chemdah 会话文件绑 adyeshach guide/adyeshach merchant(NPC 右键开真会话 UI);Chemdah 任务 sk_slayer + questgps 呈现层(quests.yml 的 slayer 委托线)。abyss 加载模板时自动把 external/ 部署到 plugins/Chemdah/core/ 与 data/questgps/quests/ 并触发重载。缺依赖时自动降级:原版实体 NPC、聊天栏对话、委托线静默跳过。

区域模板 regions/

手工搭好的建筑可以整片圈选存为模板,副本脚本一键铺放——比逐行 fillblocks 快几个量级。

管理(见 命令页·区域模板):/axs abyss region pos1|pos2 圈选 → save <地牢> <名> 存到 abysses/<地牢>/regions/<名>.yml。

使用(scripts.yml 任意段,world-init 最常用):

world-init:
  - "paste_region 神庙 0,60,0"         # 默认跳过 AIR——结构嵌入已有地形
  - "paste_region 遗迹 100,60,0 full"  # full=连空气一起覆盖——虚空世界铺房/挖空腔体

模板是调色板(BlockData 串,保留朝向/状态)+ 逐层索引的 YAML——方块实体内容不迁移(箱内物品/刷怪笼/告示牌文字这些纯 NBT 不复制)。铺放同 fillblocks 免物理更新,不会触发邻接级联。