宝石流程
这里详细描述 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_action 是 return_gem 或 destroy_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
每个镶嵌的宝石实例包含:
| 字段 | 类型 | 说明 |
|---|---|---|
gemId | string | 宝石定义 ID |
level | int | 宝石等级 |
token | string | 唯一标识令牌,格式为 {gemId}:{level}:{随机串} |
token 的作用是唯一标识一次镶嵌操作,即使两颗相同的宝石镶嵌到同一件装备上,它们的 token 也不同。
PDC 序列化格式
# 存储在物品 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。如果你发现物品展示异常,可以检查是否有其他装配层的配置冲突。