summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/Interaction.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 10:12:20 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 10:12:20 +0800
commit9df2da41289f4d0852f86e86eae81ca7f7e7b598 (patch)
treebca1a071e3369a9e2ebbf5e424da3dd45547f2be /src/LivingVillage.Desktop/Interaction.fs
parentc932969539b00a42937f0aa5ed1e10b4e94932a3 (diff)
downloadliving-village-9df2da41289f4d0852f86e86eae81ca7f7e7b598.tar.gz
fix(desktop): 修复 HUD 缺字并统一轻量面板与情境提示
字符表扩充至 197 字(补 间/门/谈/体/屋/返/×/,/:等全部 UI 用字),用 Noto Sans CJK 重新生成图集并逐字校验;新增字符表覆盖全部 UI 文案、瞬态状态 2.5 秒过期、对话完成后回到轻量面板等回归测试;户外与室内统一左下角轻量 HUD,互动日志改为短暂反馈;样例脚本保留 E 交谈 提示与对话菜单停留帧。
Diffstat (limited to 'src/LivingVillage.Desktop/Interaction.fs')
-rw-r--r--src/LivingVillage.Desktop/Interaction.fs65
1 files changed, 57 insertions, 8 deletions
diff --git a/src/LivingVillage.Desktop/Interaction.fs b/src/LivingVillage.Desktop/Interaction.fs
index 58ebdd4..91e09ae 100644
--- a/src/LivingVillage.Desktop/Interaction.fs
+++ b/src/LivingVillage.Desktop/Interaction.fs
@@ -43,6 +43,7 @@ type M5View =
Observations: Observation list
Chronicle: string
Status: string
+ StatusTick: int64 option
HomeMode: HomeMode
Prompt: string option }
@@ -162,6 +163,7 @@ module M5Interaction =
Observations = []
Chronicle = ""
Status = "ready"
+ StatusTick = None
HomeMode = Outside
Prompt = None }
@@ -234,6 +236,10 @@ module M5Interaction =
let private npcNumber (NpcId id) = id
+ let menuNpcNumber (menu: DialogueMenu) : int = npcNumber menu.Target
+
+ let intentLine (intent: DialogueIntent) : string = intentName intent
+
let private chooseIntent (index: int) (world: World) (view: M5View) : World * M5View =
match view.Menu with
| None -> world, { view with Status = "no dialogue menu; press E near an NPC" }
@@ -245,15 +251,36 @@ module M5Interaction =
| DialogueSucceeded(outcome, next) ->
next,
{ view with
- Panel = DialoguePanel
+ Panel = WorldPanel
Menu = None
Chronicle = Sim.annalText next
Prompt = None
- Status = sprintf "dialogue intent=%A response=%A" outcome.Intent outcome.Response }
+ Status = sprintf "dialogue intent=%A response=%A" outcome.Intent outcome.Response
+ StatusTick = Some next.Tick }
| DialogueRejected(failure, next) ->
- next, { view with Status = sprintf "dialogue rejected: %s" (dialogueFailureName failure) }
+ next,
+ { view with
+ Panel = WorldPanel
+ Menu = None
+ Prompt = None
+ Status = sprintf "dialogue rejected: %s" (dialogueFailureName failure)
+ StatusTick = Some next.Tick }
- let apply (command: M5Command) (world: World) (view: M5View) : World * M5View =
+ let private commandAllowed (command: M5Command) (view: M5View) : bool =
+ match command with
+ | ClosePanel -> true
+ | Interact -> view.Panel = WorldPanel
+ | Intent1
+ | Intent2
+ | Intent3
+ | Intent4
+ | Intent5
+ | Intent6 -> view.Panel = DialoguePanel
+ | Observe
+ | ToggleNeeds
+ | ShowChronicle -> view.Panel <> DialoguePanel
+
+ let private applyAllowed (command: M5Command) (world: World) (view: M5View) : World * M5View =
match command with
| Interact ->
match InteractionResolver.resolve world view.HomeMode with
@@ -325,6 +352,20 @@ module M5Interaction =
Status = sprintf "chronicle entries=%d" world.Annals.Length }
| ClosePanel -> world, refreshPrompt world { view with Panel = WorldPanel; Menu = None; Status = "panel closed" }
+ let apply (command: M5Command) (world: World) (view: M5View) : World * M5View =
+ if commandAllowed command view then
+ let nextWorld, nextView = applyAllowed command world view
+ let stamped =
+ if nextView.Status <> view.Status && Option.isNone nextView.StatusTick then
+ { nextView with StatusTick = Some nextWorld.Tick }
+ else
+ nextView
+ nextWorld, stamped
+ else
+ world, view
+
+ let statusDisplayDurationTicks = 150L
+
let tradeResultText (request: TradeRequest) (quote: float32 option) (result: TradeResult) : string =
match result with
| TradeSucceeded _ ->
@@ -384,6 +425,15 @@ module M5Interaction =
| value when value.StartsWith("no nearby") -> "附近没有可互动目标"
| _ -> "状态已更新"
+ let statusText (view: M5View) : string =
+ if String.IsNullOrWhiteSpace view.Status then "准备就绪" else statusLabel view.Status
+
+ let transientStatusLine (nowTick: int64) (view: M5View) : string option =
+ match view.StatusTick with
+ | Some tick when view.Panel = WorldPanel && nowTick - tick < statusDisplayDurationTicks ->
+ Some(statusText view)
+ | _ -> None
+
let private annalLine (entry: AnnalEntry) : string =
match entry.Kind with
| DialogueAnnal outcome ->
@@ -415,12 +465,11 @@ module M5Interaction =
|> List.map annalLine
let panelLines (world: World) (view: M5View) : string list =
- let status = if String.IsNullOrWhiteSpace view.Status then "准备就绪" else statusLabel view.Status
+ let status = statusText view
match view.Panel with
| WorldPanel ->
- [ "村庄" ]
- @ (view.Prompt |> Option.toList)
- @ [ status ]
+ (view.Prompt |> Option.toList)
+ @ (transientStatusLine world.Tick view |> Option.toList)
| NeedsPanel ->
match view.Needs with
| None -> [ "需求"; "暂无数据"; status ]