Save system: ceremonial PR #42
@@ -206,7 +206,11 @@ export class PlayerProgress {
 | 
				
			|||||||
    return skillsAvailable.slice(0, 6);
 | 
					    return skillsAvailable.slice(0, 6);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getLearnedSkills() {
 | 
					  getUntrimmedAvailableSkillIds(): number[] {
 | 
				
			||||||
 | 
					    return this.#untrimmedSkillsAvailable.map((s) => s.id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getLearnedSkills() : Skill[] {
 | 
				
			||||||
    let learnedSkills = [];
 | 
					    let learnedSkills = [];
 | 
				
			||||||
    for (let s of this.#skillsLearned.values()) {
 | 
					    for (let s of this.#skillsLearned.values()) {
 | 
				
			||||||
      learnedSkills.push({ id: s });
 | 
					      learnedSkills.push({ id: s });
 | 
				
			||||||
@@ -214,6 +218,10 @@ export class PlayerProgress {
 | 
				
			|||||||
    return learnedSkills;
 | 
					    return learnedSkills;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRawLearnedSkills() : number[] {
 | 
				
			||||||
 | 
					    return [...this.#skillsLearned];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getStats() {
 | 
					  getStats() {
 | 
				
			||||||
    return { ...this.#stats };
 | 
					    return { ...this.#stats };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -233,6 +241,10 @@ export class PlayerProgress {
 | 
				
			|||||||
    return this.#thrallsUnlocked.indexOf(thrall.id) != -1;
 | 
					    return this.#thrallsUnlocked.indexOf(thrall.id) != -1;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getUnlockedThrallIds() : number[] {
 | 
				
			||||||
 | 
					    return [...this.#thrallsUnlocked];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  damageThrall(thrall: Thrall, amount: number) {
 | 
					  damageThrall(thrall: Thrall, amount: number) {
 | 
				
			||||||
    if (amount <= 0.0) {
 | 
					    if (amount <= 0.0) {
 | 
				
			||||||
      throw new Error(`damage must be some positive amount, not ${amount}`);
 | 
					      throw new Error(`damage must be some positive amount, not ${amount}`);
 | 
				
			||||||
@@ -246,6 +258,10 @@ export class PlayerProgress {
 | 
				
			|||||||
      (this.#thrallDamage[thrall.id] ?? 0.0) + amount;
 | 
					      (this.#thrallDamage[thrall.id] ?? 0.0) + amount;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getThrallDamage(thrall: Thrall): number {
 | 
				
			||||||
 | 
					    return this.#thrallDamage[thrall.id] ?? 0.0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getThrallLifeStage(thrall: Thrall): LifeStage {
 | 
					  getThrallLifeStage(thrall: Thrall): LifeStage {
 | 
				
			||||||
    let damage = this.#thrallDamage[thrall.id] ?? 0;
 | 
					    let damage = this.#thrallDamage[thrall.id] ?? 0;
 | 
				
			||||||
    if (damage < 0.5) {
 | 
					    if (damage < 0.5) {
 | 
				
			||||||
@@ -270,6 +286,10 @@ export class PlayerProgress {
 | 
				
			|||||||
    this.#thrallsObtainedItem.push(thrall.id);
 | 
					    this.#thrallsObtainedItem.push(thrall.id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getThrallObtainedItemIds() : number[] {
 | 
				
			||||||
 | 
					    return [...this.#thrallsObtainedItem];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  deliverThrallItem(thrall: Thrall) {
 | 
					  deliverThrallItem(thrall: Thrall) {
 | 
				
			||||||
    if (this.#thrallsDeliveredItem.indexOf(thrall.id) != -1) {
 | 
					    if (this.#thrallsDeliveredItem.indexOf(thrall.id) != -1) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
@@ -277,6 +297,10 @@ export class PlayerProgress {
 | 
				
			|||||||
    this.#thrallsDeliveredItem.push(thrall.id);
 | 
					    this.#thrallsDeliveredItem.push(thrall.id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getThrallDeliveredItemIds() : number[] {
 | 
				
			||||||
 | 
					    return [...this.#thrallsDeliveredItem];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getThrallItemStage(thrall: Thrall): ItemStage {
 | 
					  getThrallItemStage(thrall: Thrall): ItemStage {
 | 
				
			||||||
    if (this.#thrallsDeliveredItem.indexOf(thrall.id) != -1) {
 | 
					    if (this.#thrallsDeliveredItem.indexOf(thrall.id) != -1) {
 | 
				
			||||||
      return ItemStage.Delivered;
 | 
					      return ItemStage.Delivered;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										321
									
								
								src/save.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								src/save.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,321 @@
 | 
				
			|||||||
 | 
					import { getPlayerProgress } from "./playerprogress"
 | 
				
			||||||
 | 
					import { getStateManager } from "./statemanager";
 | 
				
			||||||
 | 
					import { getThralls } from "./thralls";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SaveFile {
 | 
				
			||||||
 | 
					    version: string;
 | 
				
			||||||
 | 
					    revision: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export 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.
 | 
				
			||||||
 | 
					interface SaveFileV1LoadResult {
 | 
				
			||||||
 | 
					    // If present and valid, the loaded file.
 | 
				
			||||||
 | 
					    file : SaveFileV1 | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// A file loading error, if any. If `file` is present, this refers
 | 
				
			||||||
 | 
					    /// to an error reading from the *other* slot. 
 | 
				
			||||||
 | 
					    error: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The slot this file was loaded from, or that a load attempt failed from.
 | 
				
			||||||
 | 
					    /// If multiple load attempts failed and none succeeded, this refers to
 | 
				
			||||||
 | 
					    /// any one attempted slot.
 | 
				
			||||||
 | 
					    slot: SaveSlot;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function readFromSlot(slot: SaveSlot): SaveFileV1LoadResult {
 | 
				
			||||||
 | 
					    var serialized = localStorage.getItem(slot);
 | 
				
			||||||
 | 
					    if (serialized === null) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            file: null,
 | 
				
			||||||
 | 
					            error: null,
 | 
				
			||||||
 | 
					            slot: slot,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            file: mustBeSaveFileV1(JSON.parse(serialized)),
 | 
				
			||||||
 | 
					            error: null,
 | 
				
			||||||
 | 
					            slot: slot,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					        let message = "unidentifiable error";
 | 
				
			||||||
 | 
					        if (e instanceof Error) {
 | 
				
			||||||
 | 
					            message = e.message;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            file: null,
 | 
				
			||||||
 | 
					            error: message,
 | 
				
			||||||
 | 
					            slot: slot,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Reads the more recent valid save file, if either is valid. If no save files
 | 
				
			||||||
 | 
					/// are present, `file` and `error` will both be absent. If an invalid save
 | 
				
			||||||
 | 
					/// file is discovered, `error` will refer to the issue(s) detected.
 | 
				
			||||||
 | 
					function readBestSave(): SaveFileV1LoadResult {
 | 
				
			||||||
 | 
					    const from1 = readFromSlot("FLEDGLING_SLOT_1");
 | 
				
			||||||
 | 
					    const from2 = readFromSlot("FLEDGLING_SLOT_2");
 | 
				
			||||||
 | 
					    if (from1.file && from2.file) {
 | 
				
			||||||
 | 
					        return (from1.file.revision > from2.file.revision) ? from1 : from2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var errors : string[] = [];
 | 
				
			||||||
 | 
					    if (from1.error) {
 | 
				
			||||||
 | 
					        errors = ["slot 1 error: " + from1.error];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (from2.error) {
 | 
				
			||||||
 | 
					        errors.push("slot 2 error: " + from2.error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var msg : string | null = errors.length > 0 ? errors.join("\n") : null;
 | 
				
			||||||
 | 
					    if (from1.file) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            file: from1.file,
 | 
				
			||||||
 | 
					            error: msg,
 | 
				
			||||||
 | 
					            slot: "FLEDGLING_SLOT_1",
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        file: from2.file,
 | 
				
			||||||
 | 
					        error: msg,
 | 
				
			||||||
 | 
					        slot: "FLEDGLING_SLOT_2",
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function save() {
 | 
				
			||||||
 | 
					    const targetSlot : SaveSlot = (readBestSave().slot === "FLEDGLING_SLOT_1") ? "FLEDGLING_SLOT_2" : "FLEDGLING_SLOT_1";
 | 
				
			||||||
 | 
					    return saveIntoSlot(targetSlot);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function extractCurrentState() : SaveFileV1 {
 | 
				
			||||||
 | 
					    const progress = getPlayerProgress();
 | 
				
			||||||
 | 
					    const stateManager = getStateManager();
 | 
				
			||||||
 | 
					    var thrallDamage : number[] = [];
 | 
				
			||||||
 | 
					    const nThralls = getThralls().length;
 | 
				
			||||||
 | 
					    for (let i = 0; i < nThralls; ++i) {
 | 
				
			||||||
 | 
					        thrallDamage.push(progress.getThrallDamage({id: i}));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        version: "fledgling_save_v1",
 | 
				
			||||||
 | 
					        revision: stateManager.nextRevision(),
 | 
				
			||||||
 | 
					        turn: stateManager.getTurn(),
 | 
				
			||||||
 | 
					        name: progress.name,
 | 
				
			||||||
 | 
					        thrallTemplateId: progress.template.id,
 | 
				
			||||||
 | 
					        nImprovements: progress.nImprovements,
 | 
				
			||||||
 | 
					        stats: {
 | 
				
			||||||
 | 
					            agi: progress.getStat("AGI"),
 | 
				
			||||||
 | 
					            int: progress.getStat("INT"),
 | 
				
			||||||
 | 
					            cha: progress.getStat("CHA"),
 | 
				
			||||||
 | 
					            psi: progress.getStat("PSI"),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        talents: {
 | 
				
			||||||
 | 
					            agi: progress.getStat("AGI"),
 | 
				
			||||||
 | 
					            int: progress.getStat("INT"),
 | 
				
			||||||
 | 
					            cha: progress.getStat("CHA"),
 | 
				
			||||||
 | 
					            psi: progress.getStat("PSI"),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        isInPenance: progress.isInPenance,
 | 
				
			||||||
 | 
					        wishId: progress.getWish()?.id ?? -1,
 | 
				
			||||||
 | 
					        exp: progress.getExperience(),
 | 
				
			||||||
 | 
					        blood: progress.getBlood(),
 | 
				
			||||||
 | 
					        itemsPurloined: progress.getItemsPurloined(),
 | 
				
			||||||
 | 
					        skillsLearned: progress.getRawLearnedSkills(),
 | 
				
			||||||
 | 
					        untrimmedSkillsAvailableIds: progress.getUntrimmedAvailableSkillIds(),
 | 
				
			||||||
 | 
					        thrallsUnlocked: progress.getUnlockedThrallIds(),
 | 
				
			||||||
 | 
					        thrallDamage: thrallDamage,
 | 
				
			||||||
 | 
					        thrallsObtainedItem: progress.getThrallObtainedItemIds(),
 | 
				
			||||||
 | 
					        thrallsDeliveredItem: progress.getThrallDeliveredItemIds(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function saveIntoSlot(slot: SaveSlot) {
 | 
				
			||||||
 | 
					    localStorage.setItem(slot, JSON.stringify(extractCurrentState()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function wipeSaves() {
 | 
				
			||||||
 | 
					    localStorage.removeItem("FLEDGLING_SLOT_1");
 | 
				
			||||||
 | 
					    localStorage.removeItem("FLEDGLING_SLOT_2");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -52,6 +52,10 @@ class ThrallsTable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return thralls;
 | 
					    return thralls;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get length(): number {
 | 
				
			||||||
 | 
					    return this.#thralls.length;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export type ThrallData = {
 | 
					export type ThrallData = {
 | 
				
			||||||
  label: string;
 | 
					  label: string;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user