Save system: ceremonial PR #42
							
								
								
									
										155
									
								
								src/save.ts
									
									
									
									
									
								
							
							
						
						
									
										155
									
								
								src/save.ts
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| import { getPlayerProgress } from "./playerprogress"; | import { getPlayerProgress } from "./playerprogress"; | ||||||
| import { getStateManager } from "./statemanager"; | import { getStateManager } from "./statemanager"; | ||||||
| import { getThralls } from "./thralls"; | import { getThralls } from "./thralls"; | ||||||
| import { SaveFileV1, StatCounterV1 } from "./saveformat"; | import { SaveFileV1, mustBeSaveFileV1 } from "./saveformat"; | ||||||
|  |  | ||||||
| export interface SaveFile { | export interface SaveFile { | ||||||
|   version: string; |   version: string; | ||||||
| @@ -10,159 +10,6 @@ export interface SaveFile { | |||||||
|  |  | ||||||
| type SaveSlot = "FLEDGLING_SLOT_1" | "FLEDGLING_SLOT_2"; | type SaveSlot = "FLEDGLING_SLOT_1" | "FLEDGLING_SLOT_2"; | ||||||
|  |  | ||||||
| /// Checks whether obj is a valid save file, as far as we can tell, and returns |  | ||||||
| /// it unchanged if it is, or throws an error if it's not valid. |  | ||||||
| export function mustBeSaveFileV1(obj: unknown): SaveFileV1 { |  | ||||||
|   if (obj === undefined || obj === null) { |  | ||||||
|     throw new Error("nonexistent"); |  | ||||||
|   } |  | ||||||
|   if (typeof obj !== "object") { |  | ||||||
|     throw new Error(`not an object; was ${typeof obj}`); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (!("version" in obj)) { |  | ||||||
|     throw new Error("no magic number"); |  | ||||||
|   } |  | ||||||
|   if (obj.version !== "fledgling_save_v1") { |  | ||||||
|     throw new Error(`bad magic number: ${obj.version}`); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return { |  | ||||||
|     version: "fledgling_save_v1", |  | ||||||
|     revision: mustGetNumber(obj, "revision"), |  | ||||||
|     turn: mustGetNumber(obj, "turn"), |  | ||||||
|     name: mustGetString(obj, "name"), |  | ||||||
|     thrallTemplateId: mustGetNumber(obj, "thrallTemplateId"), |  | ||||||
|     nImprovements: mustGetNumber(obj, "nImprovements"), |  | ||||||
|     stats: mustGetStatCounterV1(obj, "stats"), |  | ||||||
|     talents: mustGetStatCounterV1(obj, "talents"), |  | ||||||
|     isInPenance: mustGetBoolean(obj, "isInPenance"), |  | ||||||
|     wishId: mustGetNumber(obj, "wishId"), |  | ||||||
|     exp: mustGetNumber(obj, "exp"), |  | ||||||
|     blood: mustGetNumber(obj, "blood"), |  | ||||||
|     itemsPurloined: mustGetNumber(obj, "itemsPurloined"), |  | ||||||
|     skillsLearned: mustGetNumberArray(obj, "skillsLearned"), |  | ||||||
|     untrimmedSkillsAvailableIds: mustGetNumberArray( |  | ||||||
|       obj, |  | ||||||
|       "untrimmedSkillsAvailableIds", |  | ||||||
|     ), |  | ||||||
|     thrallsUnlocked: mustGetNumberArray(obj, "thrallsUnlocked"), |  | ||||||
|     thrallDamage: mustGetNumberArray(obj, "thrallDamage"), |  | ||||||
|     thrallsObtainedItem: mustGetNumberArray(obj, "thrallsObtainedItem"), |  | ||||||
|     thrallsDeliveredItem: mustGetNumberArray(obj, "thrallsDeliveredItem"), |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function mustGetNumber(obj: object, key: string): number { |  | ||||||
|   if (obj === null || obj === undefined) { |  | ||||||
|     throw new Error("container absent"); |  | ||||||
|   } |  | ||||||
|   if (typeof obj !== "object") { |  | ||||||
|     throw new Error(`container was not an object; was ${typeof obj}`); |  | ||||||
|   } |  | ||||||
|   if (!(key in obj)) { |  | ||||||
|     throw new Error(`missing number: ${key}`); |  | ||||||
|   } |  | ||||||
|   const dict = obj as { [key: string]: any }; |  | ||||||
|   const val = dict[key]; |  | ||||||
|   if (typeof val !== "number") { |  | ||||||
|     throw new Error(`not a number: ${key}: ${val}`); |  | ||||||
|   } |  | ||||||
|   return val; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function mustGetString(obj: object, key: string): string { |  | ||||||
|   if (obj === null || obj === undefined) { |  | ||||||
|     throw new Error("container absent"); |  | ||||||
|   } |  | ||||||
|   if (typeof obj !== "object") { |  | ||||||
|     throw new Error(`container was not an object; was ${typeof obj}`); |  | ||||||
|   } |  | ||||||
|   if (!(key in obj)) { |  | ||||||
|     throw new Error(`missing number: ${key}`); |  | ||||||
|   } |  | ||||||
|   const dict = obj as { [key: string]: any }; |  | ||||||
|   const val = dict[key]; |  | ||||||
|   if (typeof val !== "string") { |  | ||||||
|     throw new Error(`not a string: ${key}: ${val}`); |  | ||||||
|   } |  | ||||||
|   return val; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function mustGetStatCounterV1(obj: object, key: string): StatCounterV1 { |  | ||||||
|   if (obj === null || obj === undefined) { |  | ||||||
|     throw new Error("container absent"); |  | ||||||
|   } |  | ||||||
|   if (typeof obj !== "object") { |  | ||||||
|     throw new Error(`container was not an object; was ${typeof obj}`); |  | ||||||
|   } |  | ||||||
|   if (!(key in obj)) { |  | ||||||
|     throw new Error(`missing number: ${key}`); |  | ||||||
|   } |  | ||||||
|   const dict = obj as { [key: string]: any }; |  | ||||||
|   const val = dict[key]; |  | ||||||
|   if (typeof val !== "object") { |  | ||||||
|     throw new Error(`not an object: ${key}: ${val}`); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   try { |  | ||||||
|     return { |  | ||||||
|       agi: mustGetNumber(val, "agi"), |  | ||||||
|       int: mustGetNumber(val, "int"), |  | ||||||
|       cha: mustGetNumber(val, "cha"), |  | ||||||
|       psi: mustGetNumber(val, "psi"), |  | ||||||
|     }; |  | ||||||
|   } catch (e) { |  | ||||||
|     let message = "unrecognizable error"; |  | ||||||
|     if (e instanceof Error) { |  | ||||||
|       message = e.message; |  | ||||||
|     } |  | ||||||
|     throw new Error(`reading ${key}: ${message}`); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function mustGetBoolean(obj: object, key: string): boolean { |  | ||||||
|   if (obj === null || obj === undefined) { |  | ||||||
|     throw new Error("container absent"); |  | ||||||
|   } |  | ||||||
|   if (typeof obj !== "object") { |  | ||||||
|     throw new Error(`container was not an object; was ${typeof obj}`); |  | ||||||
|   } |  | ||||||
|   if (!(key in obj)) { |  | ||||||
|     throw new Error(`missing number: ${key}`); |  | ||||||
|   } |  | ||||||
|   const dict = obj as { [key: string]: any }; |  | ||||||
|   const val = dict[key]; |  | ||||||
|   if (typeof val !== "boolean") { |  | ||||||
|     throw new Error(`not boolean: ${key}: ${val}`); |  | ||||||
|   } |  | ||||||
|   return val; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function mustGetNumberArray(obj: object, key: string): number[] { |  | ||||||
|   if (obj === null || obj === undefined) { |  | ||||||
|     throw new Error("container absent"); |  | ||||||
|   } |  | ||||||
|   if (typeof obj !== "object") { |  | ||||||
|     throw new Error(`container was not an object; was ${typeof obj}`); |  | ||||||
|   } |  | ||||||
|   if (!(key in obj)) { |  | ||||||
|     throw new Error(`missing number: ${key}`); |  | ||||||
|   } |  | ||||||
|   const dict = obj as { [key: string]: any }; |  | ||||||
|   const val = dict[key]; |  | ||||||
|   if (typeof val !== "object") { |  | ||||||
|     throw new Error(`not an object: ${key}: ${val}`); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   for (const x of val) { |  | ||||||
|     if (typeof x !== "number") { |  | ||||||
|       throw new Error(`contained non-number item in ${key}: ${val}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   return val; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// The result of attempting to load a V1 save file. | /// The result of attempting to load a V1 save file. | ||||||
| interface SaveFileV1LoadResult { | interface SaveFileV1LoadResult { | ||||||
|   // If present and valid, the loaded file. |   // If present and valid, the loaded file. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user