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 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