Appearance
持久化数据总览
文档版本:基于 2026-06-01 代码分析
概述
逃走中MOD使用 Minecraft 原生的 SavedData 框架(net.minecraft.world.level.saveddata.SavedData)实现数据持久化。所有数据以 NBT 格式存储在 <world>/data/nfa_rfm/ 目录下,总计 26 个 SavedData 类,覆盖手机系统(4个)、RFM 游戏核心(17个)、权限与状态(2个)、扩展模组(2个)、工具模组(1个)。
每个 SavedData 类遵循统一的工厂模式:get(server) 获取/创建实例 → load(tag) 从 NBT 反序列化 → save(tag) 序列化为 NBT → setDirty() 标记待保存。
核心概念
SavedData 生命周期
mermaid
sequenceDiagram
participant Manager as 游戏管理器
participant SD as SavedData 实例
participant DS as DataStorage
participant Disk as .dat 文件
Manager->>SD: get(server)
SD->>DS: computeIfAbsent(factory, DATA_NAME)
alt 文件存在
DS->>Disk: 读取 NBT
Disk-->>DS: CompoundTag
DS->>SD: load(tag)
else 文件不存在
DS->>SD: new 空实例
end
SD-->>Manager: SavedData 实例
Manager->>SD: 修改数据
Manager->>SD: setDirty()
Note over DS: 世界保存时机触发
DS->>SD: save(tag)
SD-->>DS: CompoundTag
DS->>Disk: 写入 .dat 文件数据路径映射
| 数据键(DATA_NAME) | 文件路径 |
|---|---|
nfa_rfm/rfm_game | data/nfa_rfm/rfm_game.dat |
nfa_rfm/phone/registry | data/nfa_rfm/phone/registry.dat |
DATA_NAME 中的 / 直接映射为文件系统路径分隔符。
数据迁移模式
部分类保留了 LEGACY_DATA_NAME,用于从旧版数据文件自动迁移:
- 优先按新
DATA_NAME查找 - 如果不存在,按
LEGACY_DATA_NAME查找 - 如果找到旧版数据,通过
load → save → set(DATA_NAME)迁移到新版 - 调用
deleteLegacyDataFile()删除旧.dat文件
架构设计
SavedData 分布
mermaid
graph TB
subgraph Phone["手机系统 (4个)"]
P1["PersistentPhoneRegistrySavedData<br/>手机注册表"]
P2["PersistentPhoneMessagesSavedData<br/>短信存储"]
P3["PersistentPhoneCallHistorySavedData<br/>通话记录"]
P4["PersistentPhoneConversationCooldownSavedData<br/>对话冷却"]
end
subgraph Game["RFM 游戏核心 (17个)"]
G1["PersistentRfmGameSavedData<br/>游戏主状态"]
G2["PersistentRfmRoundSavedData<br/>回合参与者快照"]
G3["PersistentRfmPlayerIdentitySavedData<br/>玩家身份"]
G4["PersistentRfmPlayerCoinsSavedData<br/>玩家总金币"]
G5["PersistentRfmHunterRoundSavedData<br/>猎人回合状态"]
G6["PersistentRfmHunterEntitySavedData<br/>A型猎人实体列表"]
G7["PersistentRfmHunterEntityBSavedData<br/>B型猎人实体列表"]
G8["PersistentRfmHunterSpawnSavedData<br/>猎人出生点"]
G9["PersistentRfmHunterBoxLockSavedData<br/>猎人箱锁记录"]
G10["PersistentRfmHudSavedData<br/>HUD模式与坐标"]
G11["PersistentRfmNoticeSavedData<br/>公告模板"]
G12["PersistentRfmPhoneNoticesSavedData<br/>手机通知"]
G13["PersistentRfmTriggerSavedData<br/>红石触发器"]
G14["PersistentRfmCoordinateSavedData<br/>坐标标记"]
G15["PersistentRfmLocatorSavedData<br/>定位追踪"]
G16["PersistentRfmAlarmSavedData<br/>警报列表"]
G17["PersistentRfmPairingSavedData<br/>结对系统"]
end
subgraph Perm["权限与状态 (2个)"]
PM1["PersistentNfaPermissionSavedData<br/>NFA权限数据"]
PM2["PlayerStateStore<br/>玩家运行时状态"]
end
subgraph Ext["扩展与工具 (3个)"]
E1["PersistentGroupSavedData<br/>语音群组 (addition)"]
E2["PersistentMentalCountdownSavedData<br/>心算读秒 (addition)"]
E3["ToolModeSavedData<br/>工具模式 (tools)"]
end关键文件
手机系统 SavedData
| 完整限定类名 | DATA_NAME | 功能说明 |
|---|---|---|
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneRegistrySavedData | nfa_rfm/phone/registry | 手机注册表:PhoneId → UUID、玩家名、电话号码 |
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneMessagesSavedData | nfa_rfm/phone/messages | 短信系统:消息记录和未读线程标记 |
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneCallHistorySavedData | nfa_rfm/phone/call_history | 通话记录:主叫/被叫、方向、状态、时间 |
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneConversationCooldownSavedData | nfa_rfm/phone/conversation_cooldown | 对话冷却:发送者-目标者冷却到期时间 |
RFM 游戏核心 SavedData
| 完整限定类名 | DATA_NAME | 功能说明 |
|---|---|---|
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmGameSavedData | nfa_rfm/rfm_game | 游戏主状态:state/phase/倒计时/运行时间 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmRoundSavedData | nfa_rfm/rfm_round | 回合参与者 UUID 快照 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPlayerIdentitySavedData | nfa_rfm/rfm_player_identity | 玩家身份记录:UUID/原名/昵称/Role |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPlayerCoinsSavedData | nfa_rfm/rfm_player_coins | 玩家总金币:UUID → 累计金币数 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterRoundSavedData | nfa_rfm/rfm_hunter_round | 猎人的回合级状态:回合金币、淘汰奖励、复活、猎人设备 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterEntitySavedData | nfa_rfm/rfm_hunter_entity | A型猎人实体 UUID 列表 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterEntityBSavedData | nfa_rfm/rfm_hunter_entity_b | B型猎人实体 UUID 列表 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterSpawnSavedData | nfa_rfm/rfm_hunter_spawn | 猎人随机出生点设置 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterBoxLockSavedData | nfa_rfm/rfm_hunter_box_lock | 猎人箱锁:记录已使用过封锁卡的玩家 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHudSavedData | nfa_rfm/rfm_hud | HUD 模式(world/local)和原点坐标 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmNoticeSavedData | nfa_rfm/rfm_notice | 公告系统:存储公告模板 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPhoneNoticesSavedData | nfa_rfm/rfm_phone_notices | 手机通知:按玩家 UUID 存储通知记录 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmTriggerSavedData | nfa_rfm/rfm_trigger | 红石触发器:按维度/坐标保存触发点 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmCoordinateSavedData | nfa_rfm/rfm_coordinate | 坐标标记:命名坐标点列表 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmLocatorSavedData | nfa_rfm/rfm_locator | 定位系统:被追踪玩家 UUID 集合和位置缓存 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmAlarmSavedData | nfa_rfm/rfm_alarm | 警报系统:被警报的玩家 UUID 集合 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmDetectSavedData | nfa_rfm/rfm_detect | 侦测方块系统:注册/激活/摧毁计数 |
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPairingSavedData | nfa_rfm/rfm_pairing | 结对系统:A/B槽位玩家UUID和结对模式 |
权限与状态 SavedData
| 完整限定类名 | DATA_NAME | 功能说明 |
|---|---|---|
com.chenxi.chenxi_rfm.server.config.rfm.PersistentNfaPermissionSavedData | nfa_rfm/nfa_permission_data | NFA 权限系统:三层权限 + 主控互斥 |
com.chenxi.chenxi_rfm.server.config.rfm.PlayerStateStore | nfa_rfm/rfm_player_state | 玩家运行时状态:RunnerState + HunterState |
扩展与工具模组 SavedData
| 完整限定类名 | 所属模组 | 功能说明 |
|---|---|---|
com.chenxi.chenxi_rfm_addition.server.config.group.PersistentGroupSavedData | addition | 持久语音群组 |
com.chenxi.chenxi_rfm_addition.server.config.minigames.PersistentMentalCountdownSavedData | addition | 心算读秒持久数据 |
com.chenxi.rfmtools.server.config.ToolModeSavedData | rfmtools | 工具模式状态 |
关键流程
工厂模式的 get() 方法
java
// 以 PersistentRfmGameSavedData 为例
public static PersistentRfmGameSavedData get(MinecraftServer server) {
SavedData.Factory<PersistentRfmGameSavedData> factory =
new SavedData.Factory<>(
PersistentRfmGameSavedData::new, // 构造器:创建空实例
PersistentRfmGameSavedData::load // 加载器:从 NBT 反序列化
);
ensureDataDirectory(server); // 确保 data/nfa_rfm/ 目录存在
return server.overworld().getDataStorage()
.computeIfAbsent(factory, DATA_NAME); // 从 DataStorage 获取或创建
}computeIfAbsent 的工作逻辑:
- Minecraft 的
DataStorage检查data/DATA_NAME.dat文件是否存在 - 如果文件存在 → 反序列化 NBT,调用
load(tag, provider)方法 - 如果文件不存在 → 调用构造器
new()创建空实例 - 返回实例,保存在
DataStorage缓存中
数据持久化
核心 SavedData 的 NBT 结构
PersistentRfmGameSavedData(游戏主状态)
json
{
"State": "idle" | "running",
"Phase": "normal" | "paused" | "countdown_frozen",
"TotalSeconds": 3600,
"ElapsedBeforeRunSeconds": 0,
"RunStartMillis": 1700000000000
}PersistentRfmPlayerIdentitySavedData(玩家身份)
json
{
"Players": [
{
"Uuid": "550e8400-e29b-41d4-a716-446655440000",
"OriginalName": "Steve",
"Nickname": "逃跑者A",
"Role": "runner"
}
]
}注意:旧版中的
RunnerEliminated、HunterCatchable、Traitor等运行时状态字段已剥离至PlayerStateStore,PlayerIdentityRecord只保留纯身份信息。
PersistentNfaPermissionSavedData(权限数据)
json
{
"MasterAdminUuid": "550e8400-e29b-41d4-a716-446655440000",
"Permissions": [
{ "Uuid": "660e8400-...", "Level": "game_admin" }
]
}注意:仅存储非
PLAYER等级的玩家,默认PLAYER不写入文件以减少数据体积。
PlayerStateStore(玩家运行时状态)
json
{
"RunnerStates": [
{ "Uuid": "...", "Eliminated": false, "Abstained": false, "Traitor": false, "TraitorCaptureUnlocked": false }
],
"HunterStates": [
{ "Uuid": "...", "Catchable": true, "Type": "NORMAL" }
],
"CanCapture": [
{ "Uuid": "...", "Value": true }
],
"CanBeCaptured": [
{ "Uuid": "...", "Value": true }
]
}PersistentRfmHunterRoundSavedData(猎人回合状态)
json
{
"Hunters": [
{
"Uuid": "...",
"RoundCoins": 500,
"EliminationReward": 200,
"ResurrectAvailable": true,
"HunterDeviceAvailable": true
}
]
}配置文件(非 SavedData 的持久化数据)
以下配置通过 ConfigManager 的 TOML 体系管理,不通过 SavedData:
| 配置文件 | 路径 | 管理类 |
|---|---|---|
client.toml | config/nfa_rfm/client.toml | PhoneClientConfig |
common.toml | config/nfa_rfm/common.toml | CommonConfig |
server.toml | <world>/serverconfig/nfa_rfm/server.toml | PersistentPhoneServerConfigSavedData + RfmServerConfigStore |
此外,RfmTitleWriterConfigManager 使用 JSON 文件存储打字机标题配置,路径为 <server>/nfa_rfm/typewriter_titles/*.json。
注意事项 / 限制
- setDirty() 是关键:每次数据变更后必须调用
setDirty(),否则数据不会写入磁盘。Minecraft 的DataStorage在世界保存时机(自动保存、玩家退出、服务器停止)才会真正将标记为 dirty 的数据序列化到.dat文件。 - 数据仅在主世界加载:所有 SavedData 通过
server.overworld().getDataStorage()获取,数据只存在于主世界的 DataStorage 中。 - 线程安全:SavedData 的读写均在服务端主线程执行,无需额外的线程同步。
- 数据迁移时机:旧版数据迁移在
ServerStartedEvent时触发(通过RfmPlayerIdentityManager.migrateLegacyDataIfNeeded()),而非在每次get()时检测。 - 过期数据清理:部分类在
load()时自动清理过期数据。例如PersistentPhoneConversationCooldownSavedData在加载时清除已过期的冷却记录。 - 关注点分离:旧版
PersistentRfmPlayerIdentitySavedData原包含运行时状态字段,新版将其剥离到独立的PlayerStateStore,PlayerIdentityRecord只保留纯身份信息(UUID/名称/昵称/角色)。