Skip to content

宝石流程

这里详细描述 EmakiGem 四个核心操作的完整流程,以及底层的数据模型。如果你只是写配置,可以跳过这页;如果你想理解系统内部是怎么运作的,或者需要排查问题,这些信息会很有用。

镶嵌流程 (Inlay)

玩家放入装备和宝石 → 点击确认

    ├─ 1. 验证装备和宝石有效性
    │     检查物品非空、非空气

    ├─ 2. 匹配装备模板 (GemItemMatcher)
    │     遍历所有 GemItemDefinition,通过 item_sources / slot_groups / lore_contains 匹配

    ├─ 3. 检查目标孔位
    │     确认选中的孔位已开启且未被占用

    ├─ 4. 检查兼容性
    │     宝石的 socket_compatibility 是否包含孔位的 type
    │     检查 max_same_type 和 max_same_id 限制

    ├─ 5. 计算成功率
    │     使用 inlay_success.rate_formula 或 default_chance

    ├─ 6. 收费时机
    │     根据 failure_action 决定:
    │     - return_gem: 失败前收费(费用不退)
    │     - destroy_gem: 失败前收费
    │     - none: 成功后收费

    ├─ 7. 掷骰判定
    │     随机数 < 成功率 → 成功

    ├─ 成功路径:
    │     ├─ 8a. 更新 GemState(添加 socketAssignment)
    │     ├─ 9a. 重建物品(applyState)
    │     └─ 10a. 消耗宝石物品

    └─ 失败路径:
          ├─ failure_action = return_gem → 宝石退回玩家
          ├─ failure_action = destroy_gem → 宝石销毁
          └─ failure_action = none → 宝石退回

注意第 6 步的收费时机:如果 failure_actionreturn_gemdestroy_gem,费用在判定之前就扣了,失败也不退。这是为了防止玩家零成本反复尝试。而 none 模式下只有成功才收费,因为宝石本身就会退回。

开孔流程 (Open)

玩家放入装备和开孔器 → 点击确认

    ├─ 1. 匹配开孔器 (SocketOpenerService)
    │     遍历 socket_openers 配置,匹配 item_source

    ├─ 2. 查找第一个未开启的兼容孔位
    │     遍历装备模板的 slots,找到 type 在 opens_gem_types 中且未开启的孔位

    ├─ 3. 更新 GemState(添加 openedSlotIndex)

    ├─ 4. 重建物品(applyState)

    ├─ 5. 消耗开孔器(如果 consume_on_success = true)

    └─ 6. 执行 success_actions

开孔是按顺序来的——系统会找到第一个未开启且类型兼容的孔位。所以孔位在模板中的定义顺序很重要,它决定了开孔的先后。

提取流程 (Extract)

玩家选择要提取的孔位 → 点击确认

    ├─ 1. 验证选中孔位有宝石

    ├─ 2. 收取提取费用 (extract_cost)

    ├─ 3. 确定返还模式 (extract_return.mode)
    │     ├─ exact: 返还原宝石(含等级)
    │     ├─ downgrade: 返还降级宝石
    │     └─ degraded: 概率返还降级或原宝石

    ├─ 4. 更新 GemState(移除 socketAssignment,孔位保持开启)

    ├─ 5. 重建物品(applyState)

    └─ 6. 返还宝石到玩家背包

提取后孔位仍然是开启状态,不需要重新开孔。这是有意为之——开孔是对装备的永久改造。

升级流程 (Upgrade)

玩家放入宝石 → 确认升级

    ├─ 1. 验证宝石有效性
    │     检查宝石定义存在且 upgrade.enabled = true

    ├─ 2. 检查等级上限
    │     当前等级 < max_level

    ├─ 3. 验证升级材料
    │     检查 economy.currencies 和材料是否满足

    ├─ 4. 收取费用
    │     扣除货币和消耗材料

    ├─ 5. 掷骰判定
    │     使用 success_rates[next_level] 或 global_success_rates

    ├─ 成功:
    │     生成新等级的宝石物品,替换原宝石

    └─ 失败:
          根据 failure_penalty / global_failure_penalty:
          ├─ none: 保持当前等级
          ├─ downgrade: 降低一级
          └─ destroy: 销毁宝石

升级的成功率查找顺序:先看宝石定义中的 success_rates,没有再看全局的 global_success_rates。失败惩罚同理。这样你可以为特殊宝石设置独立的升级规则。

applyState 内部流程

applyState 是宝石系统的核心重建流程。每次宝石状态发生变化(镶嵌、提取、开孔),都会调用它来重新生成物品的展示和属性:

1. GemSnapshotBuilder 构建宝石层快照
   ├─ 遍历所有 socketAssignments
   ├─ 为每个镶嵌的宝石收集 stats / attributes
   ├─ 构建 structured_presentation 数据
   └─ 生成 LayerSnapshot

2. AssemblyService.preview 预览重建
   ├─ 将宝石层快照与其他层(锻造、强化等)合并
   ├─ 渲染名称贡献(name_contributions)
   └─ 渲染 Lore 段落(lore_sections)

3. GemPdcAttributeWriter 写入属性
   ├─ 将宝石属性写入 PDC(通过 PdcAttributeApi)
   └─ 仅在 EmakiAttribute 可用时执行

GemState 数据模型

宝石状态数据存储在装备 PDC 中,记录了所有孔位的开启和镶嵌情况。详细的数据结构定义和编程接口参见 API 与集成 页面。

GemItemInstance

每个镶嵌的宝石实例包含:

字段类型说明
gemIdstring宝石定义 ID
levelint宝石等级
tokenstring唯一标识令牌,格式为 {gemId}:{level}:{随机串}

token 的作用是唯一标识一次镶嵌操作,即使两颗相同的宝石镶嵌到同一件装备上,它们的 token 也不同。

PDC 序列化格式

yaml
# 存储在物品 PDC 中的审计数据
item_definition_id: "diamond_sword"
opened_slots: [0, 1, 2]
socket_assignments:
  0:
    gem_id: "ruby_attack"
    level: 3
    token: "ruby_attack:3:abc123"
  1:
    gem_id: "sapphire_defense"
    level: 1
    token: "sapphire_defense:1:def456"
updated_at: 1700000000000

提示

GemState 是不可变记录类型(record),每次修改都会创建新实例。withOpenedSlots()withAssignment() 方法返回新的 GemState 对象,原对象不受影响。

注意

applyState 会触发完整的物品重建流程,包括所有已注册的装配层(宝石、锻造、强化等)。这保证了跨模块状态的一致性,但也意味着每次宝石操作都会刷新整个物品的名称和 Lore。如果你发现物品展示异常,可以检查是否有其他装配层的配置冲突。

Released under the GPL-3.0 License