summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 11:45:11 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 11:45:11 +0800
commitb13b28521f4b286d9a750848c7940546cf50adf3 (patch)
tree440a8dc2943d52d17a0dd8db5523680732648802
parent89be1bce62a01c6994c0de4d5c9f05338f3b4d4f (diff)
downloadliving-village-b13b28521f4b286d9a750848c7940546cf50adf3.tar.gz
feat(desktop): 顶部极简状态条
常驻 HUD 新增右上角半透明状态条:天数(world.Tick/ticksPerDay+1)与金钱/精力(直接取自 avatar.Mind.Needs 真实数据,无编造数值源);新增 cornerStatusLines 纯函数与回归测试,UI 文案目录补 天数/金钱/精力(字符表已含,无需重生成图集)。
-rw-r--r--src/LivingVillage.Desktop.Tests/PrototypeTests.fs27
-rw-r--r--src/LivingVillage.Desktop/ChineseText.fs5
-rw-r--r--src/LivingVillage.Desktop/Game.fs13
-rw-r--r--src/LivingVillage.Desktop/Interaction.fs8
4 files changed, 52 insertions, 1 deletions
diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
index ab7ae1a..f1f4aed 100644
--- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
+++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs
@@ -203,6 +203,33 @@ type PrototypeTests () =
Assert.IsTrue(first.Tiles |> List.exists (fun tile -> tile.Kind = (VillageArt.StonePaving)))
[<TestMethod>]
+ member _.CornerStatusShowsDayMoneyAndEnergyFromRealData () =
+ let world = Sim.initialWorldN 42UL 0
+ let lines = M5Interaction.cornerStatusLines world
+ Assert.AreEqual<int>(2, lines.Length)
+ Assert.IsTrue(lines.[0].StartsWith("天数 "), sprintf "day line: %s" lines.[0])
+ Assert.IsTrue(lines.[1].StartsWith("金钱 "), sprintf "money line: %s" lines.[1])
+ Assert.IsTrue(lines.[1].Contains("精力"))
+
+ let nextDay = { world with Tick = world.Tick + Sim.ticksPerDay }
+ Assert.AreNotEqual<string>(lines.[0], M5Interaction.cornerStatusLines nextDay |> List.item 0)
+
+ let richWorld =
+ { world with
+ Avatar =
+ { world.Avatar with
+ Mind =
+ { world.Avatar.Mind with
+ Needs = { world.Avatar.Mind.Needs with Money = 88.0f; Energy = 76.0f } } } }
+ let richLines = M5Interaction.cornerStatusLines richWorld
+ Assert.IsTrue(richLines.[1].Contains("88"), sprintf "money value missing: %s" richLines.[1])
+ Assert.IsTrue(richLines.[1].Contains("76"), sprintf "energy value missing: %s" richLines.[1])
+
+ Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "天数")
+ Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "金钱")
+ Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "精力")
+
+ [<TestMethod>]
member _.TitleScreenLayoutIsDeterministicAndLayered () =
let first = TitleScreen.layout 1280 720
let second = TitleScreen.layout 1280 720
diff --git a/src/LivingVillage.Desktop/ChineseText.fs b/src/LivingVillage.Desktop/ChineseText.fs
index 95ec180..d516432 100644
--- a/src/LivingVillage.Desktop/ChineseText.fs
+++ b/src/LivingVillage.Desktop/ChineseText.fs
@@ -181,7 +181,10 @@ module ChineseText =
"获得"
"单价"
"食物"
- "关闭" ]
+ "关闭"
+ "天数"
+ "金钱"
+ "精力" ]
let layoutText (maxWidth: int) (supported: Set<char>) (text: string) : TextLayout =
let width = max 1 maxWidth
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 219cc96..785a6aa 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -650,6 +650,7 @@ type LivingVillageGame() as this =
let statusView = { m5View with StatusTick = Some hudStatusAt }
let statusLine = M5Interaction.transientStatusLine hudClock statusView |> Option.toList
let allLines = persistentLines @ dialogueLines @ statusLine
+ let cornerLines = M5Interaction.cornerStatusLines world
let panelWidth = min 360 (max 1 (viewport.Width - 32))
let maxLines = max 1 ((viewport.Height - 48) / lineHeight)
let lines = allLines |> List.truncate maxLines
@@ -668,4 +669,16 @@ type LivingVillageGame() as this =
let visible = if line.Length > maxChars then line.Substring(0, maxChars) else line
let color = if index = 0 then Color(220, 235, 220) else Color(190, 205, 210)
PixelText.draw spriteBatch pixel cjkAtlas innerX (panelY + 12 + index * lineHeight) textScale color visible)
+
+ // Minimal top-right status strip: day counter + real money/energy values.
+ let cornerWidth = 232
+ let cornerHeight = List.length cornerLines * lineHeight + 20
+ let cornerX = viewport.Width - cornerWidth - 16
+ let cornerY = 16
+ spriteBatch.Draw(pixel, Rectangle(cornerX, cornerY, cornerWidth, cornerHeight), Color(15, 19, 25, 150))
+ spriteBatch.Draw(pixel, Rectangle(cornerX, cornerY, cornerWidth, 3), Color(92, 180, 190))
+ cornerLines
+ |> List.iteri (fun index line ->
+ let visible = if line.Length > maxChars then line.Substring(0, maxChars) else line
+ PixelText.draw spriteBatch pixel cjkAtlas (cornerX + 10) (cornerY + 10 + index * lineHeight) textScale (Color(220, 235, 220)) visible)
spriteBatch.End()
diff --git a/src/LivingVillage.Desktop/Interaction.fs b/src/LivingVillage.Desktop/Interaction.fs
index 91e09ae..20ffa17 100644
--- a/src/LivingVillage.Desktop/Interaction.fs
+++ b/src/LivingVillage.Desktop/Interaction.fs
@@ -434,6 +434,14 @@ module M5Interaction =
Some(statusText view)
| _ -> None
+ /// Minimal corner status for the persistent HUD: day counter plus money/energy taken
+ /// directly from the avatar's real needs (no invented values).
+ let cornerStatusLines (world: World) : string list =
+ let day = world.Tick / Sim.ticksPerDay + 1L
+ let needs = world.Avatar.Mind.Needs
+ [ sprintf "天数 %d" day
+ sprintf "金钱 %.0f / 精力 %.0f" needs.Money needs.Energy ]
+
let private annalLine (entry: AnnalEntry) : string =
match entry.Kind with
| DialogueAnnal outcome ->