summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Kernel/WorldSave.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Kernel/WorldSave.fs')
-rw-r--r--src/LivingVillage.Kernel/WorldSave.fs72
1 files changed, 68 insertions, 4 deletions
diff --git a/src/LivingVillage.Kernel/WorldSave.fs b/src/LivingVillage.Kernel/WorldSave.fs
index e68a45f..2d3088c 100644
--- a/src/LivingVillage.Kernel/WorldSave.fs
+++ b/src/LivingVillage.Kernel/WorldSave.fs
@@ -113,10 +113,16 @@ module WorldSave =
let private writeItemKind tokens item =
match item with
| Food -> add tokens "food"
+ | Fish -> add tokens "fish"
+ | Spice -> add tokens "spice"
+ | Scroll -> add tokens "scroll"
let private readItemKind (reader: TokenReader) label =
match reader.Take(label) with
| "food" -> Food
+ | "fish" -> Fish
+ | "spice" -> Spice
+ | "scroll" -> Scroll
| _ -> invalid (sprintf "invalid %s" label)
let private writeActionKind tokens action =
@@ -467,11 +473,24 @@ module WorldSave =
Inventory = readInventory reader (sprintf "npc[%d].inventory" index)
Mind = readMind reader (sprintf "npc[%d].mind" index) }
- let save (world: World) : string =
+ /// v3 saves are produced only when an occupation is attached (design task
+ /// requires the default (occupation-free) path to stay byte-identical to
+ /// today's v2 output).
+ /// v3 saves are emitted only when an occupation is attached; the default
+ /// (occupation-free) path stays byte-identical to today's v2 output.
+ let saveWith (occupation: Occupation.State option) (world: World) : string =
let tokens = ResizeArray<string>()
- add tokens "LV_WORLD_SAVE_V2"
+ match occupation with
+ | None -> add tokens "LV_WORLD_SAVE_V2"
+ | Some state -> add tokens "LV_WORLD_SAVE_V3"
addInt tokens mapWidthTiles
addInt tokens mapHeightTiles
+ match occupation with
+ | Some state ->
+ add tokens (Occupation.saveToken state.Profile.Kind)
+ addInt tokens state.TaskToken
+ addInt tokens state.StoryStage
+ | None -> ()
addInt64 tokens world.Tick
addFloat tokens world.Time
addUInt64 tokens world.Rng.State
@@ -494,12 +513,30 @@ module WorldSave =
try
let tokens = text.Split([| '|' |], StringSplitOptions.None)
let reader = TokenReader(tokens)
- match reader.Take("format") with
- | "LV_WORLD_SAVE_V2" ->
+ let mutable occupationOption : Occupation.State option = None
+ let versionTaken = reader.Take("format")
+ match versionTaken with
+ | "LV_WORLD_SAVE_V3" ->
+ configureBounds (readInt reader "bounds.w") (readInt reader "bounds.h")
+ | v2 when v2 = "LV_WORLD_SAVE_V2" ->
configureBounds (readInt reader "bounds.w") (readInt reader "bounds.h")
| v1 when v1 = formatVersion ->
configureBounds 64 48
| _ -> invalid "unsupported save format"
+ match versionTaken with
+ | "LV_WORLD_SAVE_V3" ->
+ let kindToken = reader.Take("occupation.kind")
+ let task = readInt reader "occupation.task"
+ let stage = readInt reader "occupation.stage"
+ let kind =
+ match kindToken with
+ | "farmer" -> Some Occupation.Farmer
+ | "fisher" -> Some Occupation.Fisher
+ | "peddler" -> Some Occupation.Peddler
+ | "scholar" -> Some Occupation.Scholar
+ | _ -> None
+ occupationOption <- kind |> Option.map (fun kind -> { Profile = Occupation.profileOf kind; TaskToken = task; StoryStage = stage })
+ | _ -> ()
let tick = readInt64 reader "tick"
let time = readFloat reader "time"
let rng = { State = readUInt64 reader "rng" }
@@ -531,11 +568,38 @@ module WorldSave =
| :? OverflowException as ex -> Error(sprintf "invalid save: %s" ex.Message)
| :? ArgumentException as ex -> Error(sprintf "invalid save: %s" ex.Message)
+ /// v2 output (no occupation) — byte-identical to the historic format.
+ let save (world: World) : string = saveWith None world
+
let saveToFile (path: string) (world: World) : unit =
File.WriteAllText(path, save world, UTF8Encoding(false))
+ let saveToFileWith (occupation: Occupation.State option) (path: string) (world: World) : unit =
+ File.WriteAllText(path, saveWith occupation world, UTF8Encoding(false))
+
let loadFromFile (path: string) : Result<World, string> =
try
File.ReadAllText(path) |> load
with
| :? IOException as ex -> Error(sprintf "could not read save: %s" ex.Message)
+
+ /// v3 companion reader: (world, occupation option); None for v1/v2 or v3 w/o kind.
+ let loadFromFileWith (path: string) : Result<World * Occupation.State option, string> =
+ match loadFromFile path with
+ | Error e -> Error e
+ | Ok world ->
+ try
+ let text = File.ReadAllText path
+ let tokens = text.Split([| '|' |])
+ if tokens.[0] <> "LV_WORLD_SAVE_V3" then
+ Ok(world, None)
+ else
+ match tokens.[3] with
+ | "farmer" -> Ok(world, Some (Occupation.stateOf Occupation.Farmer))
+ | "fisher" -> Ok(world, Some (Occupation.stateOf Occupation.Fisher))
+ | "peddler" -> Ok(world, Some (Occupation.stateOf Occupation.Peddler))
+ | "scholar" -> Ok(world, Some (Occupation.stateOf Occupation.Scholar))
+ | _ -> Ok(world, None)
+ with
+ | :? IOException as ex -> Error(sprintf "could not read save: %s" ex.Message)
+ | :? FormatException as ex -> Error(sprintf "invalid save: %s" ex.Message)