summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Kernel
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-28 22:38:52 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-28 22:38:52 +0800
commitaa3baa953ea5c4f99f5f23cb3eef9400338b61ec (patch)
tree641759916bcf736ea91af48f798a0f7a3a406c99 /src/LivingVillage.Kernel
parent3aa7221453afec24757b0c7e4d352f69af8b395b (diff)
downloadliving-village-aa3baa953ea5c4f99f5f23cb3eef9400338b61ec.tar.gz
p51: 职业系统实现首切片——开局钱/体力/背包侧车 + 当日任务/跨日刷新 + StartNewGameWith 读回(design §2.4-1)
Diffstat (limited to 'src/LivingVillage.Kernel')
-rw-r--r--src/LivingVillage.Kernel/Occupation.fs15
-rw-r--r--src/LivingVillage.Kernel/WorldSave.fs46
2 files changed, 52 insertions, 9 deletions
diff --git a/src/LivingVillage.Kernel/Occupation.fs b/src/LivingVillage.Kernel/Occupation.fs
index c207395..45ef428 100644
--- a/src/LivingVillage.Kernel/Occupation.fs
+++ b/src/LivingVillage.Kernel/Occupation.fs
@@ -194,14 +194,25 @@ module Occupation =
/// 挂在玩家侧的职业状态,随存档 v3 保存(design §2)。
/// Today = 当日任务(§3);TaskToken 保留为 v3 前缀占位扩展位(恒 0,暂未启用)。
+ /// Backpack = 玩家背包侧车(design §2.4-1):开局 = profile.InitialInventory 的纯数据副本,
+ /// 不进 `Sim.World`、不动 `Sim.step` 数值路径;键值对保持声明顺序以稳定序列化。
type State =
{ Profile: Profile
TaskToken: int
Today: DailyTask option
- StoryStage: int }
+ StoryStage: int
+ Backpack: (Sim.ItemKind * int) list }
let stateOf (kind: Kind) : State =
- { Profile = profileOf kind; TaskToken = 0; Today = None; StoryStage = 0 }
+ { Profile = profileOf kind
+ TaskToken = 0
+ Today = None
+ StoryStage = 0
+ Backpack = (profileOf kind).InitialInventory }
+
+ /// 背包内某物品数量(纯函数;无则 0)。
+ let backpackQuantity (item: Sim.ItemKind) (state: State) : int =
+ state.Backpack |> List.sumBy (fun (kind, quantity) -> if kind = item then quantity else 0)
// ---- 既有事件通道 → 任务信号桥(design §3:完成判定统一走 InteractionEvent)----
diff --git a/src/LivingVillage.Kernel/WorldSave.fs b/src/LivingVillage.Kernel/WorldSave.fs
index 7f724a3..3e11810 100644
--- a/src/LivingVillage.Kernel/WorldSave.fs
+++ b/src/LivingVillage.Kernel/WorldSave.fs
@@ -494,6 +494,24 @@ module WorldSave =
DueTick = dueTick
State = state }
+ /// v3 backpack 尾段(design §2.4-1):玩家背包侧车,按声明顺序写入;旧 v3(无此段)读回
+ /// 由 kind 的 `profileOf` 默认值补齐。
+ let private writeBackpack tokens (items: (ItemKind * int) list) =
+ add tokens "backpack"
+ addInt tokens items.Length
+ for item, quantity in items do
+ writeItemKind tokens item
+ addInt tokens quantity
+
+ let private readBackpack (reader: TokenReader) : (ItemKind * int) list =
+ let count = readCount reader "backpack.count"
+ [ for index in 0 .. count - 1 do
+ let item = readItemKind reader (sprintf "backpack[%d].item" index)
+ let quantity = readInt reader (sprintf "backpack[%d].quantity" index)
+ if quantity < 0 then
+ invalid (sprintf "invalid backpack[%d].quantity" index)
+ yield item, quantity ]
+
let private writeMind tokens (value: Mind) =
writeNeeds tokens value.Needs
writePersonality tokens value.Personality
@@ -598,6 +616,7 @@ module WorldSave =
match state.Today with
| Some task -> writeDailyTask tokens task
| None -> ()
+ writeBackpack tokens state.Backpack
| None -> ()
String.Join("|", tokens)
@@ -634,7 +653,14 @@ module WorldSave =
| "peddler" -> Some Occupation.Peddler
| "scholar" -> Some Occupation.Scholar
| _ -> None
- occupationOption <- kind |> Option.map (fun kind -> { Profile = Occupation.profileOf kind; TaskToken = task; Today = None; StoryStage = stage })
+ occupationOption <-
+ kind
+ |> Option.map (fun kind ->
+ { Profile = Occupation.profileOf kind
+ TaskToken = task
+ Today = None
+ StoryStage = stage
+ Backpack = (Occupation.profileOf kind).InitialInventory })
| _ -> ()
let tick = readInt64 reader "tick"
let time = readFloat reader "time"
@@ -650,17 +676,23 @@ module WorldSave =
let rumorStats = rumorWorkingSetStats rumors
let annalCount = readCount reader "annals"
let annals = List.init annalCount (fun index -> readAnnal reader (sprintf "annal[%d]" index))
- let todayTask =
- if reader.Remaining > 0 then
- match reader.Take("task.today") with
- | "task-today" -> Some(readDailyTask reader)
+ let tail =
+ let mutable task : Occupation.DailyTask option = None
+ let mutable backpack : (ItemKind * int) list option = None
+ while reader.Remaining > 0 do
+ match reader.Take("occupation.tail") with
+ | "task-today" -> task <- Some(readDailyTask reader)
+ | "backpack" -> backpack <- Some(readBackpack reader)
| _ -> invalid "trailing save data"
- else None
+ task, backpack
if reader.Remaining <> 0 then
invalid "trailing save data"
let occupation =
occupationOption
- |> Option.map (fun state -> { state with Today = todayTask })
+ |> Option.map (fun state ->
+ { state with
+ Today = fst tail
+ Backpack = snd tail |> Option.defaultValue state.Backpack })
Ok
({ Tick = tick
Time = time