diff options
Diffstat (limited to 'src/LivingVillage.Kernel/WorldSave.fs')
| -rw-r--r-- | src/LivingVillage.Kernel/WorldSave.fs | 72 |
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) |
