export interface StatCounterV1 { agi: number; int: number; cha: number; psi: number; } export interface SaveFileV1 { version: "fledgling_save_v1"; revision: number; turn: number; name: string; thrallTemplateId: number; nImprovements: number; stats: StatCounterV1; talents: StatCounterV1; isInPenance: boolean; wishId: number; // negative: Wish is absent exp: number; blood: number; itemsPurloined: number; skillsLearned: number[]; untrimmedSkillsAvailableIds: number[]; thrallsUnlocked: number[]; thrallDamage: number[]; // 0: thrall is absent or undamaged thrallsObtainedItem: number[]; thrallsDeliveredItem: number[]; depth: number; } /// 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"), depth: mustGetNumber(obj, "depth"), }; } 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; }