diff --git a/src/Company/Companies.ts b/src/Company/Companies.ts index 1c1716ff2..99fbe9d00 100644 --- a/src/Company/Companies.ts +++ b/src/Company/Companies.ts @@ -3,7 +3,7 @@ import { getCompaniesMetadata } from "./data/CompaniesMetadata"; import { Company } from "./Company"; import { Reviver, assertLoadingType } from "../utils/JSONReviver"; import { CompanyName } from "./Enums"; -import { createEnumKeyedRecord } from "../Types/Record"; +import { PartialRecord, createEnumKeyedRecord } from "../Types/Record"; import { getEnumHelper } from "../utils/EnumHelper"; export const Companies: Record = (() => { @@ -11,6 +11,8 @@ export const Companies: Record = (() => { return createEnumKeyedRecord(CompanyName, (name) => new Company(metadata[name])); })(); +type SavegameCompany = { favor?: number; playerReputation?: number }; + // Used to load Companies map from a save export function loadCompanies(saveString: string): void { const loadedCompanies = JSON.parse(saveString, Reviver) as unknown; @@ -22,9 +24,21 @@ export function loadCompanies(saveString: string): void { if (!loadedCompany) continue; if (typeof loadedCompany !== "object") continue; const company = Companies[loadedCompanyName]; - assertLoadingType(loadedCompany); + assertLoadingType(loadedCompany); const { playerReputation: loadedRep, favor: loadedFavor } = loadedCompany; if (typeof loadedRep === "number" && loadedRep > 0) company.playerReputation = loadedRep; if (typeof loadedFavor === "number" && loadedFavor > 0) company.favor = loadedFavor; } } + +// Most companies are usually at default values, so we'll only save the companies with non-default data +export function getCompaniesSave(): PartialRecord { + const save: PartialRecord = {}; + for (const companyName of getEnumHelper("CompanyName").valueArray) { + const { favor, playerReputation } = Companies[companyName]; + if (favor || playerReputation) { + save[companyName] = { favor: favor || undefined, playerReputation: playerReputation || undefined }; + } + } + return save; +} diff --git a/src/Company/Company.ts b/src/Company/Company.ts index fdbf4e9a5..93247189f 100644 --- a/src/Company/Company.ts +++ b/src/Company/Company.ts @@ -3,8 +3,6 @@ import type { CompanyPosition } from "./CompanyPosition"; import { CompanyName, JobName, FactionName } from "@enums"; import { favorToRep, repToFavor } from "../Faction/formulas/favor"; -import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; - export interface CompanyCtorParams { name: CompanyName; info?: string; @@ -41,8 +39,7 @@ export class Company { playerReputation = 0; favor = 0; - constructor(p?: CompanyCtorParams) { - if (!p) return; + constructor(p: CompanyCtorParams) { this.name = p.name; if (p.info) this.info = p.info; p.companyPositions.forEach((jobName) => this.companyPositions.add(jobName)); @@ -74,19 +71,4 @@ export class Company { const newFavor = repToFavor(totalRep); return newFavor - this.favor; } - - /** Serialize the current object to a JSON save state. */ - toJSON(): IReviverValue { - return Generic_toJSON("Company", this, Company.includedKeys); - } - - /** Initializes a Company from a JSON save state. */ - static fromJSON(value: IReviverValue): Company { - return Generic_fromJSON(Company, value.data, Company.includedKeys); - } - - // Only these 2 keys are relevant to the save file - static includedKeys = ["favor", "playerReputation"] as const; } - -constructorsForReviver.Company = Company; diff --git a/src/Faction/Faction.ts b/src/Faction/Faction.ts index c9a722ed3..f6d959c7e 100644 --- a/src/Faction/Faction.ts +++ b/src/Faction/Faction.ts @@ -1,8 +1,6 @@ import { AugmentationName, FactionName, FactionDiscovery } from "@enums"; import { FactionInfo, FactionInfos } from "./FactionInfo"; import { favorToRep, repToFavor } from "./formulas/favor"; -import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; -import { getKeyList } from "../utils/helpers/getKeyList"; export class Faction { /** @@ -32,7 +30,7 @@ export class Faction { /** Amount of reputation player has with this faction */ playerReputation = 0; - constructor(name = FactionName.Sector12) { + constructor(name: FactionName) { this.name = name; } @@ -77,20 +75,4 @@ export class Faction { const newFavor = repToFavor(totalRep); return newFavor - this.favor; } - - static savedKeys = getKeyList(Faction, { - removedKeys: ["augmentations", "name", "alreadyInvited", "isBanned", "isMember"], - }); - - /** Serialize the current object to a JSON save state. */ - toJSON(): IReviverValue { - return Generic_toJSON("Faction", this, Faction.savedKeys); - } - - /** Initializes a Faction object from a JSON save state. */ - static fromJSON(value: IReviverValue): Faction { - return Generic_fromJSON(Faction, value.data, Faction.savedKeys); - } } - -constructorsForReviver.Faction = Faction; diff --git a/src/Faction/Factions.ts b/src/Faction/Factions.ts index 76008fc01..8642e6150 100644 --- a/src/Faction/Factions.ts +++ b/src/Faction/Factions.ts @@ -4,7 +4,7 @@ import { FactionName, FactionDiscovery } from "@enums"; import { Faction } from "./Faction"; import { Reviver, assertLoadingType } from "../utils/JSONReviver"; -import { createEnumKeyedRecord, getRecordValues } from "../Types/Record"; +import { PartialRecord, createEnumKeyedRecord, getRecordValues } from "../Types/Record"; import { Augmentations } from "../Augmentation/Augmentations"; import { getEnumHelper } from "../utils/EnumHelper"; @@ -18,6 +18,8 @@ for (const aug of getRecordValues(Augmentations)) { } } +type SavegameFaction = { playerReputation?: number; favor?: number; discovery?: FactionDiscovery }; + export function loadFactions(saveString: string, player: PlayerObject): void { const loadedFactions = JSON.parse(saveString, Reviver) as unknown; // This loading method allows invalid data in player save, but just ignores anything invalid @@ -28,7 +30,7 @@ export function loadFactions(saveString: string, player: PlayerObject): void { if (!loadedFaction) continue; const faction = Factions[loadedFactionName]; if (typeof loadedFaction !== "object") continue; - assertLoadingType(loadedFaction); + assertLoadingType(loadedFaction); const { playerReputation: loadedRep, favor: loadedFavor, discovery: loadedDiscovery } = loadedFaction; if (typeof loadedRep === "number" && loadedRep > 0) faction.playerReputation = loadedRep; if (typeof loadedFavor === "number" && loadedFavor > 0) faction.favor = loadedFavor; @@ -56,3 +58,16 @@ export function loadFactions(saveString: string, player: PlayerObject): void { Factions[invitedFaction].discovery = FactionDiscovery.known; } } + +export function getFactionsSave(): PartialRecord { + const save: PartialRecord = {}; + for (const factionName of getEnumHelper("FactionName").valueArray) { + const faction = Factions[factionName]; + const discovery = faction.discovery === FactionDiscovery.unknown ? undefined : faction.discovery; + const { favor, playerReputation } = faction; + if (discovery || favor || playerReputation) { + save[factionName] = { favor: favor || undefined, playerReputation: playerReputation || undefined, discovery }; + } + } + return save; +} diff --git a/src/SaveObject.ts b/src/SaveObject.ts index 29d4c3b0d..918b13a52 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -1,9 +1,9 @@ import { Skills } from "@nsdefs"; import { loadAliases, loadGlobalAliases, Aliases, GlobalAliases } from "./Alias"; -import { Companies, loadCompanies } from "./Company/Companies"; +import { getCompaniesSave, loadCompanies } from "./Company/Companies"; import { CONSTANTS } from "./Constants"; -import { Factions, loadFactions } from "./Faction/Factions"; +import { getFactionsSave, loadFactions } from "./Faction/Factions"; import { loadAllGangs, AllGangs } from "./Gang/AllGangs"; import { Player, setPlayer, loadPlayer } from "./Player"; import { @@ -26,11 +26,10 @@ import { dialogBoxCreate } from "./ui/React/DialogBox"; import { Reviver, constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "./utils/JSONReviver"; import { save } from "./db"; import { AwardNFG, v1APIBreak } from "./utils/v1APIBreak"; -import { AugmentationName, FactionName, LocationName, ToastVariant } from "@enums"; +import { AugmentationName, LocationName, ToastVariant } from "@enums"; import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation"; import { pushGameSaved } from "./Electron"; import { defaultMonacoTheme } from "./ScriptEditor/ui/themes"; -import { Faction } from "./Faction/Faction"; import { safelyCreateUniqueServer } from "./Server/ServerHelpers"; import { SpecialServers } from "./Server/data/SpecialServers"; import { v2APIBreak } from "./utils/v2APIBreak"; @@ -98,8 +97,8 @@ class BitburnerSaveObject { this.AllServersSave = saveAllServers(); Settings.ExcludeRunningScriptsFromSave = originalExcludeSetting; - this.CompaniesSave = JSON.stringify(Companies); - this.FactionsSave = JSON.stringify(Factions); + this.CompaniesSave = JSON.stringify(getCompaniesSave()); + this.FactionsSave = JSON.stringify(getFactionsSave()); this.AliasesSave = JSON.stringify(Object.fromEntries(Aliases.entries())); this.GlobalAliasesSave = JSON.stringify(Object.fromEntries(GlobalAliases.entries())); this.StockMarketSave = JSON.stringify(StockMarket); @@ -382,7 +381,6 @@ function evaluateVersionCompatibility(ver: string | number): void { } //Fix contract names if (ver < 16) { - Factions[FactionName.ShadowsOfAnarchy] = new Faction(FactionName.ShadowsOfAnarchy); //Iterate over all contracts on all servers for (const server of GetAllServers()) { for (const contract of server.contracts) { diff --git a/src/utils/JSONReviver.ts b/src/utils/JSONReviver.ts index 64b7eceb6..cd8f8405a 100644 --- a/src/utils/JSONReviver.ts +++ b/src/utils/JSONReviver.ts @@ -30,15 +30,13 @@ export function Reviver(_key: string, value: unknown): any { if (!ctor) { // Known missing constructors with special handling. switch (value.ctor) { - case "AllServersMap": - console.warn("Converting AllServersMap for v0.43.1"); + case "AllServersMap": // Reviver removed in v0.43.1 + case "Industry": // No longer part of save data since v2.3.0 + case "Employee": // Entire object removed from game in v2.2.0 (employees abstracted) + case "Company": // Reviver removed in v2.6.1 + case "Faction": // Reviver removed in v2.6.1 + console.warn(`Legacy load type ${value.ctor} converted to expected format while loading.`); return value.data; - case "Industry": - console.warn("Converting a corp from pre-2.3"); - return value.data; // Will immediately be overwritten by v2.3 save migration code - case "Employee": - console.warn("Converting a corp from pre-2.2"); - return value.data; // Will immediately be overwritten by v2.3 save migration code } // Missing constructor with no special handling. Throw error. throw new Error(`Could not locate constructor named ${value.ctor}. If the save data is valid, this is a bug.`); diff --git a/test/jest/__snapshots__/FullSave.test.ts.snap b/test/jest/__snapshots__/FullSave.test.ts.snap index 29ea09f1a..bbc438f50 100644 --- a/test/jest/__snapshots__/FullSave.test.ts.snap +++ b/test/jest/__snapshots__/FullSave.test.ts.snap @@ -2,548 +2,26 @@ exports[`Check Save File Continuity CompaniesSave continuity 1`] = ` { - "AeroCorp": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Aevum Police Headquarters": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Alpha Enterprises": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Bachman & Associates": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Blade Industries": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Carmichael Security": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Central Intelligence Agency": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Clarke Incorporated": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "CompuTek": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "DefComm": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "DeltaOne": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "ECorp": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "FoodNStuff": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Four Sigma": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Fulcrum Technologies": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Galactic Cybersystems": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Global Pharmaceuticals": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Helios Labs": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Icarus Microsystems": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Joe's Guns": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "KuaiGong International": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "LexoCorp": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "MegaCorp": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "NWO": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "National Security Agency": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "NetLink Technologies": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, "Noodle Bar": { - "ctor": "Company", - "data": { - "favor": 100, - "playerReputation": 100000, - }, - }, - "Nova Medical": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Omega Software": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "OmniTek Incorporated": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Omnia Cybersystems": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Rho Construction": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Solaris Space Systems": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Storm Technologies": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "SysCore Securities": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Universal Energy": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "VitaLife": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, - }, - "Watchdog Security": { - "ctor": "Company", - "data": { - "favor": 0, - "playerReputation": 0, - }, + "favor": 100, + "playerReputation": 100000, }, } `; exports[`Check Save File Continuity FactionsSave continuity 1`] = ` { - "Aevum": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Bachman & Associates": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "BitRunners": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Blade Industries": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, "Bladeburners": { - "ctor": "Faction", - "data": { - "discovery": "known", - "favor": 0, - "playerReputation": 4000, - }, - }, - "Chongqing": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Church of the Machine God": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Clarke Incorporated": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, + "discovery": "known", + "playerReputation": 4000, }, "CyberSec": { - "ctor": "Faction", - "data": { - "discovery": "known", - "favor": 20, - "playerReputation": 1000000, - }, - }, - "Daedalus": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "ECorp": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Four Sigma": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Fulcrum Secret Technologies": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Illuminati": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Ishima": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "KuaiGong International": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "MegaCorp": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "NWO": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Netburners": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "New Tokyo": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "NiteSec": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "OmniTek Incorporated": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Sector-12": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Shadows of Anarchy": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Silhouette": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, + "discovery": "known", + "favor": 20, + "playerReputation": 1000000, }, "Slum Snakes": { - "ctor": "Faction", - "data": { - "discovery": "known", - "favor": 0, - "playerReputation": 0, - }, - }, - "Speakers for the Dead": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Tetrads": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "The Black Hand": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "The Covenant": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "The Dark Army": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "The Syndicate": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Tian Di Hui": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, - }, - "Volhaven": { - "ctor": "Faction", - "data": { - "discovery": "unknown", - "favor": 0, - "playerReputation": 0, - }, + "discovery": "known", }, } `;