diff options
Diffstat (limited to 'docs/api-reference/interaction-and-trade.md')
| -rw-r--r-- | docs/api-reference/interaction-and-trade.md | 60 |
1 files changed, 60 insertions, 0 deletions
diff --git a/docs/api-reference/interaction-and-trade.md b/docs/api-reference/interaction-and-trade.md new file mode 100644 index 0000000..bbc0687 --- /dev/null +++ b/docs/api-reference/interaction-and-trade.md @@ -0,0 +1,60 @@ +# 交互与交易(M5Interaction / PlayerTrade) + +源码:`src/LivingVillage.Desktop/Interaction.fs`、`src/LivingVillage.Desktop/PlayerTrade.fs`。 + +## 职责 + +把键盘输入翻译为 M5 面板/对话/交易命令,并维护玩家可见面板与状态文案; +`PlayerTrade` 把职业背包侧车变成世界里可买卖的物品(不改 `Sim.step` 数值路径)。 + +## 对外 API 概览 + +- `type M5Panel = WorldPanel | NeedsPanel | ObservationPanel | DialoguePanel | ChroniclePanel | TaskPanel`。 +- `type M5Command = Interact | Intent1..Intent6 | BuyFromTarget | SellToTarget | AcceptTask | + Observe | ToggleNeeds | ShowChronicle | ShowTaskPanel | ClosePanel`。 +- `type M5View = { Panel; Menu; Needs; Observations; Chronicle; Status; StatusTick; HomeMode; + Prompt; PromptTargetPos; Task: Occupation.State option; Seed: uint64 }`。 +- `M5Interaction.initial : M5View`;`M5Interaction.apply command world view : World * M5View`(public,纯函数); + `panelLines world view`、`statusText view`、`panelName view`、`titleText view`、 + `transientStatusLine nowTick view`、`cornerStatusLines world`、`refreshPrompt world view`。 +- `PlayerTrade.Outcome = { World; Occupation; Succeeded; UnitPrice: float32 option; + Reward: (ItemKind*int) option; Message: string }`。 +- `PlayerTrade.quoteBase / quote`、`PlayerTrade.buy state counterparty item quantity world`、 + `PlayerTrade.sell ...`、`PlayerTrade.buyFood`、`PlayerTrade.sellFirst`。 + +## 单位与量纲 + +- `Status: string` 为**内部 token**(如 `"dialogue target=..."`、`"task|..."`、`"inquiry|..."`), + 经 `statusLabel` 映射为中文;`StatusTick: int64 option` 为显示计时(`statusDisplayDurationTicks = 150L`)。 +- 交易金额/单价为 `float32`,与 `Sim.Needs.Money` 同尺度;数量 `int`(必须 > 0)。 +- 面板快捷键:`1..6` → 六种对话意图;`B` 买入食物×1、`V` 卖出背包首件×1;`Tab` 需求、`Q` 观察、 + `C` 年鉴、`T`/回车 任务面板、`Esc` 关闭;`E` 互动/进屋/出屋。文案见 `ChineseText.requiredUiLabels`。 + +## 不变量 + +1. **apply 纯函数**:返回新 `(World, M5View)`,不改动入参;世界交互仅在 `WorldPanel` 生效 + (`worldInputAllowed`)。 +2. **面板行来源唯一**:所有面板正文由 `panelLines` 生成;文案经 `CjkGlyphAtlas` 全可渲染。 +3. **无伪造状态**:面板只展示 `M5View`/`World` 中真实存在的值(需求、观察、年鉴、任务)。 +4. **交易不改数值路径**:`PlayerTrade` 只追加 `TradeEvent`/`TradeAnnal` 并写双方记忆与背包, + 不动 `Sim.step`/地图/seed;玩家以临时镜像 `NpcId -1` 仅用于报价。 +5. **背包口径**:`Occupation.State.Backpack` 为声明顺序稳定的 `(ItemKind*int) list`,归零移除、新物品追加尾部。 + +## 失败分支 + +- 命令不可用(如非对话面板发 `Intent1`):`applyAllowed` 返回 `(world, view)` 原样,不改变状态。 +- 对话失败 token:`dialogue rejected:<reason>` → 文案「对话失败:<reason>」; + 无可用对话 → 「当前没有可用对话」;无效选项 → 「对话选项无效」;附近无目标 → 「附近没有可互动目标」。 +- 交易失败:`PlayerTrade` 返回 `Succeeded = false` 且 `Message` 为中文原因—— + 「未选择职业」「找不到对方」「数量必须大于零」「库存不足」「无法报价」「对方资金不足」/ + 「背包里没有可卖物品」;对话交易侧失败名为「找不到目标 / 目标不在范围内 / 目标暂时无法互动 / + 找不到买家 / 找不到卖家 / 买家和卖家不能是同一人 / 数量必须大于零 / 库存不足 / 资金不足」。 +- 未知状态 token:`statusLabel` 兜底「状态已更新」。 + +## 算法与性能取舍 + +- `apply` 是纯状态转换:命令 → 校验(`commandAllowed`)→ 执行 → 生成 `Status` token; + 面板文本在帧内按需拼接,无额外缓存。 +- `PlayerTrade` 报价复用 `Sim.quotePrice`(先经 `Occupation.biasedQuote`;无职业恒等 1.0), + 通过「报价世界副本」把玩家镜像成 `Npc`,副本不写回真实世界——避免给 `Avatar` 引入 Inventory 字段。 +- 面板文案统一走既有 CJK 图集,不新增字体/外部素材。 |
