1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
namespace LivingVillage.Desktop
open LivingVillage.Kernel
/// P54 职业切片三:把 Kernel 的当日任务状态机接到 Desktop 运行时(玩家可见 / 可完成)。
///
/// 全部为纯函数:接受任务、按**真实世界事件/信号**推进、给出短中文反馈。
/// 完成判定只复用 Kernel 既有 `Occupation.advanceTask` / `applySignal`
/// (其内部是 `satisfiesCompletion`),绝不按日历索引硬编码计数。
module TaskRuntime =
/// 完成短中文反馈:有设计口径物品奖励时带「+物品×数量」。
let completionText (task: Occupation.DailyTask) (reward: (Sim.ItemKind * int) option) : string =
let headline = sprintf "任务完成:%s" (Occupation.taskNameOf task.TemplateId)
match reward with
| Some(item, quantity) -> sprintf "%s +%s×%d" headline (Occupation.itemNameOf item) quantity
| None -> headline
// ---- P67 任务目标 UI(design §2.4-3 收口后的可见化)----
/// 带目标时的中文目标名:帮工 → 指定村民;翻土 → 自家地块。其余模板/无目标 → None。
let targetText (task: Occupation.DailyTask) : string option =
match task.TemplateId, task.TargetNpc, task.TargetTile with
| Occupation.HelpWork, Some(Sim.NpcId id), _ -> Some(sprintf "村民 %d" id)
| Occupation.TillSoil, _, Some _ -> Some "自家地块"
| _ -> None
/// 任务标题行:有目标时「模板:状态 → 目标」;无目标时与既有文案逐字节一致。
let taskLine (task: Occupation.DailyTask) (stateText: string) : string =
match targetText task with
| Some target -> sprintf "%s:%s → %s" (Occupation.taskNameOf task.TemplateId) stateText target
| None -> sprintf "%s:%s" (Occupation.taskNameOf task.TemplateId) stateText
let private completionMessage
(before: Occupation.DailyTask option)
(after: Occupation.DailyTask option)
(reward: (Sim.ItemKind * int) option)
: string option =
match before, after with
| Some previous, Some current when previous.State = Occupation.Active && current.State = Occupation.Done ->
Some(completionText current reward)
| _ -> None
/// 接受当日任务(Offered → Active);无任务 / 已在进行 / 终态时无变化、无反馈。
let accept (state: Occupation.State) : Occupation.State * string option =
match state.Today with
| Some task when task.State = Occupation.Offered ->
{ state with Today = Some { task with State = Occupation.Active } },
Some(sprintf "已接受任务:%s" (Occupation.taskNameOf task.TemplateId))
| _ -> state, None
/// 用一条既有世界事件推进当日任务;Active → Done 时返回(含奖励的)短中文反馈。
let advanceWithEvent (event: Sim.InteractionEvent) (state: Occupation.State) : Occupation.State * string option =
let before = state.Today
let next, reward = Occupation.advanceTaskWithReward event state
next, completionMessage before next.Today reward
/// 用一条已折叠的任务信号(到达瓦片 / 观察 / 夜在水边)推进当日任务。
let advanceWithSignal (signal: Occupation.TaskSignal) (state: Occupation.State) : Occupation.State * string option =
let before = state.Today
let next, reward = Occupation.applySignalToState signal state
next, completionMessage before next.Today reward
/// 折叠多条信号(后面的不覆盖已得到的完成反馈)。
let foldSignals (signals: Occupation.TaskSignal list) (state: Occupation.State) : Occupation.State * string option =
signals
|> List.fold
(fun (current, found) signal ->
let next, message = advanceWithSignal signal current
next, (match found with Some _ -> found | None -> message))
(state, None)
/// 由真实世界环境折叠出的任务信号:跨瓦片 → 到达;夜 + 水边 → 夜在水边。
/// `previousTile = None`(尚未建立基线)时不产生到达信号,避免开局即误判完成。
let environmentSignals
(isNight: bool)
(nearWater: bool)
(previousTile: (int * int) option)
(tile: int * int)
: Occupation.TaskSignal list =
[ if previousTile.IsSome && previousTile.Value <> tile then yield Occupation.ArrivedAt tile
if isNight && nearWater then yield Occupation.NightAtWater ]
/// 比较两份职业状态,返回本次推进是否让当日任务完成(供交易等路径复用)。
let completedMessageWithReward
(before: Occupation.State option)
(after: Occupation.State option)
(reward: (Sim.ItemKind * int) option)
: string option =
completionMessage
(before |> Option.bind (fun state -> state.Today))
(after |> Option.bind (fun state -> state.Today))
reward
|