Skip to content

持久化数据总览

文档版本:基于 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_gamedata/nfa_rfm/rfm_game.dat
nfa_rfm/phone/registrydata/nfa_rfm/phone/registry.dat

DATA_NAME 中的 / 直接映射为文件系统路径分隔符。

数据迁移模式

部分类保留了 LEGACY_DATA_NAME,用于从旧版数据文件自动迁移:

  1. 优先按新 DATA_NAME 查找
  2. 如果不存在,按 LEGACY_DATA_NAME 查找
  3. 如果找到旧版数据,通过 load → save → set(DATA_NAME) 迁移到新版
  4. 调用 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.PersistentPhoneRegistrySavedDatanfa_rfm/phone/registry手机注册表:PhoneId → UUID、玩家名、电话号码
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneMessagesSavedDatanfa_rfm/phone/messages短信系统:消息记录和未读线程标记
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneCallHistorySavedDatanfa_rfm/phone/call_history通话记录:主叫/被叫、方向、状态、时间
com.chenxi.chenxi_rfm.server.config.phone.PersistentPhoneConversationCooldownSavedDatanfa_rfm/phone/conversation_cooldown对话冷却:发送者-目标者冷却到期时间

RFM 游戏核心 SavedData

完整限定类名DATA_NAME功能说明
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmGameSavedDatanfa_rfm/rfm_game游戏主状态:state/phase/倒计时/运行时间
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmRoundSavedDatanfa_rfm/rfm_round回合参与者 UUID 快照
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPlayerIdentitySavedDatanfa_rfm/rfm_player_identity玩家身份记录:UUID/原名/昵称/Role
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPlayerCoinsSavedDatanfa_rfm/rfm_player_coins玩家总金币:UUID → 累计金币数
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterRoundSavedDatanfa_rfm/rfm_hunter_round猎人的回合级状态:回合金币、淘汰奖励、复活、猎人设备
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterEntitySavedDatanfa_rfm/rfm_hunter_entityA型猎人实体 UUID 列表
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterEntityBSavedDatanfa_rfm/rfm_hunter_entity_bB型猎人实体 UUID 列表
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterSpawnSavedDatanfa_rfm/rfm_hunter_spawn猎人随机出生点设置
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHunterBoxLockSavedDatanfa_rfm/rfm_hunter_box_lock猎人箱锁:记录已使用过封锁卡的玩家
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmHudSavedDatanfa_rfm/rfm_hudHUD 模式(world/local)和原点坐标
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmNoticeSavedDatanfa_rfm/rfm_notice公告系统:存储公告模板
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPhoneNoticesSavedDatanfa_rfm/rfm_phone_notices手机通知:按玩家 UUID 存储通知记录
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmTriggerSavedDatanfa_rfm/rfm_trigger红石触发器:按维度/坐标保存触发点
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmCoordinateSavedDatanfa_rfm/rfm_coordinate坐标标记:命名坐标点列表
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmLocatorSavedDatanfa_rfm/rfm_locator定位系统:被追踪玩家 UUID 集合和位置缓存
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmAlarmSavedDatanfa_rfm/rfm_alarm警报系统:被警报的玩家 UUID 集合
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmDetectSavedDatanfa_rfm/rfm_detect侦测方块系统:注册/激活/摧毁计数
com.chenxi.chenxi_rfm.server.config.rfm.PersistentRfmPairingSavedDatanfa_rfm/rfm_pairing结对系统:A/B槽位玩家UUID和结对模式

权限与状态 SavedData

完整限定类名DATA_NAME功能说明
com.chenxi.chenxi_rfm.server.config.rfm.PersistentNfaPermissionSavedDatanfa_rfm/nfa_permission_dataNFA 权限系统:三层权限 + 主控互斥
com.chenxi.chenxi_rfm.server.config.rfm.PlayerStateStorenfa_rfm/rfm_player_state玩家运行时状态:RunnerState + HunterState

扩展与工具模组 SavedData

完整限定类名所属模组功能说明
com.chenxi.chenxi_rfm_addition.server.config.group.PersistentGroupSavedDataaddition持久语音群组
com.chenxi.chenxi_rfm_addition.server.config.minigames.PersistentMentalCountdownSavedDataaddition心算读秒持久数据
com.chenxi.rfmtools.server.config.ToolModeSavedDatarfmtools工具模式状态

关键流程

工厂模式的 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 的工作逻辑:

  1. Minecraft 的 DataStorage 检查 data/DATA_NAME.dat 文件是否存在
  2. 如果文件存在 → 反序列化 NBT,调用 load(tag, provider) 方法
  3. 如果文件不存在 → 调用构造器 new() 创建空实例
  4. 返回实例,保存在 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"
    }
  ]
}

注意:旧版中的 RunnerEliminatedHunterCatchableTraitor 等运行时状态字段已剥离至 PlayerStateStorePlayerIdentityRecord 只保留纯身份信息。

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.tomlconfig/nfa_rfm/client.tomlPhoneClientConfig
common.tomlconfig/nfa_rfm/common.tomlCommonConfig
server.toml<world>/serverconfig/nfa_rfm/server.tomlPersistentPhoneServerConfigSavedData + RfmServerConfigStore

此外,RfmTitleWriterConfigManager 使用 JSON 文件存储打字机标题配置,路径为 <server>/nfa_rfm/typewriter_titles/*.json


注意事项 / 限制

  1. setDirty() 是关键:每次数据变更后必须调用 setDirty(),否则数据不会写入磁盘。Minecraft 的 DataStorage 在世界保存时机(自动保存、玩家退出、服务器停止)才会真正将标记为 dirty 的数据序列化到 .dat 文件。
  2. 数据仅在主世界加载:所有 SavedData 通过 server.overworld().getDataStorage() 获取,数据只存在于主世界的 DataStorage 中。
  3. 线程安全:SavedData 的读写均在服务端主线程执行,无需额外的线程同步。
  4. 数据迁移时机:旧版数据迁移在 ServerStartedEvent 时触发(通过 RfmPlayerIdentityManager.migrateLegacyDataIfNeeded()),而非在每次 get() 时检测。
  5. 过期数据清理:部分类在 load() 时自动清理过期数据。例如 PersistentPhoneConversationCooldownSavedData 在加载时清除已过期的冷却记录。
  6. 关注点分离:旧版 PersistentRfmPlayerIdentitySavedData 原包含运行时状态字段,新版将其剥离到独立的 PlayerStateStorePlayerIdentityRecord 只保留纯身份信息(UUID/名称/昵称/角色)。

相关文档