TYPESAFETY: FactionName (#644)

This commit is contained in:
Snarling
2023-06-25 22:53:35 -04:00
committed by GitHub
parent 1de676972f
commit 9a0a843ffc
31 changed files with 295 additions and 751 deletions
+25 -17
View File
@@ -1,8 +1,8 @@
import type { AugmentationName } from "@enums";
import { AugmentationName, FactionName } from "@enums";
import { FactionInfo, FactionInfos } from "./FactionInfo";
import { favorToRep, repToFavor } from "./formulas/favor";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
import { getEnumHelper } from "../utils/EnumHelper";
import { getKeyList } from "../utils/helpers/getKeyList";
export class Faction {
/**
@@ -24,12 +24,12 @@ export class Faction {
isMember = false;
/** Name of faction */
name = "";
name: FactionName;
/** Amount of reputation player has with this faction */
playerReputation = 0;
constructor(name = "") {
constructor(name = FactionName.Sector12) {
this.name = name;
}
@@ -44,11 +44,24 @@ export class Faction {
return info;
}
gainFavor(): void {
if (this.favor == null) {
this.favor = 0;
}
prestigeSourceFile() {
// Reset favor, reputation, and flags
this.favor = 0;
this.playerReputation = 0;
this.alreadyInvited = false;
this.isMember = false;
this.isBanned = false;
}
prestigeAugmentation(): void {
// Gain favor
if (this.favor == null) this.favor = 0;
this.favor += this.getFavorGain();
// Reset reputation and flags
this.playerReputation = 0;
this.alreadyInvited = false;
this.isMember = false;
this.isBanned = false;
}
//Returns an array with [How much favor would be gained, how much rep would be left over]
@@ -62,21 +75,16 @@ export class Faction {
return newFavor - this.favor;
}
static savedKeys = getKeyList(Faction, { removedKeys: ["augmentations", "name"] });
/** Serialize the current object to a JSON save state. */
toJSON(): IReviverValue {
return Generic_toJSON("Faction", this);
return Generic_toJSON("Faction", this, Faction.savedKeys);
}
/** Initializes a Faction object from a JSON save state. */
static fromJSON(value: IReviverValue): Faction {
const faction = Generic_fromJSON(Faction, value.data);
if (!Array.isArray(faction.augmentations)) faction.augmentations = [];
// Remove invalid augs from faction. Augs are repopulated with correct augs during any reset.
const augHelper = getEnumHelper("AugmentationName");
faction.augmentations = faction.augmentations.filter((augName) => augHelper.isMember(augName));
// Fix broken saves, this will soon be removed when better fix is implemented
faction.augmentations = [...new Set(faction.augmentations)];
return faction;
return Generic_fromJSON(Faction, value.data, Faction.savedKeys);
}
}
+9 -7
View File
@@ -1,12 +1,13 @@
import type { Augmentation } from "../Augmentation/Augmentation";
import type { Faction } from "./Faction";
import { Augmentations } from "../Augmentation/Augmentations";
import { Augmentation } from "../Augmentation/Augmentation";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationName, FactionName } from "@enums";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
import { Player } from "@player";
import { Factions } from "./Factions";
import { Settings } from "../Settings/Settings";
import {
getHackingWorkRepGain,
@@ -19,6 +20,7 @@ import { InvitationEvent } from "./ui/InvitationModal";
import { SFC32RNG } from "../Casino/RNG";
import { isFactionWork } from "../Work/FactionWork";
import { getAugCost } from "../Augmentation/AugmentationHelpers";
import { createEnumKeyedRecord, getRecordKeys } from "../Types/Record";
export function inviteToFaction(faction: Faction): void {
Player.receiveInvite(faction.name);
@@ -32,8 +34,9 @@ export function joinFaction(faction: Faction): void {
if (faction.isMember) return;
faction.isMember = true;
Player.factions.push(faction.name);
const allFactions = Object.values(FactionName).map((faction) => faction as string);
Player.factions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b));
let i = 0;
const factionIndexes = createEnumKeyedRecord(FactionName, (__) => i++);
Player.factions.sort((a, b) => factionIndexes[a] - factionIndexes[b]);
const factionInfo = faction.getInfo();
//Determine what factions you are banned from now that you have joined this faction
@@ -108,9 +111,8 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
export function processPassiveFactionRepGain(numCycles: number): void {
if (Player.bitNodeN === 2) return;
for (const name of Object.keys(Factions)) {
for (const name of getRecordKeys(Factions)) {
if (isFactionWork(Player.currentWork) && name === Player.currentWork.factionName) continue;
if (!Object.hasOwn(Factions, name)) continue;
const faction = Factions[name];
if (!faction.isMember) continue;
// No passive rep for special factions
+3 -3
View File
@@ -7,7 +7,7 @@ import { Typography } from "@mui/material";
interface FactionInfoParams {
infoText?: JSX.Element;
enemies?: string[];
enemies?: FactionName[];
offerHackingWork?: boolean;
offerFieldWork?: boolean;
offerSecurityWork?: boolean;
@@ -19,7 +19,7 @@ interface FactionInfoParams {
/** Contains the "information" property for all the Factions, which is just a description of each faction */
export class FactionInfo {
/** The names of all other factions considered to be enemies to this faction. */
enemies: string[];
enemies: FactionName[];
/** The descriptive text to show on the faction's page. */
infoText: JSX.Element;
@@ -60,7 +60,7 @@ export class FactionInfo {
}
/** A map of all factions and associated info to them. */
export const FactionInfos: Record<string, FactionInfo> = {
export const FactionInfos: Record<FactionName, FactionInfo> = {
// Endgame
[FactionName.Illuminati]: new FactionInfo({
infoText: (
+32 -53
View File
@@ -2,63 +2,42 @@
* Initialization and manipulation of the Factions object, which stores data
* about all Factions in the game
*/
import { FactionName } from "@enums";
import { Faction } from "./Faction";
import { FactionInfos } from "./FactionInfo";
import { Reviver } from "../utils/JSONReviver";
import { getRecordValues } from "../Types/Record";
import { Augmentations, initCircadianModulator } from "../Augmentation/Augmentations";
import { Reviver, assertLoadingType } from "../utils/JSONReviver";
import { createEnumKeyedRecord, getRecordValues } from "../Types/Record";
import { Augmentations } from "../Augmentation/Augmentations";
import { getEnumHelper } from "../utils/EnumHelper";
export let Factions: Record<string, Faction> = {};
/** The static list of all factions. Initialized once and never modified. */
export const Factions = createEnumKeyedRecord(FactionName, (name) => new Faction(name));
// Add the associated augs to every faction
for (const aug of getRecordValues(Augmentations)) {
for (const factionName of aug.factions) {
const faction = Factions[factionName];
faction.augmentations.push(aug.name);
}
}
export function loadFactions(saveString: string): void {
Factions = JSON.parse(saveString, Reviver);
// safety check for when we load older save file that don't have newer factions
for (const faction of Object.keys(Factions)) {
try {
Factions[faction].getInfo();
} catch (err) {
console.error("deleting " + faction);
delete Factions[faction];
}
// The only information that should be loaded from player save is
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;
if (typeof loadedFactions !== "object") return;
for (const [loadedFactionName, loadedFaction] of Object.entries(loadedFactions) as [string, unknown][]) {
if (!getEnumHelper("FactionName").isMember(loadedFactionName)) continue;
if (!loadedFaction) continue;
const faction = Factions[loadedFactionName];
if (typeof loadedFaction !== "object") continue;
assertLoadingType<Faction>(loadedFaction);
const { playerReputation: loadedRep, favor: loadedFavor } = loadedFaction;
if (typeof loadedRep === "number" && loadedRep > 0) faction.playerReputation = loadedRep;
if (typeof loadedFavor === "number" && loadedFavor > 0) faction.favor = loadedFavor;
// 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;
}
}
function AddToFactions(faction: Faction): void {
const name: string = faction.name;
Factions[name] = faction;
}
export function factionExists(name: string): boolean {
return Object.hasOwn(Factions, name);
}
export function initFactions(): void {
for (const name of Object.keys(FactionInfos)) {
resetFaction(new Faction(name));
}
// All factions are added, this is a good place to add augs back to factions.
initCircadianModulator();
for (const aug of getRecordValues(Augmentations)) {
for (const factionName of aug.factions) {
const faction = Factions[factionName];
if (!faction) {
console.error(`Faction ${factionName} did not exist while adding augs to factions`);
continue;
}
faction.augmentations.push(aug.name);
}
}
}
//Resets a faction during (re-)initialization. Saves the favor in the new
//Faction object and deletes the old Faction Object from "Factions". Then
//reinserts the new Faction object
function resetFaction(newFactionObject: Faction): void {
const factionName: string = newFactionObject.name;
if (factionExists(factionName)) {
newFactionObject.favor = Factions[factionName].favor;
delete Factions[factionName];
}
AddToFactions(newFactionObject);
}
+2 -2
View File
@@ -11,7 +11,7 @@ import { FactionName } from "@enums";
interface IProps {
open: boolean;
onClose: () => void;
facName: string;
facName: FactionName;
}
/** React Component for the popup used to create a new gang. */
@@ -27,7 +27,7 @@ export function CreateGangModal(props: IProps): React.ReactElement {
"is not as important.";
function isHacking(): boolean {
return [FactionName.NiteSec as string, FactionName.TheBlackHand as string].includes(props.facName);
return [FactionName.NiteSec, FactionName.TheBlackHand].includes(props.facName);
}
function createGang(): void {
+4 -3
View File
@@ -1,12 +1,13 @@
import type { Faction } from "../Faction";
import React, { useEffect } from "react";
import { Explore, Info, LastPage, LocalPolice, NewReleases, Report, SportsMma } from "@mui/icons-material";
import { Box, Button, Container, Paper, Tooltip, Typography, useTheme } from "@mui/material";
import React, { useEffect } from "react";
import { Player } from "@player";
import { Settings } from "../../Settings/Settings";
import { formatFavor, formatReputation } from "../../ui/formatNumber";
import { Router } from "../../ui/GameRoot";
import { FactionName } from "@enums";
import { Faction } from "../Faction";
import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers";
import { Factions } from "../Factions";
import { useRerender } from "../../ui/React/hooks";
@@ -59,7 +60,7 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
Router.toFaction(faction, true);
}
function acceptInvitation(event: React.MouseEvent<HTMLButtonElement>, faction: string): void {
function acceptInvitation(event: React.MouseEvent<HTMLButtonElement>, faction: FactionName): void {
if (!event.isTrusted) return;
joinFaction(Factions[faction]);
props.rerender();