后室:逃出生天插件详解


后室:逃出生天 (Backrooms Escape)

扮演 M.E.G.CN 工作人员,从 Level 0 出发,在后室中寻找出口不断切入下一个楼层,直至找到最终出口 Level 399。

游戏背景

你是一名 M.E.G.CN(探险者总署中文分部)的探员,在一次任务中不小心切入了后室。这是一个由无数诡异楼层组成的超自然空间。你从著名的 Level 0「前厅」开始,必须穿越一个个危险而诡异的楼层,找到传说中的 Level 399 最终出口,才能回到现实世界。

后室中潜伏着各种危险的实体,但也有前人留下的补给品和线索。能否活着逃出去,取决于你的智慧和运气。

快速上手

第一步:验证插件

在群里或私聊发送:

plaintext
/br test

如果收到回复「✅ 后室逃生插件已正常接收消息!」,说明插件工作正常,可以开始游戏。

第二步(可选):查看已解锁的工作故事

plaintext
/br story

完成 Alpha 基地工作后,使用 /br story 查看已解锁的故事列表,使用 /br story <ID> 通过合并转发消息阅读具体内容。

第三步:开始游戏

plaintext
/br start

你会从 Level 0「前厅」出发,系统会给你当前楼层的描述和可用的命令。

第四步:游戏主循环

每一层的标准流程:

plaintext
/br explore  →  探索楼层(搜集物品、了解环境)
/br exit     →  寻找出口(切入下一层)
/br status   →  随时查看探员状态和背包

重复以上步骤,直到抵达 Level 399。

游戏内智能引导

游戏会在每一轮回复后自动给出下一步建议:

  • 进入新楼层后 → 提示先探索再找出口
  • 多次找不到出口 → 建议探索搜集物品来提升概率
  • 生命值/理智值过低 → 提醒检查背包中的补给品
  • 查看背包时 → 根据持有物品给出个性化策略建议

命令列表

命令说明使用时机
/br test测试插件连通性首次使用前,验证插件是否正常
/br story故事档案查看已解锁的工作故事列表,/br story <ID> 以转发消息查看具体故事
/br start开始新游戏首次进入 / 死亡后重开
/br explore探索当前楼层每层第一步,搜寻物品和线索
/br exit尝试寻找出口探索之后,尝试切入下一层
/br read阅读捡到的纸条探索中捡到纸条后,通过合并转发消息展示内容
/br use <编号>使用背包中的物品需要恢复生命/理智时使用。例:/br use 1
/br status查看探员状态随时查看生命/理智/进度/贡献点
/br inventory查看背包检查物品,获取使用建议
/br quest任务系统查看/接受/提交任务,获得贡献点
/br work基地工作在 Level 1 Alpha 基地参与解谜工作,获得贡献点
/br help游戏帮助忘记命令时查看
/br people_net人物关系图查看已解锁角色的背景与关系
/br say随机名言随机输出一句名人名言
/br off关闭插件(管理员)仅首次使用的用户可关闭,关闭后仅管理员可用
/br on启用插件(管理员)管理员重新开放插件

技术架构

本插件使用 MaiBot SDK 的 @Command 组件进行命令路由,当用户消息匹配命令的正则模式时,MaiBot 会自动调度执行对应的处理函数,匹配到的消息不会进入 Planner/LLM 处理链。

所有游戏命令以 /br 为前缀,兼容 NapCat(OneBot v11)消息格式。

消息输出模式

所有游戏输出统一通过 _send() 方法发送,根据 config.toml 中的 [plugin]output_mode 配置自动切换:

  • "text"(默认):所有回复以普通文本形式发送
  • "forward":所有回复以合并转发消息形式发送,支持多节点拆分展示

合并转发消息

/br story <ID>/br read 命令在 output_mode = "forward" 时使用合并转发消息展示。

节点格式:

python
nodes = [
    {
        "user_id": "标识",
        "user_nickname": "昵称",
        "content": [{"type": "text", "data": "消息内容"}],
    },
]

adapter 内部流程:

  1. SDK 将 nodes 包装为 raw_message = [{"type": "forward", "data": nodes}]
  2. adapter 的 _contains_forward_segment() 检测到 type=forward
  3. _build_forward_nodes() 映射:user_id→uin, user_nickname→name, content 经 segment encoder 转换
  4. send_group_forward_msg / send_private_forward_msg → NapCat

不用 send.custom("send_msg", ...) 传 node 段 — adapter 将其识别为普通消息段输出 [unsupported:node]。也不用 send.custom("send_forward_msg", ...) — 绕过了 adapter 的消息段转换和 group_id/user_id 自动注入。

权限要求

需在 _manifest.jsoncapabilities 中声明:

json
"capabilities": [
  "send.text",
  "send.forward",
  ...
]

项目结构

目录树

plaintext
backrooms_escape/

├── plugin.py                  # 插件主入口
│                              游戏逻辑、命令注册、数据持久化

├── renderer.py                # 消息渲染器 + 拓展模块加载入口
│                              格式化所有回复文本;从 renderer_load/ 导入并透出所有拓展类

├── config.py                  # 配置模型
│                              PluginConfigBase 子类,定义 [plugin]/[game]/[whitelist]/[blacklist]

├── renderer_load/             # 拓展功能模块目录
│   ├── __init__.py            # 统一导出(上层只需 import renderer_load 即可)
│   ├── state_machine.py       # 有限状态机 — GameState / GameEvent / GameStateMachine
│   ├── shut.py                # 群聊静默管理 — ShutManager
│   ├── story_manage.py        # 数据加载层 — StoryManager / PeopleStoryMgmt /
│   │                          #               QuestManager / WorkManager / BaseWorkStoryManager
│   └── people_manage.py       # 角色业务层 — CharacterEncounterService / CHARACTERS 注册表

├── br_story/                  # 故事/剧情/数据目录
│   ├── level_story/           # 后室背景故事纸条文本
│   │   └── l1_story.txt ~ l11_story.txt
│   ├── people_story/          # NPC 角色剧情 + 任务数据 + 人物关系数据
│   │   ├── ankexin.txt
│   │   ├── anjinian.txt
│   │   ├── people_quests.json
│   │   └── people_relationship.json
│   └── base_story/            # 基地工作解谜数据 + 解锁故事
│       ├── base_work.json
│       └── work_W001_story.txt ~ work_W005_story.txt

├── config.toml                # 插件配置文件(启动时自动读取)
├── _manifest.json             # 插件元信息(ID、版本、兼容性声明)
├── backrooms_data.json        # 物品/实体数据池
├── br_data/                   # 玩家存档(运行时自动创建)
│   └── *.json
└── .gitignore

分层设计

plaintext
┌─────────────────────────────────────────────────────┐
│                   plugin.py                          │
│   命令路由 + 状态管理 + 存档读写 + 游戏流程编排         │
│   依赖: renderer, config                             │
└─────────────────────────┬───────────────────────────┘


┌─────────────────────────────────────────────────────┐
│                   renderer.py                        │
│   消息格式化 + 拓展模块统一加载入口                     │
│   从 renderer_load/ 导入并透出所有类                   │
└─────────────────────────┬───────────────────────────┘

          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
┌─────────────────┐ ┌──────────┐ ┌──────────────────┐
│  renderer_load/  │ │ config   │ │ renderer_load/   │
│  state_machine   │ │          │ │ shut             │
│  (FSM)           │ └──────────┘ │ (群聊静默)        │
└─────────────────┘              └──────────────────┘
                                  ┌──────────────────┐
                                  │ renderer_load/   │
                                  │ story_manage     │
                                  │ (数据加载层)       │
                                  │  .StoryManager    │
                                  │  .PeopleStoryMgmt │
                                  │  .QuestManager    │
                                  │  .WorkManager     │
                                  │  .BaseWorkStory   │
                                  └────────┬─────────┘
                                           │ 调用获取数据

                                  ┌──────────────────┐
                                  │ renderer_load/   │
                                  │ people_manage    │
                                  │ (角色业务层)       │
                                  │  .CHARACTERS      │
                                  │  .EncounterService│
                                  └──────────────────┘

数据流

plaintext
用户命令 → plugin.py (命令处理)

            ├──→ renderer.xxx()       ← 格式化回复消息
            ├──→ fsm.apply(event)     ← 状态转移
            ├──→ story_manage.xxx()   ← 读取故事/任务/工作数据
            ├──→ people_manage.xxx()  ← 角色遭遇/礼品/任务发放
            ├──→ _save_player()       ← 持久化存档
            └──→ _send()              ← 发送回复

约定

所有新增的功能拓展模块应放置在 renderer_load/ 目录下,并在 renderer.py 中导入并透出。plugin.py 无需直接引用 renderer_load/ 下的任何模块。

状态机

插件使用有限状态机(FSM) 管理游戏核心流程,定义在 renderer_load/state_machine.py 中。

状态定义

状态含义可执行操作
NOT_STARTED未开始游戏/br start
ALIVE存活探索中(Level 0~398)全部命令
AT_399到达最终出口/br exit(触发通关)
DEAD生命值归零/br start 重新开始
ESCAPED成功逃出后室/br start 重新开始

状态转移图

plaintext
NOT_STARTED ──start──▶ ALIVE ──reach_399──▶ AT_399 ──exit_399──▶ ESCAPED
                         │                                              │
                         │ die                                           │ restart
                         ▼                                              │
                       DEAD ◀────────────────────────────────────────────┘

                         └── restart ──▶ ALIVE

ALIVE 状态下 exploreexituse_item 等事件不会改变状态,但可能触发 die(生命归零)或 reach_399(到达 Level 399)进入新状态。

在 PlayerState 中使用

python
from dataclasses import dataclass, field
from .renderer import GameStateMachine

@dataclass
class PlayerState:
    fsm: GameStateMachine = field(default_factory=GameStateMachine)
    # ... 其他字段

# 守卫检查(替代旧的 game_started 判断)
if not player.fsm.is_playable():
    # 拒绝操作

状态机将游戏流程显式化为一张可读的转移表,非法操作自然被拦截,不再需要散落在各命令处理器中的 if game_started 隐式状态判断。

游戏机制

核心属性

  • 生命值(❤️):初始 100 点。归零则游戏结束,角色永远留在后室中。
  • 理智值(🧠):初始 100 点。探索和寻找出口都会消耗理智值。理智值过低会遭受额外惩罚。可通过 o1(杏仁水) 恢复。
  • 楼层进度:从 Level 0 出发,目标是 Level 399。共有 400 个楼层需要穿越。

探索(/br explore)

每次探索消耗 2 点理智值,会触发以下随机事件:

  • 发现线索:找到 M.E.G. 遗留的信息、地图碎片或日记。
  • 获得物品:有几率找到补给品,如 o1(杏仁水)、o2(急救包)、o3(手电筒) 等。
  • 遭遇危险:可能受伤,或遭遇后室实体。
  • 诡异事件:一些令人不安但不会直接伤害你的事件。

寻找出口(/br exit)

每次尝试寻找出口消耗 5 点理智值。基础成功率 20%,每次失败后会递增成功率,确保你不会无限卡关。

物品可为成功率提供加成:

  • o4(层级钥匙):100% 找到出口
  • o3(手电筒) / o5(M.E.G. 无线电):各 +5% 成功率

捷径系统

有 12% 的概率触发捷径,可一次跳过 2~20 个楼层,大幅加速进度。

知名楼层

游戏包含以下根据后室维基设定的知名楼层,每个都有独特描述和专属实体:

楼层名称危险等级特色
Level 0前厅无限黄色办公空间,一切的起点
Level 1宜居区M.E.G. Alpha 基地所在地
Level 2管道梦魇巨型管道迷宫,猎犬出没
Level 3电气站遍布电线和配电箱
Level 4废弃办公室被遗弃的办公大楼
Level 5恐怖旅馆扭曲奢华的旅馆,镜像实体徘徊
Level 6熄灯完全黑暗,笑魇聚集地
Level 7深海恐惧极高无边海洋,深海巨兽潜伏
Level 8洞穴系统巨大的天然洞穴网络
Level 9暗黑郊区永夜的诡异郊区
Level 10丰收之景无边麦田,稻草人守望
Level 11无尽城市空无一人的现代化城市
Level 399真正的结局最终出口——欢迎回家

其他楼层(Level 12~398)将程序化生成,包含不同主题和危险等级。

物品系统

探索中有概率获得以下物品:

代码名称类型效果
o1杏仁水消耗品使用后恢复 30 点理智值,在探索和找出口中自动消耗前手动使用
o2急救包消耗品使用后恢复 30 点生命值;在受伤事件中自动消耗一次(减伤 5 点)
o3手电筒装备被动生效:遭遇笑魇/猎犬时驱散(免伤);其他实体伤害减 10;+5% 出口发现率
o4层级钥匙消耗品持有后下次 /br exit 必能找到出口(100% 成功率),使用后消耗
o5M.E.G. 无线电装备被动生效:+5% 出口发现率
o6能量棒消耗品使用后恢复 15 点生命值
o7镇定剂消耗品使用后恢复 15 点理智值

物资箱系统

探索和寻找出口时(Level 0 除外),有概率发现大、中、小三种物资箱。每种箱子的物资内容不同:

箱型杏仁水额外物品
🟦 大型物资箱✅ 必出随机物品 +1
🟩 中型物资箱✅ 必出随机物品 +1
🟨 小型物资箱✅ 必出随机物品 +1

额外随机物品根据各物品的权重分配概率(权重越高越常见),可在 config.toml[game] 段中调整:

参数默认值对应物品默认概率
item_weight_o13o1 杏仁水(恢复 30 理智)20.0%
item_weight_o23o2 急救包(恢复 30 生命)20.0%
item_weight_o32o3 手电筒(驱散笑魇/猎犬)13.3%
item_weight_o41o4 层级钥匙(稀有,100% 出口)6.7%
item_weight_o52o5 M.E.G. 无线电(+5% 出口率)13.3%
item_weight_o62o6 能量棒(恢复 15 生命)13.3%
item_weight_o72o7 镇定剂(恢复 15 理智)13.3%

同时获得杏仁水和额外物品时,二者概率独立计算。

物资箱概率配置

可在 config.toml[game] 段中可分别调整三种物资箱的出现概率:

参数默认值说明
crate_large_chance0.08触发补给事件时出现大型物资箱的概率
crate_medium_chance0.15触发补给事件时出现中型物资箱的概率
crate_small_chance0.25触发补给事件时出现小型物资箱的概率

三种概率参数可独立调整。总判定区间为三参数之和(默认 0.08+0.15+0.25=0.48),剩下的 1−0.48=0.52 为空箱概率。若调整后三参数之和超过 1.0,则空箱概率为 0%。

实体

后室中潜伏着多种危险实体,遇到它们会损失生命值:

  • 笑魇(伤害 15):黑暗中的无面人形实体,手电筒可驱散
  • 猎犬(伤害 20):高速四足猛兽,手电筒可驱散
  • 窃皮者(伤害 25):剥取皮肤的恐怖实体
  • 死亡飞蛾(伤害 10):能让人产生幻觉的巨型飞蛾
  • 旅馆管理者(伤害 30):Level 5 的主人
  • 深海之物(伤害 35):Level 7 的不可名状巨兽
  • 以及更多……

胜利条件

到达 Level 399 并推开最终之门,你就成功逃出了后室!

新手策略建议

  1. 保持节奏:每层先 /br explore 搜集物品,再用 /br exit 找出口。别跳过探索直接找出口,那样会错过补给品。
  2. 关注状态:经常用 /br status/br inventory 了解自己的情况。生命值或理智值低时优先考虑使用补给。
  3. 善用物品:o4(层级钥匙) 是通关神器,o3(手电筒) 也能提高出口发现率。
  4. 不要死磕:如果同一层 /br exit 多次失败,回头 /br explore 看看能不能找到 o4(层级钥匙)。
  5. 理智管理:o1(杏仁水) 是关键资源,理智值耗尽会导致额外扣血。保持在 30 以上比较安全。

配置说明

可在 config.toml 中调整游戏参数和访问控制。配置模型定义在 config.py 中,包含五个配置段:[plugin][game][whitelist][blacklist]

插件输出模式 [plugin]

参数默认值说明
output_mode"text"消息输出模式:"text"=普通消息,"forward"=合并转发消息
toml
[plugin]
output_mode = "text"      # 普通文本消息(默认)
# output_mode = "forward" # 合并转发消息(所有回复以合并转发形式发送)

游戏参数 [game]

参数默认值说明
initial_health100初始生命值
initial_sanity100初始理智值
explore_sanity_cost2每次探索所消耗的理智值
exit_search_sanity_cost5每次寻找出口所消耗的理智值
base_exit_chance0.2成功找到出口的基础概率(0.0~1.0)
exit_chance_increment0.1每次寻找失败后成功概率的提升值
entity_encounter_chance0.25在楼层中遭遇实体的基础概率
crate_large_chance0.08触发补给时出现大型物资箱的概率(必出杏仁水)
crate_medium_chance0.15触发补给时出现中型物资箱的概率(必出杏仁水)
crate_small_chance0.25触发补给时出现小型物资箱的概率(必出杏仁水)

物品权重

每个物品有独立的获取权重(item_weight_o1 ~ item_weight_o7)。当触发物品获取事件时,系统根据权重加权随机选择。权重越高,该物品越常见。设为 0 则该物品不会出现。

概率计算方式:某物品概率 = 该物品权重 ÷ 所有物品权重之和

默认配置下总权重 = 3+3+2+1+2+2+2 = 15:

参数默认值对应物品默认概率
item_weight_o13o1 杏仁水(恢复 30 理智)20.0%
item_weight_o23o2 急救包(恢复 30 生命)20.0%
item_weight_o32o3 手电筒(驱散笑魇/猎犬)13.3%
item_weight_o41o4 层级钥匙(稀有,100% 出口)6.7%
item_weight_o52o5 M.E.G. 无线电(+5% 出口率)13.3%
item_weight_o62o6 能量棒(恢复 15 生命)13.3%
item_weight_o72o7 镇定剂(恢复 15 理智)13.3%

概率计算公式

出口概率

plaintext
最终概率 = min(base_exit_chance + 尝试次数 × exit_chance_increment, 1.0)

累计尝试次数下各次的实际出口概率(默认 base_exit_chance = 0.2exit_chance_increment = 0.1):

尝试次数计算公式实际概率
第 1 次0.2 + 0 × 0.120%
第 2 次0.2 + 1 × 0.130%
第 3 次0.2 + 2 × 0.140%
第 4 次0.2 + 3 × 0.150%
第 5 次0.2 + 4 × 0.160%
第 6 次0.2 + 5 × 0.170%
第 7 次0.2 + 6 × 0.180%
第 8 次0.2 + 7 × 0.190%
第 9 次0.2 + 8 × 0.1100%(达上限)

使用 o4(层级钥匙) 时概率直接锁定为 100%,不参与上述计算。携带 o3(手电筒) 或 o5(无线电) 时,各自额外 +5%(于公式计算后累加,上限仍为 100%)。

实体遭遇概率

plaintext
实际遭遇概率 = entity_encounter_chance × 当前楼层危险系数

各楼层危险等级对应的系数(默认 entity_encounter_chance = 0.25):

危险等级系数实际遭遇概率
🔵 低0.50.25 × 0.5 = 12.5%
🟡 中1.00.25 × 1.0 = 25.0%
🟠 高1.50.25 × 1.5 = 37.5%
🔴 极高2.00.25 × 2.0 = 50.0%

物资箱判定

每次触发补给事件时,按以下顺序判定物资箱类型(默认值):

plaintext
判定流程:
  ┌─ random < 0.08  ──→ 🟦 大型物资箱(必出杏仁水)
  ├─ random < 0.23  ──→ 🟩 中型物资箱(必出杏仁水)
  ├─ random < 0.48  ──→ 🟨 小型物资箱(必出杏仁水)
  └─ 以上均未通过 ──→  空箱("里面已经空了")

各参数值对应的箱型出现概率总表:

参数默认值箱型判定区间独立出现概率
crate_large_chance0.08🟦 大型[0, 0.08)8%
crate_medium_chance0.15🟩 中型[0.08, 0.23)15%
crate_small_chance0.25🟨 小型[0.23, 0.48)25%
❌ 空箱[0.48, 1.0)52%

三种概率参数可独立调整。总判定区间为三参数之和(默认 0.08+0.15+0.25=0.48),剩下的 1−0.48=0.52 为空箱概率。若调整后三参数之和超过 1.0,则空箱概率为 0%。

物品权重概率

plaintext
某物品概率 = 该物品权重 ÷ 所有物品权重之和

各物品权重参数在不同总权重下的概率对照表:

参数权重值总权重=15 时概率总权重=10 时概率总权重=20 时概率
item_weight_o1320.0%30.0%15.0%
item_weight_o2320.0%30.0%15.0%
item_weight_o3213.3%20.0%10.0%
item_weight_o416.7%10.0%5.0%
item_weight_o5213.3%20.0%10.0%
item_weight_o6213.3%20.0%10.0%
item_weight_o7213.3%20.0%10.0%

将某个物品权重设为 0 可使该物品不再出现,其余物品概率会按比例重新分配。

白名单 [whitelist]

白名单用于控制哪些群组或私聊用户可以使用本插件。默认关闭,所有用户均可使用。

  • enabled:是否启用白名单(true/false,默认 false
  • group_ids:群组白名单,字符串数组,填入允许使用的群号
  • user_ids:私聊白名单,字符串数组,填入允许使用的用户 QQ 号
  • group_deny_message:群组不在白名单时的提示文本(可自定义)
  • private_deny_message:用户不在私聊白名单时的提示文本(可自定义)
  • empty_group_list_message:白名单启用但群组列表为空时的提示文本(可自定义)
  • empty_private_list_message:白名单启用但私聊列表为空时的提示文本(可自定义)

群组和私聊权限完全独立:你可以只开放群聊而关闭私聊,反之亦然。

toml
# 示例:仅允许群号 123456789 和 987654321 的群使用
[whitelist]
enabled = true
group_ids = ["123456789", "987654321"]
user_ids = []
toml
# 示例:仅允许 QQ 号 10001 私聊使用,不限群组
[whitelist]
enabled = true
group_ids = []
user_ids = ["10001"]

黑名单 [blacklist]

黑名单用于禁止指定群组或用户使用插件,优先级高于白名单。

  • enabled:是否启用黑名单(true/false,默认 false
  • group_ids:群组黑名单,字符串数组,填入禁止使用的群号
  • user_ids:用户黑名单,字符串数组,填入禁止使用的用户 QQ 号
  • group_deny_message:群组被黑名单拦截时的提示文本(可自定义)
  • private_deny_message:用户被黑名单拦截时的提示文本(可自定义)
toml
# 示例:禁止群号 111222333 和用户 444555666 使用
[blacklist]
enabled = true
group_ids = ["111222333"]
user_ids = ["444555666"]

任务系统

任务系统通过安可欣发布任务,完成后可获得 M.E.G.CN 贡献点。在 Level 1(Alpha 基地)探索时有概率遇到安可欣,她可能给你一个随机任务。

任务类型

类型说明示例
reach_level到达指定楼层到达 Level 5 / Level 11 / Level 399
collect_item收集特定物品获取一台 M.E.G. 无线电
use_item提交指定物资提交 2 个杏仁水和 1 个急救包

任务列表

ID标题目标奖励
M001初来乍到到达 Level 550 贡献点
M002信号搜寻收集无线电(o5)80 贡献点 + 2 急救包
M003深层探索到达 Level 11150 贡献点
M004紧急补给提交 2 个 o1 + 消耗 1 个 o260 贡献点 + 层级钥匙
M005归途之旅到达 Level 399500 贡献点

相关命令

命令说明
/br quest查看任务面板(进行中/可接/已完成)
/br quest accept <ID>接受任务
/br quest submit <ID>提交已完成的任务

到达目标楼层时,系统会自动提示任务可提交。

基地工作系统

在 Level 1(Alpha 基地)探索时,有概率触发工作事件,完成后可参与解谜工作赚取贡献点。工作内容基于高中自然地理知识,包括坐标计算、气候识别、时区换算、气压带分析和地形推算。

工作列表

ID标题部门谜题类型奖励
W001经纬度坐标校准信息分析组正午太阳高度角计算30 贡献点
W002气候带识别与物资调配仓储管理世界气候类型判断25 贡献点
W003时区与抵达时间推算工程部时区换算35 贡献点 + 杏仁水
W004气压带与后室气流分析信息分析组全球大气环流25 贡献点
W005等高线地形与路线规划工程部海拔与气温垂直递减率40 贡献点 + 无线电

相关命令

命令说明
/br work查看基地工作面板
/br work start <ID>开始工作(显示谜题详情)
/br work answer <ID> <答案>提交答案

工作故事

每个工作完成后会解锁一段故事文本。故事文件位于 br_story/base_story/ 目录下,使用 ===STORY_XXX=== 分隔符,格式与 br_story/level_story/ 中的纸条文件相同。

plaintext
backrooms_escape/
├── br_story/base_story/
│   ├── work_W001_story.txt      ← 坐标校准工作故事(占位符)
│   ├── work_W002_story.txt      ← 气候识别工作故事(占位符)
│   ├── work_W003_story.txt      ← 时区推算工作故事(占位符)
│   ├── work_W004_story.txt      ← 气流分析工作故事(占位符)
│   └── work_W005_story.txt      ← 路线规划工作故事(占位符)

人物关系图

/br people_net 命令查看已解锁角色的人物关系图。数据来源为 br_story/people_story/people_relationship.json

  • 仅已解锁的角色会显示完整信息
  • 未解锁的角色显示为 ❓ ???
  • 首次在 Level 1 遇到角色时自动解锁,并赠送 2 瓶杏仁水

如何新增角色

  1. br_story/people_story/ 下新建 .txt 文件,使用 ===CHARACTER_NNN=== 分隔剧情片段
  2. br_story/people_story/people_relationship.json 中添加该角色的数据条目
  3. plugin.py_do_explore 中该角色出现的楼层区域添加触发逻辑
  4. 重载插件生效

自定义故事文本

文件位置

plaintext
backrooms_escape/
├── br_story/
│   ├── level_story/
│   │   └── l1_story.txt ~ l11_story.txt  ← 故事纸条
│   ├── base_story/
│   │   ├── base_work.json                ← 基地工作解谜数据
│   │   └── work_W001_story.txt ~ W005    ← 基地工作故事
│   └── people_story/
│       ├── ankexin.txt                   ← 安可欣剧情
│       ├── anjinian.txt                  ← 安继年剧情
│       ├── people_quests.json            ← 任务数据
│       └── people_relationship.json      ← 人物关系数据
├── backrooms_data.json                   ← 物品/实体数据
├── plugin.py
├── config.py
├── config.toml

格式说明

每个故事文件内使用 ===STORY_NNN=== 作为分隔符,格式相同:

plaintext
===STORY_001===
第一条故事内容……

===STORY_002===
第二条故事内容……

规则

  • 每个故事以 ===STORY_ 开头,后跟编号(任意数字或字母),再以 === 结尾
  • 编号只用于分隔,不影响游戏逻辑(不会显示给玩家)
  • 两个分隔符之间的内容即该条故事的正文
  • 故事正文支持多行文本,会原样展示给玩家
  • 编码使用 UTF-8

如何新增故事

在任意 l*_story.txt 文件末尾追加一个新的故事段:

plaintext
===STORY_009===
你的故事文本写在这里。
可以写多行。
不需要转义,直接写中文即可。

也可以新建一个 l12_story.txt(或任意匹配 l*_story.txt 模式的文件)放入 br_story/level_story/ 目录,插件重载后会自动加载。

如何修改现有故事

直接编辑对应 ===STORY_NNN=== 下方的正文内容。分隔符和编号不要改,只改正文部分。

如何删除故事

删除对应的整个段落(从 ===STORY_NNN=== 到下一个分隔符之前的所有内容)。

注意事项

  1. 插件重载后生效:修改任意故事文件后,需要对插件执行卸载/加载(或重启 MaiBot)才能重新读取。
  2. 不要删除所有故事:如果文件中没有有效故事,玩家将不会再捡到纸条。
  3. 空行处理:分隔符之间的空行会被保留,可以利用空行控制段落间距。
  4. 编号去重:建议使用不重复的编号以便管理,但即使编号重复也不影响程序运行。
  5. 新文件自动识别:任何匹配 l*_story.txt 模式的新文件都会被自动加载,无需修改代码。

人物剧情系统

游戏中,在 Level 1(Alpha 基地)探索时有 40% 概率遇到基地中的 NPC 角色(安可欣、安继年)。首次遇到时对方会赠送 2 瓶杏仁水。每段剧情通过合并转发消息展示。角色在首次遭遇后自动解锁。

自定义角色剧情文件

角色剧情文件位于 br_story/people_story/ 目录,每个 .txt 文件代表一个角色,文件名作为角色 ID。

plaintext
backrooms_escape/
├── br_story/people_story/
│   ├── ankexin.txt             ← 安可欣(信息分析组探员,7段剧情)
│   ├── anjinian.txt            ← 安继年(工程部技师,7段剧情)
│   └── people_relationship.json  ← 人物关系配置文件(JSON 格式)
└── ...

文件格式

使用 ===CHARACTER_NNN=== 作为分隔符,格式与故事纸条相同:

plaintext
===CHARACTER_001===
你走进了 Alpha 基地的休息区……

===CHARACTER_002===

数据持久化(存档系统)

插件的玩家游戏进度会自动保存到磁盘,即使插件重载或 MaiBot 重启也不会丢失进度。

存档位置

存档文件存放在插件目录下的 br_data/ 文件夹中:

plaintext
backrooms_escape/
├── br_data/              ← 存档目录(插件首次加载时自动创建)
│   ├── 123456789.json    ← 用户 QQ 号对应的存档文件
│   └── 987654321.json
├── plugin.py
├── config.py
└── ...

每个玩家对应一个 JSON 文件,文件名即用户 ID。

存档文件结构

json
{
  "user_id": "123456789",
  "current_level": 5,
  "health": 85,
  "sanity": 72,
  "inventory": [
    {
      "name": "o1",
      "type": "consumable",
      "effect": "sanity_restore",
      "value": 30,
      "display_name": "杏仁水",
      "description": "后室中最常见的补给品…"
    }
  ],
  "exit_attempts": 2,
  "pending_note": null,
  "state": "ALIVE",
  "unlocked_chars": ["ankexin"],
  "currency": 85,
  "active_quests": ["M001"],
  "completed_quests": [],
  "pending_quest_offer": null,
  "available_works": ["W001", "W002"],
  "completed_works": [],
  "work_stories": []
}
字段类型说明
user_idstring玩家 QQ 号
current_levelint当前所在楼层
healthint当前生命值
sanityint当前理智值
inventoryarray背包物品列表
statestring游戏状态:"NOT_STARTED" / "ALIVE" / "AT_399" / "DEAD" / "ESCAPED"
exit_attemptsint当前楼层寻找出口的累计次数
pending_notestring|null待阅读的纸条内容,无则为 null
unlocked_charsarray已解锁的角色 ID 列表
currencyintM.E.G.CN 贡献点余额
active_questsarray进行中的任务 ID 列表
completed_questsarray已完成的任务 ID 列表
pending_quest_offerstring|null待接受的任务 ID
available_worksarray基地可用工作 ID 列表
completed_worksarray已完成的工作 ID 列表
work_storiesarray已解锁的工作故事 ID 列表

自动保存时机

以下操作完成后会自动保存存档:

时机触发操作
开始新游戏/br start
探索完成/br explore(无论是否遇敌/获得物品)
寻找出口完成/br exit(无论是否找到出口)
使用物品后/br use <ID>
查看状态后/br status
接受/提交任务/br quest accept <ID> / /br quest submit <ID>
完成基地工作/br work answer <ID> <答案>
插件卸载插件被重载或 MaiBot 关闭时,批量保存所有在线玩家

存档清除时机

以下情况会自动删除存档文件:

  • 玩家生命值归零死亡 → 存档被删除,需 /br start 重新开始
  • 玩家到达 Level 399 通关 → 存档被删除,可 /br start 重新挑战

存档恢复

插件每次加载时(包括首次安装和重启),会自动扫描 br_data/ 目录下的所有 .json 文件,将有效存档恢复到内存中。玩家无需手动操作,直接使用 /br status 即可继续之前的游戏。

注意事项

  1. 不要手动编辑存档:手动修改 JSON 文件可能导致数据格式错误,插件会丢弃损坏的存档并在日志中记录错误。
  2. 删除存档:如需强制清除某玩家的进度,直接删除 br_data/ 下对应的 .json 文件,然后重载插件即可。
  3. 跨设备迁移:将 br_data/ 文件夹整体复制到另一台机器的同插件目录下,即可迁移所有玩家存档。