summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 19:34:32 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 19:34:32 +0800
commit98b6b21c56c7b061578e7d4b56d52450dc7bd575 (patch)
tree8c3ce5912294207d37863c37b15f2bce89c0787f /src/LivingVillage.Desktop
parentc348da258f072bc88ea422cbc534c0831817b3c8 (diff)
downloadliving-village-98b6b21c56c7b061578e7d4b56d52450dc7bd575.tar.gz
feat(desktop): P12 中文每日任务面板——T 键任务页、Offered/Active/Done 三态+职业名,存读档字段级携带职业
Diffstat (limited to 'src/LivingVillage.Desktop')
-rw-r--r--src/LivingVillage.Desktop/Game.fs8
-rw-r--r--src/LivingVillage.Desktop/Interaction.fs37
2 files changed, 39 insertions, 6 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 9fab5f7..cbe2b9d 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -202,7 +202,7 @@ type LivingVillageGame() as this =
member private this.SaveWorld (prefix: string) : bool =
try
- WorldSave.saveToFile savePath world
+ WorldSave.saveToFileWith m5View.Task savePath world
m5View <- { m5View with Status = "save succeeded"; StatusTick = Some world.Tick }
printfn $"{prefix}=ok path={savePath} tick={world.Tick}"
true
@@ -213,10 +213,11 @@ type LivingVillageGame() as this =
false
member private this.LoadWorld (prefix: string) : Result<int64, string> =
- match WorldSave.loadFromFile savePath with
- | Ok nextWorld ->
+ match WorldSave.loadFromFileWith savePath with
+ | Ok (nextWorld, occupation) ->
world <- nextWorld
this.ResetWorldView()
+ m5View <- { m5View with Task = occupation }
printfn $"{prefix}=ok path={savePath} tick={world.Tick}"
Ok world.Tick
| Error failure ->
@@ -384,6 +385,7 @@ type LivingVillageGame() as this =
elif pressed Keys.Q then Some Observe
elif pressed Keys.Tab then Some ToggleNeeds
elif pressed Keys.C then Some ShowChronicle
+ elif pressed Keys.T then Some ShowTaskPanel
else None
match m5Command with
| Some command ->
diff --git a/src/LivingVillage.Desktop/Interaction.fs b/src/LivingVillage.Desktop/Interaction.fs
index 3f04da8..95aa84c 100644
--- a/src/LivingVillage.Desktop/Interaction.fs
+++ b/src/LivingVillage.Desktop/Interaction.fs
@@ -22,6 +22,7 @@ type M5Panel =
| ObservationPanel
| DialoguePanel
| ChroniclePanel
+ | TaskPanel
type M5Command =
| Interact
@@ -34,6 +35,7 @@ type M5Command =
| Observe
| ToggleNeeds
| ShowChronicle
+ | ShowTaskPanel
| ClosePanel
type M5View =
@@ -46,7 +48,8 @@ type M5View =
StatusTick: int64 option
HomeMode: HomeMode
Prompt: string option
- PromptTargetPos: Vec2 option }
+ PromptTargetPos: Vec2 option
+ Task: Occupation.State option }
module InteractionResolver =
@@ -208,7 +211,8 @@ module M5Interaction =
StatusTick = None
HomeMode = Outside
Prompt = None
- PromptTargetPos = None }
+ PromptTargetPos = None
+ Task = None }
let worldInputAllowed (view: M5View) : bool =
view.Panel = WorldPanel
@@ -326,7 +330,8 @@ module M5Interaction =
| Intent6 -> view.Panel = DialoguePanel
| Observe
| ToggleNeeds
- | ShowChronicle -> view.Panel <> DialoguePanel
+ | ShowChronicle
+ | ShowTaskPanel -> view.Panel <> DialoguePanel
let private applyAllowed (command: M5Command) (world: World) (view: M5View) : World * M5View =
match command with
@@ -404,6 +409,13 @@ module M5Interaction =
Prompt = None
PromptTargetPos = None
Status = sprintf "chronicle entries=%d" world.Annals.Length }
+ | ShowTaskPanel ->
+ world,
+ { view with
+ Panel = TaskPanel
+ Prompt = None
+ PromptTargetPos = None
+ Status = "task panel open" }
| ClosePanel -> world, refreshPrompt world { view with Panel = WorldPanel; Menu = None; Status = "panel closed" }
let apply (command: M5Command) (world: World) (view: M5View) : World * M5View =
@@ -440,6 +452,7 @@ module M5Interaction =
| ObservationPanel -> "observation"
| DialoguePanel -> "dialogue"
| ChroniclePanel -> "chronicle"
+ | TaskPanel -> "task"
let titleText (view: M5View) : string =
match view.Panel with
@@ -456,6 +469,7 @@ module M5Interaction =
sprintf "互动:村民 %d | %s | ESC 关闭" (npcNumber menu.Target) options
| None -> "互动已记录 | E 互动 | ESC 关闭"
| ChroniclePanel -> "年鉴 | TAB 需求 | Q 观察 | ESC 关闭"
+ | TaskPanel -> "任务 | TAB 需求 | Q 观察 | ESC 关闭"
let private statusLabel (status: string) : string =
match status with
@@ -474,6 +488,7 @@ module M5Interaction =
if String.IsNullOrWhiteSpace reason then "对话失败" else sprintf "对话失败:%s" reason
| value when value.StartsWith("observation") -> "观察完成"
| value when value.StartsWith("chronicle") -> "年鉴已打开"
+ | "task panel open" -> "任务面板已打开"
| value when value.StartsWith("no dialogue") -> "当前没有可用对话"
| value when value.StartsWith("invalid dialogue") -> "对话选项无效"
| value when value.StartsWith("no nearby") -> "附近没有可互动目标"
@@ -576,3 +591,19 @@ module M5Interaction =
| ChroniclePanel ->
let entries = chronicleLines world
"年鉴" :: (if entries.IsEmpty then [ "暂无记录" ] else entries) @ [ status ]
+ | TaskPanel ->
+ let taskStateText (state: Occupation.TaskState) : string =
+ match state with
+ | Occupation.Offered -> "待接受"
+ | Occupation.Active -> "进行中"
+ | Occupation.Done -> "已完成"
+ | Occupation.Failed -> "已失败"
+ match view.Task with
+ | None -> [ "任务"; "无职业"; status ]
+ | Some occupationState ->
+ let taskLines =
+ match occupationState.Today with
+ | None -> [ "今日暂无任务" ]
+ | Some task ->
+ [ sprintf "%s:%s" (Occupation.taskNameOf task.TemplateId) (taskStateText task.State) ]
+ Occupation.nameOf occupationState.Profile.Kind :: taskLines @ [ status ]