From 2997384403226f9c7a001356f6f6e68b938b187d Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:02:12 -0400 Subject: [PATCH] FACTIONS: Followup changes for Rumors PR (#910) --- src/DevMenu/ui/FactionsDev.tsx | 2 +- src/Faction/Faction.ts | 29 +---- src/Faction/FactionHelpers.tsx | 8 +- src/Faction/Factions.ts | 34 ++++-- src/Faction/ui/FactionsRoot.tsx | 16 +-- src/PersonObjects/Player/PlayerObject.ts | 4 +- .../Player/PlayerObjectGeneralMethods.ts | 57 ++++------ src/SaveObject.ts | 2 +- test/jest/__snapshots__/FullSave.test.ts.snap | 107 +----------------- 9 files changed, 63 insertions(+), 196 deletions(-) diff --git a/src/DevMenu/ui/FactionsDev.tsx b/src/DevMenu/ui/FactionsDev.tsx index a64677e9c..8dbec91b9 100644 --- a/src/DevMenu/ui/FactionsDev.tsx +++ b/src/DevMenu/ui/FactionsDev.tsx @@ -66,7 +66,7 @@ export function FactionsDev(): React.ReactElement { Object.values(Factions).forEach((faction) => { faction.discovery = FactionDiscovery.unknown; }); - Player.factionRumors.length = 0; + Player.factionRumors.clear(); setFactionDiscovery(Factions[factionName].discovery); } diff --git a/src/Faction/Faction.ts b/src/Faction/Faction.ts index e2b3682ea..c9a722ed3 100644 --- a/src/Faction/Faction.ts +++ b/src/Faction/Faction.ts @@ -3,7 +3,6 @@ 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"; -import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; export class Faction { /** @@ -79,31 +78,9 @@ export class Faction { return newFavor - this.favor; } - checkForInvite(p: PlayerObject): boolean { - if (this.isBanned) return false; - if (this.isMember) return false; - if (this.alreadyInvited) return false; - const conditions = this.getInfo().inviteReqs; - if (conditions.length == 0) return false; - for (const condition of conditions) { - if (!condition.isSatisfied(p)) return false; - } - return true; - } - - checkForRumor(p: PlayerObject): boolean { - if (this.isBanned) return false; - if (this.isMember) return false; - if (this.alreadyInvited) return false; - const conditions = this.getInfo().rumorReqs; - if (conditions.length == 0) return false; - for (const condition of conditions) { - if (!condition.isSatisfied(p)) return false; - } - return true; - } - - static savedKeys = getKeyList(Faction, { removedKeys: ["augmentations", "name"] }); + static savedKeys = getKeyList(Faction, { + removedKeys: ["augmentations", "name", "alreadyInvited", "isBanned", "isMember"], + }); /** Serialize the current object to a JSON save state. */ toJSON(): IReviverValue { diff --git a/src/Faction/FactionHelpers.tsx b/src/Faction/FactionHelpers.tsx index c8916be10..1b709c02b 100644 --- a/src/Faction/FactionHelpers.tsx +++ b/src/Faction/FactionHelpers.tsx @@ -43,6 +43,7 @@ export function joinFaction(faction: Faction): void { //Determine what factions you are banned from now that you have joined this faction for (const enemy of factionInfo.enemies) { if (Factions[enemy]) Factions[enemy].isBanned = true; + Player.factionRumors.delete(enemy); } for (let i = 0; i < Player.factionInvitations.length; ++i) { if (Player.factionInvitations[i] == faction.name || Factions[Player.factionInvitations[i]].isBanned) { @@ -50,12 +51,7 @@ export function joinFaction(faction: Faction): void { i--; } } - for (let i = 0; i < Player.factionRumors.length; ++i) { - if (Player.factionRumors[i] == faction.name || Factions[Player.factionRumors[i]].isBanned) { - Player.factionRumors.splice(i, 1); - i--; - } - } + Player.factionRumors.delete(faction.name); } //Returns a boolean indicating whether the player has the prerequisites for the diff --git a/src/Faction/Factions.ts b/src/Faction/Factions.ts index 74ad999f5..e697cdbfa 100644 --- a/src/Faction/Factions.ts +++ b/src/Faction/Factions.ts @@ -1,7 +1,5 @@ -/** - * Initialization and manipulation of the Factions object, which stores data - * about all Factions in the game - */ +import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; + import { FactionName, FactionDiscovery } from "@enums"; import { Faction } from "./Faction"; @@ -20,7 +18,7 @@ for (const aug of getRecordValues(Augmentations)) { } } -export function loadFactions(saveString: string): void { +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 if (!loadedFactions) return; @@ -35,11 +33,27 @@ export function loadFactions(saveString: string): void { if (typeof loadedRep === "number" && loadedRep > 0) faction.playerReputation = loadedRep; if (typeof loadedFavor === "number" && loadedFavor > 0) faction.favor = loadedFavor; if (getEnumHelper("FactionDiscovery").isMember(loadedDiscovery)) faction.discovery = loadedDiscovery; - // Todo, these 3 will be removed from Faction object and savedata after a separate PR changes some data structures on Player to make this unnecessary info to save - if (loadedFaction.alreadyInvited) faction.alreadyInvited = true; - if (loadedFaction.isBanned) faction.isBanned = true; - if (loadedFaction.isMember) faction.isMember = true; - // Fill in knowledge of currently-joined factions from pre-discovery saves if (faction.alreadyInvited || faction.isMember) faction.discovery = FactionDiscovery.known; } + // Load joined factions from player save + for (const joinedFacName of player.factions) { + if (!getEnumHelper("FactionName").isMember(joinedFacName)) { + console.error(`Invalid faction in player save factions array: ${joinedFacName}`); + continue; + } + const faction = Factions[joinedFacName]; + faction.isMember = true; + faction.alreadyInvited = true; + faction.discovery = FactionDiscovery.known; + for (const enemyFacName of faction.getInfo().enemies) Factions[enemyFacName].isBanned = true; + } + // Load invited factions from player save + for (const invitedFaction of player.factionInvitations) { + if (!getEnumHelper("FactionName").isMember(invitedFaction)) { + console.error(`Invalid faction in player save factionInvitations array: ${invitedFaction}`); + continue; + } + Factions[invitedFaction].alreadyInvited = true; + Factions[invitedFaction].discovery = FactionDiscovery.known; + } } diff --git a/src/Faction/ui/FactionsRoot.tsx b/src/Faction/ui/FactionsRoot.tsx index d0f16fe98..f72776ea1 100644 --- a/src/Faction/ui/FactionsRoot.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -51,13 +51,7 @@ const JoinChecklist = (props: { faction: Faction }): React.ReactElement => { return ( <> {info.inviteReqs.map((condition, i) => ( - + ))} ); @@ -226,8 +220,6 @@ export function FactionsRoot(): React.ReactElement { const invitations = Player.factionInvitations.map((facName) => Factions[facName]).filter((faction) => !!faction); - const rumors = Player.factionRumors.map((facName) => Factions[facName]).filter((faction) => !!faction); - return ( @@ -273,14 +265,14 @@ export function FactionsRoot(): React.ReactElement { )} - {rumors.length > 0 && ( + {Player.factionRumors.size > 0 && ( <> Rumors
- {rumors.map((faction) => ( - + {[...Player.factionRumors].map((factionName) => ( + ))}
diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts index 95b66b378..526e92c9f 100644 --- a/src/PersonObjects/Player/PlayerObject.ts +++ b/src/PersonObjects/Player/PlayerObject.ts @@ -21,7 +21,7 @@ import { CompanyName, FactionName, JobName, LocationName } from "@enums"; import { HashManager } from "../../Hacknet/HashManager"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver"; -import { JSONMap } from "../../Types/Jsonable"; +import { JSONMap, JSONSet } from "../../Types/Jsonable"; import { cyrb53 } from "../../utils/StringHelperFunctions"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { CONSTANTS } from "../../Constants"; @@ -38,7 +38,7 @@ export class PlayerObject extends Person implements IPlayer { currentServer = ""; factions: FactionName[] = []; factionInvitations: FactionName[] = []; - factionRumors: FactionName[] = []; + factionRumors = new JSONSet(); hacknetNodes: (HacknetNode | string)[] = []; // HacknetNode object or hostname of Hacknet Server has4SData = false; has4SDataTixApi = false; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts index 89f6e7f73..99b3a5f77 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts @@ -103,7 +103,7 @@ export function prestigeAugmentation(this: PlayerObject): void { this.factions = []; this.factionInvitations = []; - this.factionRumors = []; + this.factionRumors.clear(); // Clear any pending invitation modals InvitationEvent.emit(null); @@ -170,37 +170,17 @@ export function prestigeSourceFile(this: PlayerObject): void { } export function receiveInvite(this: PlayerObject, factionName: FactionName): void { - if ( - this.factionInvitations.includes(factionName) || - Factions[factionName].isMember || - Factions[factionName].isBanned - ) { - return; - } + const faction = Factions[factionName]; + if (faction.alreadyInvited || faction.isMember || faction.isBanned) return; this.factionInvitations.push(factionName); - if (this.factionRumors.includes(factionName)) { - this.factionRumors.splice(this.factionRumors.indexOf(factionName), 1); - } - Factions[factionName].discovery = FactionDiscovery.known; + this.factionRumors.delete(factionName); + faction.discovery = FactionDiscovery.known; } -export function receiveRumor( - this: PlayerObject, - factionName: FactionName, - discovery?: FactionDiscovery | undefined, -): void { - if (Factions[factionName].discovery == FactionDiscovery.unknown) { - Factions[factionName].discovery = discovery || FactionDiscovery.rumored; - } - if ( - this.factionRumors.includes(factionName) || - this.factionInvitations.includes(factionName) || - Factions[factionName].isMember || - Factions[factionName].isBanned - ) { - return; - } - this.factionRumors.push(factionName); +export function receiveRumor(this: PlayerObject, factionName: FactionName): void { + const faction = Factions[factionName]; + if (this.factionRumors.has(factionName) || faction.isMember || faction.isBanned || faction.alreadyInvited) return; + this.factionRumors.add(factionName); } //Calculates skill level progress based on experience. The same formula will be used for every skill @@ -630,15 +610,22 @@ export function reapplyAllSourceFiles(this: PlayerObject): void { this.updateSkillLevels(); } -/*************** Check for Faction Invitations *************/ -//This function sets the requirements to join a Faction. It checks whether the Player meets -//those requirements and will return an array of all factions that the Player should -//receive an invitation to +/** + * Checks whether a player meets the requirements for joining each faction, and returns an array of all invitations the player should receive. + * Also handles receiving rumors for factions if the rumor requirements are met. + */ export function checkForFactionInvitations(this: PlayerObject): Faction[] { const invitedFactions = []; for (const faction of Object.values(Factions)) { - if (faction.checkForInvite(this)) invitedFactions.push(faction); - if (faction.checkForRumor(this)) this.receiveRumor(faction.name); + if (faction.isBanned) continue; + if (faction.isMember) continue; + if (faction.alreadyInvited) continue; + // Handle invites + const { inviteReqs, rumorReqs } = faction.getInfo(); + if (inviteReqs.every((req) => req.isSatisfied(this))) invitedFactions.push(faction); + // Handle rumors + if (faction.discovery !== FactionDiscovery.unknown) continue; + if (rumorReqs.every((req) => req.isSatisfied(this))) this.receiveRumor(faction.name); } return invitedFactions; } diff --git a/src/SaveObject.ts b/src/SaveObject.ts index 288621fbb..5e3e499de 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -716,7 +716,7 @@ function loadGame(saveString: string): boolean { setPlayer(loadPlayer(saveObj.PlayerSave)); loadAllServers(saveObj.AllServersSave); loadCompanies(saveObj.CompaniesSave); - loadFactions(saveObj.FactionsSave); + loadFactions(saveObj.FactionsSave, Player); if (Object.hasOwn(saveObj, "StaneksGiftSave")) { loadStaneksGift(saveObj.StaneksGiftSave); diff --git a/test/jest/__snapshots__/FullSave.test.ts.snap b/test/jest/__snapshots__/FullSave.test.ts.snap index 43775f50c..1e06c2046 100644 --- a/test/jest/__snapshots__/FullSave.test.ts.snap +++ b/test/jest/__snapshots__/FullSave.test.ts.snap @@ -276,374 +276,272 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "Aevum": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Bachman & Associates": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "BitRunners": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Blade Industries": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Bladeburners": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": true, "playerReputation": 4000, }, }, "Chongqing": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Church of the Machine God": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Clarke Incorporated": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "CyberSec": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 20, - "isBanned": false, - "isMember": true, "playerReputation": 1000000, }, }, "Daedalus": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "ECorp": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Four Sigma": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Fulcrum Secret Technologies": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Illuminati": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Ishima": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "KuaiGong International": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "MegaCorp": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "NWO": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Netburners": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "New Tokyo": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "NiteSec": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "OmniTek Incorporated": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Sector-12": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Shadows of Anarchy": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Silhouette": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Slum Snakes": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": true, "playerReputation": 0, }, }, "Speakers for the Dead": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Tetrads": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "The Black Hand": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "The Covenant": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "The Dark Army": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "The Syndicate": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Tian Di Hui": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, "Volhaven": { "ctor": "Faction", "data": { - "alreadyInvited": false, "discovery": "unknown", "favor": 0, - "isBanned": false, - "isMember": false, "playerReputation": 0, }, }, @@ -1272,7 +1170,10 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = ` }, "exploits": [], "factionInvitations": [], - "factionRumors": [], + "factionRumors": { + "ctor": "JSONSet", + "data": [], + }, "factions": [ "Slum Snakes", "CyberSec",