diff options
Diffstat (limited to 'src/LivingVillage.Desktop/Interaction.fs')
| -rw-r--r-- | src/LivingVillage.Desktop/Interaction.fs | 171 |
1 files changed, 130 insertions, 41 deletions
diff --git a/src/LivingVillage.Desktop/Interaction.fs b/src/LivingVillage.Desktop/Interaction.fs index 6f175d6..da78b2e 100644 --- a/src/LivingVillage.Desktop/Interaction.fs +++ b/src/LivingVillage.Desktop/Interaction.fs @@ -52,19 +52,50 @@ module M5Interaction = let private intentName (intent: DialogueIntent) : string = match intent with - | SmallTalk -> "SMALLTALK" - | AskHelp -> "ASK HELP" - | OfferTrade -> "OFFER TRADE" - | Joke -> "JOKE" - | Apologize -> "APOLOGIZE" - | Provoke -> "PROVOKE" + | SmallTalk -> "闲聊" + | AskHelp -> "求助" + | OfferTrade -> "提议交易" + | Joke -> "讲笑话" + | Apologize -> "道歉" + | Provoke -> "挑衅" let private needName (kind: NeedKind) : string = match kind with - | HungerNeed -> "HUNGER" - | EnergyNeed -> "ENERGY" - | SocialNeed -> "SOCIAL" - | MoneyNeed -> "MONEY" + | HungerNeed -> "饥饿" + | EnergyNeed -> "精力" + | SocialNeed -> "社交" + | MoneyNeed -> "金钱" + + let private itemName (item: ItemKind) : string = + match item with + | Food -> "食物" + + let private responseName (response: DialogueResponse) : string = + match response with + | Friendly -> "友好" + | Helpful -> "乐于助人" + | Bargaining -> "讨价还价" + | Amused -> "开心" + | Forgiving -> "原谅" + | Hostile -> "敌意" + | Reserved -> "保留" + | Refused -> "拒绝" + | Offended -> "恼怒" + + let private dialogueFailureName (failure: DialogueFailure) : string = + match failure with + | DialogueTargetNotFound -> "找不到目标" + | DialogueTargetOutOfRange -> "目标不在范围内" + | DialogueTargetUnavailable -> "目标暂时无法互动" + + let private tradeFailureName (failure: TradeFailure) : string = + match failure with + | BuyerNotFound -> "找不到买家" + | SellerNotFound -> "找不到卖家" + | SameParticipant -> "买家和卖家不能是同一人" + | InvalidQuantity -> "数量必须大于零" + | OutOfStock -> "库存不足" + | InsufficientFunds -> "资金不足" let private npcNumber (NpcId id) = id @@ -84,7 +115,7 @@ module M5Interaction = Chronicle = Sim.annalText next Status = sprintf "dialogue intent=%A response=%A" outcome.Intent outcome.Response } | DialogueRejected(failure, next) -> - next, { view with Status = sprintf "dialogue rejected: %A" failure } + next, { view with Status = sprintf "dialogue rejected: %s" (dialogueFailureName failure) } let apply (command: M5Command) (world: World) (view: M5View) : World * M5View = match command with @@ -127,6 +158,19 @@ module M5Interaction = Status = sprintf "chronicle entries=%d" world.Annals.Length } | ClosePanel -> world, { view with Panel = WorldPanel; Menu = None; Status = "panel closed" } + let tradeResultText (request: TradeRequest) (quote: float32 option) (result: TradeResult) : string = + match result with + | TradeSucceeded _ -> + match quote with + | Some unitPrice -> + sprintf "交易成功:%s ×%d,报价 %.2f,总价 %.2f" + (itemName request.Item) + request.Quantity + unitPrice + (unitPrice * float32 request.Quantity) + | None -> sprintf "交易成功:%s ×%d" (itemName request.Item) request.Quantity + | TradeRejected(failure, _) -> sprintf "交易失败:%s" (tradeFailureName failure) + let panelName (view: M5View) : string = match view.Panel with | WorldPanel -> "world" @@ -137,9 +181,9 @@ module M5Interaction = let titleText (view: M5View) : string = match view.Panel with - | WorldPanel -> "E INTERACT | Q OBSERVE | TAB NEEDS | C CHRONICLE | L RELATIONS" - | NeedsPanel -> "NEEDS | TAB CLOSE | Q OBSERVE | C CHRONICLE" - | ObservationPanel -> "OBSERVATION | TAB NEEDS | C CHRONICLE | ESC CLOSE" + | WorldPanel -> "E 互动 | Q 观察 | TAB 需求 | C 年鉴 | L 关系" + | NeedsPanel -> "需求 | TAB 关闭 | Q 观察 | C 年鉴" + | ObservationPanel -> "观察 | TAB 需求 | C 年鉴 | ESC 关闭" | DialoguePanel -> match view.Menu with | Some menu -> @@ -147,38 +191,83 @@ module M5Interaction = menu.Options |> List.mapi (fun index intent -> sprintf "%d %s" (index + 1) (intentName intent)) |> String.concat " | " - sprintf "DIALOGUE TARGET=%A | %s | ESC CLOSE" menu.Target options - | None -> "DIALOGUE RECORDED | E INTERACT | ESC CLOSE" - | ChroniclePanel -> "CHRONICLE | TAB NEEDS | Q OBSERVE | ESC CLOSE" + sprintf "互动:村民 %d | %s | ESC 关闭" (npcNumber menu.Target) options + | None -> "互动已记录 | E 互动 | ESC 关闭" + | ChroniclePanel -> "年鉴 | TAB 需求 | Q 观察 | ESC 关闭" + + let private statusLabel (status: string) : string = + match status with + | "ready" -> "准备就绪" + | "needs panel open" -> "需求面板已打开" + | "needs panel closed" -> "需求面板已关闭" + | "panel closed" -> "面板已关闭" + | value when value.StartsWith("dialogue target=") -> "请选择对话方式" + | value when value.StartsWith("dialogue intent=") -> "对话完成" + | value when value.StartsWith("dialogue rejected:") -> + let reason = value.Substring("dialogue rejected:".Length).Trim() + if String.IsNullOrWhiteSpace reason then "对话失败" else sprintf "对话失败:%s" reason + | value when value.StartsWith("observation") -> "观察完成" + | value when value.StartsWith("chronicle") -> "年鉴已打开" + | value when value.StartsWith("no dialogue") -> "当前没有可用对话" + | value when value.StartsWith("invalid dialogue") -> "对话选项无效" + | value when value.StartsWith("no nearby") -> "附近没有可互动村民" + | _ -> "状态已更新" + + let private annalLine (entry: AnnalEntry) : string = + match entry.Kind with + | DialogueAnnal outcome -> + sprintf "对话:村民 %d 对村民 %d 使用%s,回应%s" + (npcNumber outcome.Actor) + (npcNumber outcome.Target) + (intentName outcome.Intent) + (responseName outcome.Response) + | RumorAnnal rumor -> + sprintf "传闻:村民 %d 传给村民 %d,传播 %d 层" + (npcNumber rumor.Narrator) + (npcNumber rumor.Receiver) + rumor.Depth + | TradeAnnal event -> + match event.Kind with + | TradeEvent(buyer, seller, item, quantity, unitPrice) -> + sprintf "交易:村民 %d 从村民 %d 获得%s ×%d,单价 %.2f" + (npcNumber buyer) + (npcNumber seller) + (itemName item) + quantity + unitPrice + | _ -> "交易记录" - let private chronicleLines (text: string) : string list = - text.Split([| '\n' |], System.StringSplitOptions.RemoveEmptyEntries) |> Array.toList + let private chronicleLines (world: World) : string list = + world.Annals + |> List.rev + |> List.truncate 10 + |> List.map annalLine let panelLines (world: World) (view: M5View) : string list = - let status = if String.IsNullOrWhiteSpace view.Status then "READY" else view.Status.ToUpperInvariant() + let status = if String.IsNullOrWhiteSpace view.Status then "准备就绪" else statusLabel view.Status match view.Panel with | WorldPanel -> - [ "M5 VILLAGE" - "E INTERACT" - "Q OBSERVE" - "TAB NEEDS" - "C CHRONICLE" - "L RELATIONS" + [ "村庄" + "E 互动" + "Q 观察" + "TAB 需求" + "C 年鉴" + "L 关系" status ] | NeedsPanel -> match view.Needs with - | None -> [ "NEEDS"; "NO DATA"; status ] + | None -> [ "需求"; "暂无数据"; status ] | Some panel -> let topNeed = panel.Ranked |> List.tryHead - |> Option.map (fun score -> sprintf "TOP %s" (needName score.Kind)) - |> Option.defaultValue "TOP NONE" - [ "NEEDS" - sprintf "HUNGER %.0f" panel.Needs.Hunger - sprintf "ENERGY %.0f" panel.Needs.Energy - sprintf "SOCIAL %.0f" panel.Needs.Social - sprintf "MONEY %.0f" panel.Needs.Money + |> Option.map (fun score -> sprintf "最紧迫:%s" (needName score.Kind)) + |> Option.defaultValue "暂无紧迫需求" + [ "需求" + sprintf "饥饿 %.0f" panel.Needs.Hunger + sprintf "精力 %.0f" panel.Needs.Energy + sprintf "社交 %.0f" panel.Needs.Social + sprintf "金钱 %.0f" panel.Needs.Money topNeed status ] | ObservationPanel -> @@ -191,21 +280,21 @@ module M5Interaction = |> List.tryHead |> Option.map (fun score -> score.Urgency) |> Option.defaultValue 0.0f - [ sprintf "NPC %d U %.0f" (npcNumber observation.Id) urgency - sprintf "H %.0f E %.0f S %.0f M %.0f" + [ sprintf "村民 %d 紧迫度 %.0f" (npcNumber observation.Id) urgency + sprintf "饥饿 %.0f 精力 %.0f 社交 %.0f 金钱 %.0f" observation.Needs.Hunger observation.Needs.Energy observation.Needs.Social observation.Needs.Money ]) - "OBSERVATION" :: (if rows.IsEmpty then [ "NO NPC IN RANGE" ] else rows) @ [ status ] + "观察" :: (if rows.IsEmpty then [ "范围内暂无村民" ] else rows) @ [ status ] | DialoguePanel -> match view.Menu with - | None -> [ "DIALOGUE RECORDED"; status ] + | None -> [ "互动已记录"; status ] | Some menu -> let options = menu.Options |> List.mapi (fun index intent -> sprintf "%d %s" (index + 1) (intentName intent)) - [ sprintf "DIALOGUE NPC %d" (npcNumber menu.Target) ] @ options @ [ "ESC CLOSE"; status ] + [ sprintf "互动:村民 %d" (npcNumber menu.Target) ] @ options @ [ "ESC 关闭"; status ] | ChroniclePanel -> - let entries = chronicleLines view.Chronicle |> List.truncate 10 - "CHRONICLE" :: (if entries.IsEmpty then [ "NO ENTRIES" ] else entries) @ [ status ] + let entries = chronicleLines world + "年鉴" :: (if entries.IsEmpty then [ "暂无记录" ] else entries) @ [ status ] |
