Skip to content

💾 数据存储

上一页:诊断与协议 · 下一页:术语表

QS 把每个玩家的技能档案持久化到磁盘。本章讲 PlayerSkillProfile 存了什么、落盘的 YAML 长什么样、何时加载/保存、以及哪些状态持久化、哪些只在内存。本文档对应 QS 1.0.22

一句话职责边界:QS 只拥有"技能档案"这一条资源缝(PlayerSkillProfile。玩家职业、玩家等级、法力池、CDR、玩家属性都归 QinhClass


1. 落盘位置

plugins/QinhSkills/players/<UUID>.yml

每个玩家一个文件,按 UUID 命名。PlayerProfileStoreConcurrentHashMap 做内存缓存,按 UUID 索引。


2. PlayerSkillProfile 字段

kotlin
data class PlayerSkillProfile(
    val uuid: UUID,
    val unlocked: MutableSet<String>,            // 已解锁技能 id
    val levels: MutableMap<String, Int>,         // 技能 → 等级
    val activeSlots: MutableMap<Int, String>,    // 槽位 → 技能 id
    val cooldownUntil: MutableMap<String, Long>, // 技能 → 冷却截止 ms
    val resources: MutableMap<String, Double>,   // 资源池,如 mana
    val castModeOverrides: MutableMap<String, String>,
    val variables: MutableMap<String, String>,   // 持久化技能变量
)
字段含义持久化?
unlocked已解锁技能集合
levels各技能等级(默认 1,下限 1)
activeSlots技能槽 → 技能 id✅(落盘键 slots
cooldownUntil技能 → 冷却截止时间戳✅(落盘键 cooldowns,过期自动过滤)
resources资源池(mana 等)
variables持久化变量
castModeOverrides施法模式覆盖⚠️ 内存(当前 save 未写盘)

所有技能 id 在档案内部统一小写unlock / getLevel / setSlot 等都 .lowercase())。


3. 落盘 YAML 结构

PlayerProfileStore.save 写出的真实结构(注意键名):

yaml
unlocked:
  - fire_wave
  - dash
levels:
  fire_wave: 3
  dash: 1
slots:                # 来自 activeSlots
  '1': fire_wave
  '2': dash
resources:
  mana: 85.0
variables:
  some_key: some_value
cooldowns:            # 来自 cooldownUntil,仅写仍未过期的
  fire_wave: 1718600000000

要点:

  • activeSlots 落盘成 slots.<槽位>cooldownUntil 落盘成 cooldowns.<技能>
  • 冷却只写仍未过期的项savefilter { it.value > now }),加载时再次过滤过期项 —— 因此冷却可防"重登重置"(下面详述)。
  • castModeOverrides 当前不在 save 写出列表中,属内存态。

4. 加载与保存时机

加载

  • 玩家档案在首次访问时懒加载(get(uuid)computeIfAbsent { load(uuid) })。
  • 文件不存在 → 生成默认档案(见下)。

保存

触发说明
玩家退出PlayerLifecycleListener 在 quit 时保存并卸载缓存
API 变更unlock / lock / setLevel / setSlot 调用后立即 save
unload(uuid)从缓存移除前保存一次

QinhSkillsAPI.unlock/lock/setLevel/setSlot 改动会同步写盘。直接改 PlayerSkillProfile 对象(不经 API)的变更,要等到玩家退出或显式 save 才落盘。


5. 默认档案

新玩家(无文件)首次加载得到默认档案,只种一个资源:

kotlin
profile.resources["mana"] = config.getDouble("resources.default_mana", 100.0)

即默认 mana = resources.default_mana(缺省 100)。其余集合为空(无解锁、无等级覆盖、无冷却)。


6. 持久化 vs 内存态(重要)

状态存哪重登后
冷却(cooldownUntil)✅ 落盘保留(按时间戳,防重登刷新)
解锁 / 等级 / 槽位 / 资源 / 变量✅ 落盘保留
充能层数(ChargeTracker)❌ 内存重置
toggle 开关态(ToggleTracker)❌ 内存重置

设计取舍:

  • 冷却落盘,专门防玩家"重登刷新冷却"的刷子行为。
  • 充能 / toggle 在内存ChargeTracker / ToggleTracker),重登归零。toggle 技能重登后默认回到关闭态。

7. 资源缝与 QinhClass 边界

resources 里的 manaQC 接管前的临时占位。当前 QS 拥有这唯一一条资源缝(PlayerSkillProfile.resources):

  • 吟唱 / 释放扣资源也走这同一条缝(不另造池子)。
  • QinhClass 接管资源时,只改这一处 —— 其余门控、占位符、API 不动。

QS 不做(全归 QinhClass):

  • 玩家职业、玩家等级
  • 法力 / 耐力资源池的真正归属(QS 只是临时占位)
  • 冷却缩减(CDR)数值
  • 玩家属性

8. 程序化读写档案

通过 QinhSkillsAPI 间接操作(推荐,自动写盘):

kotlin
QinhSkillsAPI.unlock(player, "fire_wave")        // 解锁 + 保存
QinhSkillsAPI.setLevel(player, "fire_wave", 3)   // 设等级 + 保存
QinhSkillsAPI.setSlot(player, 1, "fire_wave")    // 设槽 + 保存
QinhSkillsAPI.setSlot(player, 1, null)           // 清槽 + 保存
val unlocked = QinhSkillsAPI.isUnlocked(player, "fire_wave")

这些 API 每次都会写盘。对大批量玩家循环调用时注意 I/O 频次。


继续阅读

  • API — 操作档案的 QinhSkillsAPI 方法
  • 占位符 — 档案数据经占位符暴露
  • 诊断与协议 — 冷却/充能不对时如何排查