diff --git a/markdown/bitburner.singularity.getfactioninviterequirements.md b/markdown/bitburner.singularity.getfactioninviterequirements.md new file mode 100644 index 000000000..ba7f31cf3 --- /dev/null +++ b/markdown/bitburner.singularity.getfactioninviterequirements.md @@ -0,0 +1,46 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [Singularity](./bitburner.singularity.md) > [getFactionInviteRequirements](./bitburner.singularity.getfactioninviterequirements.md) + +## Singularity.getFactionInviteRequirements() method + +List conditions for being invited to a faction. + +**Signature:** + +```typescript +getFactionInviteRequirements(faction: string): string[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| faction | string | Name of the faction. | + +**Returns:** + +string\[\] + +Array of strings describing conditions for receiving an invitation to the faction. + +## Remarks + +RAM cost: 3 GB \* 16/4/1 + +## Example + + +```js +ns.singularity.getFactionInviteRequirements("The Syndicate") +[ + "Located in Aevum or Sector-12", + "Not working for the Central Intelligence Agency", + "Not working for the National Security Agency", + "-90 karma", + "Have $10.000m", + "Hacking level 200", + "All combat skills level 200" +] +``` + diff --git a/markdown/bitburner.singularity.md b/markdown/bitburner.singularity.md index 4f7faf82e..75579c6ec 100644 --- a/markdown/bitburner.singularity.md +++ b/markdown/bitburner.singularity.md @@ -50,6 +50,7 @@ This API requires Source-File 4 to use. The RAM cost of all these functions is m | [getDarkwebPrograms()](./bitburner.singularity.getdarkwebprograms.md) | Get a list of programs offered on the dark web. | | [getFactionFavor(faction)](./bitburner.singularity.getfactionfavor.md) | Get faction favor. | | [getFactionFavorGain(faction)](./bitburner.singularity.getfactionfavorgain.md) | Get faction favor gain. | +| [getFactionInviteRequirements(faction)](./bitburner.singularity.getfactioninviterequirements.md) | List conditions for being invited to a faction. | | [getFactionRep(faction)](./bitburner.singularity.getfactionrep.md) | Get faction reputation. | | [getOwnedAugmentations(purchased)](./bitburner.singularity.getownedaugmentations.md) | Get a list of owned augmentation. | | [getOwnedSourceFiles()](./bitburner.singularity.getownedsourcefiles.md) | Get a list of acquired Source-Files. | diff --git a/src/Augmentation/ui/PurchasableAugmentations.tsx b/src/Augmentation/ui/PurchasableAugmentations.tsx index 366a1c9a7..b99642319 100644 --- a/src/Augmentation/ui/PurchasableAugmentations.tsx +++ b/src/Augmentation/ui/PurchasableAugmentations.tsx @@ -2,7 +2,7 @@ * React component for displaying a single augmentation for purchase through * the faction UI */ -import { CheckBox, CheckBoxOutlineBlank, CheckCircle, NewReleases, Report } from "@mui/icons-material"; +import { CheckCircle, NewReleases, Report } from "@mui/icons-material"; import { Box, Button, Container, Paper, Tooltip, Typography } from "@mui/material"; import React, { useEffect, useState } from "react"; import { Faction } from "../../Faction/Faction"; @@ -15,6 +15,7 @@ import { Augmentations } from "../Augmentations"; import { PurchaseAugmentationModal } from "./PurchaseAugmentationModal"; import { getAugCost } from "../AugmentationHelpers"; import { useRerender } from "../../ui/React/hooks"; +import { Requirement } from "../../ui/Components/Requirement"; interface IPreReqsProps { aug: Augmentation; @@ -36,6 +37,7 @@ const PreReqs = (props: IPreReqsProps): React.ReactElement => { fulfilled={Player.hasAugmentation(preAug)} value={preAug} color={Settings.theme.money} + incompleteColor={Settings.theme.error} key={preAug} /> ))} @@ -110,23 +112,6 @@ const Exclusive = (props: IExclusiveProps): React.ReactElement => { ); }; -interface IReqProps { - value: string; - color: string; - fulfilled: boolean; -} - -const Requirement = (props: IReqProps): React.ReactElement => { - return ( - - {props.fulfilled ? : } - {props.value} - - ); -}; - interface IPurchasableAugsProps { augNames: AugmentationName[]; ownedAugNames: AugmentationName[]; @@ -254,12 +239,14 @@ export function PurchasableAugmentation(props: IPurchasableAugProps): React.Reac fulfilled={cost === 0 || Player.money > cost} value={formatMoney(cost)} color={Settings.theme.money} + incompleteColor={Settings.theme.error} /> {props.parent.rep !== undefined && ( = repCost} value={`${formatReputation(repCost)} rep`} color={Settings.theme.rep} + incompleteColor={Settings.theme.error} /> )} diff --git a/src/Company/Company.ts b/src/Company/Company.ts index acebb1c0f..b1bc34c32 100644 --- a/src/Company/Company.ts +++ b/src/Company/Company.ts @@ -1,6 +1,6 @@ import type { CompanyPosition } from "./CompanyPosition"; -import { CompanyName, JobName } from "@enums"; +import { CompanyName, JobName, FactionName } from "@enums"; import { favorToRep, repToFavor } from "../Faction/formulas/favor"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; @@ -12,7 +12,7 @@ export interface CompanyCtorParams { expMultiplier: number; salaryMultiplier: number; jobStatReqOffset: number; - hasFaction?: boolean; + relatedFaction?: FactionName | undefined; } export class Company { @@ -20,7 +20,7 @@ export class Company { name = CompanyName.NoodleBar; info = ""; - hasFaction = false; + relatedFaction: FactionName | undefined; companyPositions = new Set(); @@ -49,7 +49,7 @@ export class Company { this.expMultiplier = p.expMultiplier; this.salaryMultiplier = p.salaryMultiplier; this.jobStatReqOffset = p.jobStatReqOffset; - if (p.hasFaction) this.hasFaction = true; + if (p.relatedFaction) this.relatedFaction = p.relatedFaction; } hasPosition(pos: CompanyPosition | JobName): boolean { diff --git a/src/Company/data/CompaniesMetadata.ts b/src/Company/data/CompaniesMetadata.ts index e8647a585..ec86cd53a 100644 --- a/src/Company/data/CompaniesMetadata.ts +++ b/src/Company/data/CompaniesMetadata.ts @@ -1,6 +1,6 @@ import { CompanyCtorParams } from "../Company"; -import { CompanyName, JobName } from "@enums"; +import { CompanyName, JobName, FactionName } from "@enums"; import { agentJobs, businessJobs, @@ -24,6 +24,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 3, salaryMultiplier: 3, jobStatReqOffset: 249, + relatedFaction: FactionName.ECorp, }, [CompanyName.MegaCorp]: { name: CompanyName.MegaCorp, @@ -31,6 +32,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 3, salaryMultiplier: 3, jobStatReqOffset: 249, + relatedFaction: FactionName.MegaCorp, }, [CompanyName.BachmanAndAssociates]: { name: CompanyName.BachmanAndAssociates, @@ -38,6 +40,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.6, salaryMultiplier: 2.6, jobStatReqOffset: 224, + relatedFaction: FactionName.BachmanAssociates, }, [CompanyName.BladeIndustries]: { name: CompanyName.BladeIndustries, @@ -45,6 +48,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.75, salaryMultiplier: 2.75, jobStatReqOffset: 224, + relatedFaction: FactionName.BladeIndustries, }, [CompanyName.NWO]: { name: CompanyName.NWO, @@ -52,6 +56,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.75, salaryMultiplier: 2.75, jobStatReqOffset: 249, + relatedFaction: FactionName.NWO, }, [CompanyName.ClarkeIncorporated]: { name: CompanyName.ClarkeIncorporated, @@ -59,6 +64,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.25, salaryMultiplier: 2.25, jobStatReqOffset: 224, + relatedFaction: FactionName.ClarkeIncorporated, }, [CompanyName.OmniTekIncorporated]: { name: CompanyName.OmniTekIncorporated, @@ -66,6 +72,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.25, salaryMultiplier: 2.25, jobStatReqOffset: 224, + relatedFaction: FactionName.OmniTekIncorporated, }, [CompanyName.FourSigma]: { name: CompanyName.FourSigma, @@ -73,6 +80,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.5, salaryMultiplier: 2.5, jobStatReqOffset: 224, + relatedFaction: FactionName.FourSigma, }, [CompanyName.KuaiGongInternational]: { name: CompanyName.KuaiGongInternational, @@ -80,6 +88,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2.2, salaryMultiplier: 2.2, jobStatReqOffset: 224, + relatedFaction: FactionName.KuaiGongInternational, }, [CompanyName.FulcrumTechnologies]: { name: CompanyName.FulcrumTechnologies, @@ -87,6 +96,7 @@ export function getCompaniesMetadata(): Record { expMultiplier: 2, salaryMultiplier: 2, jobStatReqOffset: 224, + relatedFaction: FactionName.FulcrumSecretTechnologies, }, [CompanyName.StormTechnologies]: { name: CompanyName.StormTechnologies, diff --git a/src/DevMenu/ui/FactionsDev.tsx b/src/DevMenu/ui/FactionsDev.tsx index 26a82c9cd..a64677e9c 100644 --- a/src/DevMenu/ui/FactionsDev.tsx +++ b/src/DevMenu/ui/FactionsDev.tsx @@ -5,19 +5,25 @@ import { AccordionDetails, Button, FormControl, + FormControlLabel, IconButton, InputLabel, MenuItem, Select, SelectChangeEvent, Typography, + RadioGroup, + Radio, } from "@mui/material"; +import Tooltip from "@mui/material/Tooltip"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ReplyAllIcon from "@mui/icons-material/ReplyAll"; import ReplyIcon from "@mui/icons-material/Reply"; +import ChatIcon from "@mui/icons-material/Chat"; +import ChatBubbleIcon from "@mui/icons-material/ChatBubble"; import { Player } from "@player"; -import { FactionName } from "@enums"; +import { FactionName, FactionDiscovery } from "@enums"; import { Adjuster } from "./Adjuster"; import { Factions } from "../../Faction/Factions"; import { getRecordValues } from "../../Types/Record"; @@ -27,18 +33,41 @@ const bigNumber = 1e12; export function FactionsDev(): React.ReactElement { const [factionName, setFactionName] = useState(FactionName.Illuminati); + const [factionDiscovery, setFactionDiscovery] = useState(Factions[FactionName.Illuminati].discovery); function setFactionDropdown(event: SelectChangeEvent): void { if (!getEnumHelper("FactionName").isMember(event.target.value)) return; setFactionName(event.target.value); + setFactionDiscovery(Factions[event.target.value].discovery); } function receiveInvite(): void { Player.receiveInvite(factionName); + Factions[factionName].alreadyInvited = true; } function receiveAllInvites(): void { - Object.values(FactionName).forEach((faction) => Player.receiveInvite(faction)); + Object.values(FactionName).forEach((faction) => { + Player.receiveInvite(faction); + Factions[factionName].alreadyInvited = true; + }); + } + + function receiveRumor(): void { + Player.receiveRumor(factionName); + setFactionDiscovery(Factions[factionName].discovery); + } + + function receiveAllRumors(): void { + Object.values(FactionName).forEach((faction) => Player.receiveRumor(faction)); + } + + function resetAllDiscovery(): void { + Object.values(Factions).forEach((faction) => { + faction.discovery = FactionDiscovery.unknown; + }); + Player.factionRumors.length = 0; + setFactionDiscovery(Factions[factionName].discovery); } function modifyFactionRep(modifier: number): (x: number) => void { @@ -93,6 +122,12 @@ export function FactionsDev(): React.ReactElement { } } + function setDiscovery(event: React.ChangeEvent, value: string): void { + const disco = value as FactionDiscovery; + Factions[factionName].discovery = disco; + setFactionDiscovery(disco); + } + return ( }> @@ -115,12 +150,16 @@ export function FactionsDev(): React.ReactElement { value={factionName} startAdornment={ <> - - - - - - + + + + + + + + + + } > @@ -133,6 +172,20 @@ export function FactionsDev(): React.ReactElement { + + + Discovery: + + + + + {Object.entries(FactionDiscovery).map(([discoveryLabel, discovery]) => ( + } /> + ))} + + + + Reputation: @@ -163,6 +216,28 @@ export function FactionsDev(): React.ReactElement { /> + + + All Factions: + + + + + + + + + + + + + All Reputation: diff --git a/src/Faction/Enums.ts b/src/Faction/Enums.ts index c48999844..0c7898be4 100644 --- a/src/Faction/Enums.ts +++ b/src/Faction/Enums.ts @@ -34,3 +34,9 @@ export enum FactionName { ChurchOfTheMachineGod = "Church of the Machine God", ShadowsOfAnarchy = "Shadows of Anarchy", } + +export enum FactionDiscovery { + unknown = "unknown", + rumored = "rumored", + known = "known", +} diff --git a/src/Faction/Faction.ts b/src/Faction/Faction.ts index 83259495f..e2b3682ea 100644 --- a/src/Faction/Faction.ts +++ b/src/Faction/Faction.ts @@ -1,8 +1,9 @@ -import { AugmentationName, FactionName } from "@enums"; +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"; +import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; export class Faction { /** @@ -23,6 +24,9 @@ export class Faction { /** Flag signalling whether player is a member of this faction */ isMember = false; + /** Level of player knowledge about this faction (unknown, rumored, known) */ + discovery: FactionDiscovery = FactionDiscovery.unknown; + /** Name of faction */ name: FactionName; @@ -75,6 +79,30 @@ 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"] }); /** Serialize the current object to a JSON save state. */ diff --git a/src/Faction/FactionHelpers.tsx b/src/Faction/FactionHelpers.tsx index 593d04999..c8916be10 100644 --- a/src/Faction/FactionHelpers.tsx +++ b/src/Faction/FactionHelpers.tsx @@ -50,6 +50,12 @@ 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--; + } + } } //Returns a boolean indicating whether the player has the prerequisites for the diff --git a/src/Faction/FactionInfo.tsx b/src/Faction/FactionInfo.tsx index f9131e2a1..a7746563e 100644 --- a/src/Faction/FactionInfo.tsx +++ b/src/Faction/FactionInfo.tsx @@ -1,12 +1,41 @@ import React from "react"; -import { FactionName } from "@enums"; +import { FactionName, CompanyName, CityName, LiteratureName, MessageFilename } from "@enums"; +import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { Router } from "../ui/GameRoot"; import { Page } from "../ui/Router"; import { Option } from "./ui/Option"; import { Typography } from "@mui/material"; +import { + JoinCondition, + haveBackdooredServer, + employedBy, + executiveEmployee, + notEmployee, + haveAugmentations, + haveMoney, + haveSkill, + haveCombatSkills, + haveKarma, + haveKilledPeople, + locatedInCity, + totalHacknetRam, + totalHacknetCores, + totalHacknetLevels, + haveBladeburnerRank, + haveSourceFile, + haveFile, + someCondition, +} from "./FactionJoinCondition"; +import { SpecialServers } from "../Server/data/SpecialServers"; +import { CONSTANTS } from "../Constants"; +import { BladeburnerConstants } from "../Bladeburner/data/Constants"; +import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; interface FactionInfoParams { infoText?: JSX.Element; + rumorText?: JSX.Element; + inviteReqs?: JoinCondition[]; + rumorReqs?: JoinCondition[]; enemies?: FactionName[]; offerHackingWork?: boolean; offerFieldWork?: boolean; @@ -24,6 +53,15 @@ export class FactionInfo { /** The descriptive text to show on the faction's page. */ infoText: JSX.Element; + /** The hint to show about how to get invited to this faction. */ + rumorText: JSX.Element; + + /** Conditions for being automatically inivited to this facton. */ + inviteReqs: JoinCondition[]; + + /** Conditions for automatically hearing a rumor about this facton. */ + rumorReqs: JoinCondition[]; + /** A flag indicating if the faction supports field work to earn reputation. */ offerFieldWork: boolean; @@ -44,6 +82,9 @@ export class FactionInfo { constructor(params: FactionInfoParams) { this.infoText = params.infoText ?? <>; + this.rumorText = params.rumorText ?? <>; + this.inviteReqs = params.inviteReqs ?? []; + this.rumorReqs = params.rumorReqs ?? []; this.enemies = params.enemies ?? []; this.offerHackingWork = params.offerHackingWork ?? false; this.offerFieldWork = params.offerFieldWork ?? false; @@ -69,12 +110,28 @@ export const FactionInfos: Record = { from this chaos, we are the invisible hand that guides them to order.{" "} ), + rumorText: ( + <> + “...the ancient secret society that controls the entire world from the shadows with their invisible hand. With + their personal wealth and skills they have penetrated every major government, financial agency, and + corporation...” + + ), + inviteReqs: [haveAugmentations(30), haveMoney(150e9), haveSkill("hacking", 1500), haveCombatSkills(1200)], + rumorReqs: [haveFile(LiteratureName.TheHiddenWorld)], offerHackingWork: true, offerFieldWork: true, }), [FactionName.Daedalus]: new FactionInfo({ infoText: <>Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth., + rumorText: <>Follow the thread., + inviteReqs: [ + haveAugmentations(currentNodeMults.DaedalusAugsRequirement), + haveMoney(100e9), + someCondition(haveSkill("hacking", 2500), haveCombatSkills(1500)), + ], + rumorReqs: [haveFile(MessageFilename.TruthGazer)], offerHackingWork: true, offerFieldWork: true, }), @@ -89,6 +146,14 @@ export const FactionInfos: Record = { Only then can you discover immortality. ), + rumorText: ( + <> + {FactionName.TheCovenant} offers an exclusive service to those who have reached the limits of individual fitness + and wish to go further. + + ), + inviteReqs: [haveAugmentations(20), haveMoney(75e9), haveSkill("hacking", 850), haveCombatSkills(850)], + rumorReqs: [haveSourceFile(10)], offerHackingWork: true, offerFieldWork: true, }), @@ -102,6 +167,9 @@ export const FactionInfos: Record = { information universally accessible. ), + rumorText: <>High-ranking employees of {CompanyName.ECorp} can gain access to proprietary hacking augmentations., + inviteReqs: [employedBy(CompanyName.ECorp, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.ECorp)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -120,6 +188,11 @@ export const FactionInfos: Record = { the world. ), + rumorText: ( + <>High-ranking employees of {CompanyName.MegaCorp} can gain access to proprietary biotech augmentations. + ), + inviteReqs: [employedBy(CompanyName.MegaCorp, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.MegaCorp)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -135,6 +208,14 @@ export const FactionInfos: Record = { Legal Insight - Business Instinct - Innovative Experience. ), + rumorText: ( + <> + High-ranking employees of {CompanyName.BachmanAndAssociates} can gain access to proprietary negotiation + augmentations. + + ), + inviteReqs: [employedBy(CompanyName.BachmanAndAssociates, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.BachmanAndAssociates)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -143,6 +224,11 @@ export const FactionInfos: Record = { [FactionName.BladeIndustries]: new FactionInfo({ infoText: <>Augmentation is Salvation., + rumorText: ( + <>High-ranking employees of {CompanyName.BladeIndustries} can gain access to proprietary bionic augmentations. + ), + inviteReqs: [employedBy(CompanyName.BladeIndustries, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.BladeIndustries)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -157,6 +243,9 @@ export const FactionInfos: Record = { because of willingness, but because of a need to be incorporated into higher orders of structure and meaning. ), + rumorText: <>High-ranking employees of {CompanyName.NWO} can gain access to proprietary nanotech augmentations., + inviteReqs: [employedBy(CompanyName.NWO, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.NWO)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -165,6 +254,14 @@ export const FactionInfos: Record = { [FactionName.ClarkeIncorporated]: new FactionInfo({ infoText: <>The Power of the Genome - Unlocked., + rumorText: ( + <> + High-ranking employees of {CompanyName.ClarkeIncorporated} can gain access to proprietary neurotech + augmentations. + + ), + inviteReqs: [employedBy(CompanyName.ClarkeIncorporated, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.ClarkeIncorporated)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -173,6 +270,14 @@ export const FactionInfos: Record = { [FactionName.OmniTekIncorporated]: new FactionInfo({ infoText: <>Simply put, our mission is to design and build robots that make a difference., + rumorText: ( + <> + High-ranking employees of {CompanyName.OmniTekIncorporated} can gain access to proprietary data-processing + augmentations. + + ), + inviteReqs: [employedBy(CompanyName.OmniTekIncorporated, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.OmniTekIncorporated)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -186,6 +291,11 @@ export const FactionInfos: Record = { deep learning and innovative ideas. And improved by iteration. That's {FactionName.FourSigma}. ), + rumorText: ( + <>High-ranking employees of {CompanyName.FourSigma} can gain access to a range of versatile augmentations. + ), + inviteReqs: [employedBy(CompanyName.FourSigma, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.FourSigma)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -194,6 +304,14 @@ export const FactionInfos: Record = { [FactionName.KuaiGongInternational]: new FactionInfo({ infoText: <>Dream big. Work hard. Make history., + rumorText: ( + <> + High-ranking employees of {CompanyName.KuaiGongInternational} can gain access to proprietary dermatech + augmentations. + + ), + inviteReqs: [employedBy(CompanyName.KuaiGongInternational, { withRep: CONSTANTS.CorpFactionRepRequirement })], + rumorReqs: [employedBy(CompanyName.KuaiGongInternational)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -208,6 +326,17 @@ export const FactionInfos: Record = { would be necessary to create them. And now we can. ), + rumorText: ( + <> + High-ranking employees of {CompanyName.FulcrumTechnologies} may discover a company system with access to + proprietary neural network augmentations. + + ), + inviteReqs: [ + employedBy(CompanyName.FulcrumTechnologies, { withRep: CONSTANTS.CorpFactionRepRequirement }), + haveBackdooredServer(SpecialServers.FulcrumSecretTechnologies), + ], + rumorReqs: [employedBy(CompanyName.FulcrumTechnologies)], offerHackingWork: true, offerSecurityWork: true, keepOnInstall: true, @@ -227,6 +356,9 @@ export const FactionInfos: Record = { Those who run the bits, run the world. ), + rumorText: <>Run for the hills., + inviteReqs: [haveBackdooredServer(SpecialServers.BitRunnersServer)], + rumorReqs: [haveFile(MessageFilename.BitRunnersTest)], offerHackingWork: true, }), @@ -241,13 +373,16 @@ export const FactionInfos: Record = { So much pain. So many lives. Their darkness must end. ), + rumorText: <>I.I.I.I, + inviteReqs: [haveBackdooredServer(SpecialServers.TheBlackHandServer)], + rumorReqs: [haveFile(MessageFilename.Jumper3)], offerHackingWork: true, offerFieldWork: true, }), // prettier-ignore [FactionName.NiteSec]: new FactionInfo({ - infoText:(<> + infoText:(<> {" __..__ "}
{" _.nITESECNIt. "}
{" .-'NITESECNITESEc. "}
@@ -283,52 +418,79 @@ export const FactionInfos: Record = { {" d .dNITESEC $ | "}
{" :bp.__.gNITESEC/$ :$ ; "}
{" NITESECNITESECNIT /$b : "}
), - offerHackingWork: true, - offerFieldWork: false, - offerSecurityWork: false, - special: false, - keepOnInstall: false, + rumorText: <>A hacking group known as {FactionName.NiteSec} may recruit you if you impress them with your hacking skills., + inviteReqs: [ + haveBackdooredServer(SpecialServers.NiteSecServer) + ], + rumorReqs: [haveFile(MessageFilename.NiteSecTest)], + offerHackingWork: true, + offerFieldWork: false, + offerSecurityWork: false, + special: false, + keepOnInstall: false, }), // City factions, essentially governments [FactionName.Aevum]: new FactionInfo({ infoText: <>The Silicon City., + rumorText: <>Wealthy residents of {CityName.Aevum} may be invited to work for the Silicon City., enemies: [FactionName.Chongqing, FactionName.NewTokyo, FactionName.Ishima, FactionName.Volhaven], + inviteReqs: [locatedInCity(CityName.Aevum), haveMoney(40e6)], + rumorReqs: [locatedInCity(CityName.Aevum), haveMoney(20e6)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, }), [FactionName.Chongqing]: new FactionInfo({ infoText: <>Serve the People., + rumorText: <>Wealthy residents of {CityName.Chongqing} may be invited to serve the people., enemies: [FactionName.Sector12, FactionName.Aevum, FactionName.Volhaven], + inviteReqs: [locatedInCity(CityName.Chongqing), haveMoney(20e6)], + rumorReqs: [locatedInCity(CityName.Chongqing), haveMoney(10e6)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, }), [FactionName.Ishima]: new FactionInfo({ infoText: <>The East Asian Order of the Future., + rumorText: ( + <>Wealthy residents of {CityName.Ishima} may be invited to work for the East Asian Order of the Future. + ), enemies: [FactionName.Sector12, FactionName.Aevum, FactionName.Volhaven], + inviteReqs: [locatedInCity(CityName.Ishima), haveMoney(30e6)], + rumorReqs: [locatedInCity(CityName.Ishima), haveMoney(15e6)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, }), [FactionName.NewTokyo]: new FactionInfo({ infoText: <>Asia's World City., + rumorText: <>Wealthy residents of {CityName.NewTokyo} may be invited to work for Asia's World City., enemies: [FactionName.Sector12, FactionName.Aevum, FactionName.Volhaven], + inviteReqs: [locatedInCity(CityName.NewTokyo), haveMoney(20e6)], + rumorReqs: [locatedInCity(CityName.NewTokyo), haveMoney(10e6)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, }), [FactionName.Sector12]: new FactionInfo({ infoText: <>The City of the Future., + rumorText: <>Wealthy residents of {CityName.Sector12} may be invited to work for the City of the Future., enemies: [FactionName.Chongqing, FactionName.NewTokyo, FactionName.Ishima, FactionName.Volhaven], + inviteReqs: [locatedInCity(CityName.Sector12), haveMoney(15e6)], + rumorReqs: [locatedInCity(CityName.Sector12), haveMoney(7.5e6)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, }), [FactionName.Volhaven]: new FactionInfo({ infoText: <>Benefit, Honor, and Glory., + rumorText: ( + <>Wealthy residents of {CityName.Volhaven} may be invited to work for the city's Benefit, Honor, and Glory. + ), enemies: [FactionName.Chongqing, FactionName.Sector12, FactionName.NewTokyo, FactionName.Aevum, FactionName.Ishima], + inviteReqs: [locatedInCity(CityName.Volhaven), haveMoney(50e6)], + rumorReqs: [locatedInCity(CityName.Volhaven), haveMoney(25e6)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -337,6 +499,16 @@ export const FactionInfos: Record = { // Criminal Organizations/Gangs [FactionName.SpeakersForTheDead]: new FactionInfo({ infoText: <>It is better to reign in Hell than to serve in Heaven., + rumorText: <>“We know.”, + inviteReqs: [ + notEmployee(CompanyName.CIA), + notEmployee(CompanyName.NSA), + haveKarma(-45), + haveSkill("hacking", 100), + haveCombatSkills(300), + haveKilledPeople(30), + ], + rumorReqs: [haveKarma(-45), haveSkill("hacking", 50), haveCombatSkills(150), haveKilledPeople(5)], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -344,12 +516,43 @@ export const FactionInfos: Record = { [FactionName.TheDarkArmy]: new FactionInfo({ infoText: <>The World doesn't care about right or wrong. It only cares about power., + rumorText: <>A ruthless criminal organization based in {CityName.Chongqing}, + inviteReqs: [ + locatedInCity(CityName.Chongqing), + notEmployee(CompanyName.CIA), + notEmployee(CompanyName.NSA), + haveKarma(-45), + haveSkill("hacking", 300), + haveCombatSkills(300), + haveKilledPeople(5), + ], + rumorReqs: [ + locatedInCity(CityName.Chongqing), + haveKarma(-45), + haveSkill("hacking", 150), + haveCombatSkills(150), + haveKilledPeople(1), + ], offerHackingWork: true, offerFieldWork: true, }), [FactionName.TheSyndicate]: new FactionInfo({ infoText: <>Honor holds you back., + rumorText: <>An elite criminal organization that operates in the western hemisphere, + inviteReqs: [ + locatedInCity(CityName.Aevum, CityName.Sector12), + notEmployee(CompanyName.CIA), + notEmployee(CompanyName.NSA), + haveKarma(-90), + haveMoney(10e6), + haveSkill("hacking", 200), + haveCombatSkills(200), + ], + rumorReqs: [ + locatedInCity(CityName.Aevum, CityName.Sector12), + someCondition(haveKarma(-90), haveFile(LiteratureName.Sector12Crime)), + ], offerHackingWork: true, offerFieldWork: true, offerSecurityWork: true, @@ -366,34 +569,65 @@ export const FactionInfos: Record = { That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system. ), + rumorText: ( + <> + Corporate executives with the right moral flexiblity may be invited to find out who they are truly working for. + + ), + inviteReqs: [executiveEmployee(), haveMoney(15e6), haveKarma(-22)], + rumorReqs: [executiveEmployee()], offerHackingWork: true, offerFieldWork: true, }), [FactionName.Tetrads]: new FactionInfo({ infoText: <>Following the mandate of Heaven and carrying out the way., - + rumorText: <>A notorious East Asian criminal organization, + inviteReqs: [ + locatedInCity(CityName.Chongqing, CityName.NewTokyo, CityName.Ishima), + haveKarma(-18), + haveCombatSkills(75), + ], + rumorReqs: [ + locatedInCity(CityName.Chongqing, CityName.NewTokyo, CityName.Ishima), + someCondition(haveKarma(-18), haveFile(LiteratureName.NewTriads)), + ], offerFieldWork: true, offerSecurityWork: true, }), [FactionName.SlumSnakes]: new FactionInfo({ infoText: <>{FactionName.SlumSnakes} rule!, - + rumorText: <>Graffiti seen in the slums: “{FactionName.SlumSnakes} rule!”, + inviteReqs: [haveCombatSkills(30), haveMoney(1e6)], + rumorReqs: [haveKarma(-1)], offerFieldWork: true, offerSecurityWork: true, }), // Early game factions - factions the player will prestige with early on that don't belong in other categories. [FactionName.Netburners]: new FactionInfo({ - infoText: <>{"~~//*>H4CK||3T 8URN3R5**>?>\\~~"}, + infoText: <>{"~~//*>H4CK|\\|3T 8URN3R5**>?>\\~~"}, + rumorText: <>{"~~//*>H4CK|\\|3T 8URN3R5**>?>\\~~"}, + inviteReqs: [haveSkill("hacking", 80), totalHacknetRam(8), totalHacknetCores(4), totalHacknetLevels(100)], + rumorReqs: [totalHacknetLevels(50)], offerHackingWork: true, }), [FactionName.TianDiHui]: new FactionInfo({ infoText: <>Obey Heaven and work righteously., + rumorText: <>A Chinese honor society with the motto: “Obey Heaven and work righteously.”, + inviteReqs: [ + locatedInCity(CityName.Chongqing, CityName.NewTokyo, CityName.Ishima), + haveSkill("hacking", 50), + haveMoney(1e6), + ], + rumorReqs: [ + locatedInCity(CityName.Chongqing, CityName.NewTokyo, CityName.Ishima), + haveSkill("hacking", 25), + haveMoney(0.5e6), + ], offerHackingWork: true, - offerSecurityWork: true, }), @@ -405,6 +639,14 @@ export const FactionInfos: Record = { total chaos. We serve only to protect society, to protect humanity, to protect the world from imminent collapse. ), + rumorText: ( + <> + A hacking group known as {FactionName.CyberSec} will invite you to join them if you demonstrate your hacking + skills on their server. + + ), + inviteReqs: [haveBackdooredServer(SpecialServers.CyberSecServer)], + rumorReqs: [haveFile(MessageFilename.CyberSecTest)], offerHackingWork: true, }), @@ -419,7 +661,9 @@ export const FactionInfos: Record = { {FactionName.Bladeburners} contracts/operations will increase your reputation. ), - + rumorText: <>The {CompanyName.NSA} would like to have a word with you once you're ready., + inviteReqs: [haveSourceFile(6, 7), haveBladeburnerRank(BladeburnerConstants.RankNeededForFaction)], + rumorReqs: [haveSourceFile(6, 7)], special: true, assignment: (): React.ReactElement => { return ( @@ -465,6 +709,18 @@ export const FactionInfos: Record = { {" `..` "}

Many cultures predict an end to humanity in the near future, a final Armageddon that will end the world; but we disagree.), + rumorText: <>Trouble is brewing in {CityName.Chongqing}., + inviteReqs: [ + haveSourceFile(13), + haveAugmentations(0), + { + toString: () => `Investigate the dilapidated church in ${CityName.Chongqing}`, + isSatisfied: (p: PlayerObject) => { + return [...p.factions, ...p.factionInvitations].includes(FactionName.ChurchOfTheMachineGod); + }, + }, + ], + rumorReqs: [haveSourceFile(13), haveAugmentations(0)], offerHackingWork: false, offerFieldWork: false, offerSecurityWork: false, @@ -490,6 +746,15 @@ export const FactionInfos: Record = { shackles, the gods grant us their strength. ), + rumorText: <>Your infiltration activity has attracted attention., + inviteReqs: [ + { + toString: () => `Complete an infiltration`, + isSatisfied: (p: PlayerObject) => { + return [...p.factions, ...p.factionInvitations].includes(FactionName.ShadowsOfAnarchy); + }, + }, + ], special: true, keepOnInstall: true, assignment: (): React.ReactElement => { diff --git a/src/Faction/FactionJoinCondition.ts b/src/Faction/FactionJoinCondition.ts new file mode 100644 index 000000000..8775bba0e --- /dev/null +++ b/src/Faction/FactionJoinCondition.ts @@ -0,0 +1,269 @@ +import { CompanyName, JobName, CityName, AugmentationName, LiteratureName, MessageFilename } from "@enums"; +import { Server } from "../Server/Server"; +import { GetServer } from "../Server/AllServers"; +import { HacknetServer } from "../Hacknet/HacknetServer"; +import { serverMetadata } from "../Server/data/servers"; +import { Companies } from "../Company/Companies"; +import { formatReputation, formatMoney, formatRam } from "../ui/formatNumber"; +import type { PlayerObject } from "../PersonObjects/Player/PlayerObject"; +import type { Skills } from "../PersonObjects/Skills"; + +export interface JoinCondition { + toString(): string; + isSatisfied(p: PlayerObject): boolean; +} + +export const haveBackdooredServer = (hostname: string): JoinCondition => ({ + toString(): string { + return `Backdoor access to ${hostname} server`; + }, + isSatisfied(): boolean { + const server = GetServer(hostname); + if (!(server instanceof Server)) { + throw new Error(`${hostname} should be a normal server`); + } + return server.backdoorInstalled; + }, +}); + +export const employedBy = ( + companyName: CompanyName, + { withRep }: { withRep: number } = { withRep: 0 }, +): JoinCondition => ({ + toString(): string { + if (withRep == 0) { + return `Employed at ${companyName}`; + } else { + return `Employed at ${companyName} with ${formatReputation(withRep)} reputation`; + } + }, + isSatisfied(p: PlayerObject): boolean { + const company = Companies[companyName]; + if (!company) return false; + const serverMeta = serverMetadata.find((s) => s.specialName === companyName); + const server = GetServer(serverMeta ? serverMeta.hostname : ""); + const bonus = (server as Server).backdoorInstalled ? -100e3 : 0; + return Object.hasOwn(p.jobs, companyName) && company.playerReputation > withRep + bonus; + }, +}); + +export const executiveEmployee = (): JoinCondition => ({ + toString(): string { + return `CTO, CFO, or CEO of a company`; + }, + isSatisfied(p: PlayerObject): boolean { + const allPositions = Object.values(p.jobs); + return ( + allPositions.includes(JobName.software7) || // CTO + allPositions.includes(JobName.business4) || // CFO + allPositions.includes(JobName.business5) // CEO + ); + }, +}); + +export const notEmployee = (...companyNames: CompanyName[]): JoinCondition => ({ + toString(): string { + return `Not working for the ${joinList(companyNames)}`; + }, + isSatisfied(p: PlayerObject): boolean { + for (const companyName of companyNames) { + if (Object.hasOwn(p.jobs, companyName)) return false; + } + return true; + }, +}); + +export const haveAugmentations = (n: number): JoinCondition => ({ + toString(): string { + return `${n || "No"} augmentations installed`; + }, + isSatisfied(p: PlayerObject): boolean { + if (n == 0) { + const augs = [...p.augmentations, ...p.queuedAugmentations].filter( + (a) => a.name !== AugmentationName.NeuroFluxGovernor, + ); + return augs.length == 0; + } + return p.augmentations.length >= n; + }, +}); + +export const haveMoney = (n: number): JoinCondition => ({ + toString(): string { + return `Have ${formatMoney(n)}`; + }, + isSatisfied(p: PlayerObject): boolean { + return p.money >= n; + }, +}); + +export const haveSkill = (skill: keyof Skills, n: number): JoinCondition => ({ + toString(): string { + return `${capitalize(skill)} level ${n}`; + }, + isSatisfied(p: PlayerObject): boolean { + return p.skills[skill] >= n; + }, +}); + +export const haveCombatSkills = (n: number): JoinCondition => ({ + toString(): string { + return `All combat skills level ${n}`; + }, + isSatisfied(p: PlayerObject): boolean { + return p.skills.strength >= n && p.skills.defense >= n && p.skills.dexterity >= n && p.skills.agility >= n; + }, +}); + +export const haveKarma = (n: number): JoinCondition => ({ + toString(): string { + return `${n} karma`; + }, + isSatisfied(p: PlayerObject): boolean { + return p.karma <= n; + }, +}); + +export const haveKilledPeople = (n: number): JoinCondition => ({ + toString(): string { + return `${n} people killed`; + }, + isSatisfied(p: PlayerObject): boolean { + return p.numPeopleKilled >= n; + }, +}); + +export const locatedInCity = (...cities: CityName[]): JoinCondition => ({ + toString(): string { + return `Located in ${joinList(cities)}`; + }, + isSatisfied(p: PlayerObject): boolean { + for (const city of cities) { + if (p.city == city) return true; + } + return false; + }, +}); + +export const totalHacknetRam = (n: number): JoinCondition => ({ + toString(): string { + return `Total Hacknet RAM of ${formatRam(n)}`; + }, + isSatisfied(p: PlayerObject): boolean { + let total = 0; + for (const node of iterateHacknet(p)) { + total += node.ram; + if (total >= n) return true; + } + return false; + }, +}); + +export const totalHacknetCores = (n: number): JoinCondition => ({ + toString(): string { + return `Total Hacknet cores of ${n}`; + }, + isSatisfied(p: PlayerObject): boolean { + let total = 0; + for (const node of iterateHacknet(p)) { + total += node.cores; + if (total >= n) return true; + } + return false; + }, +}); + +export const totalHacknetLevels = (n: number): JoinCondition => ({ + toString(): string { + return `Total Hacknet levels of ${n}`; + }, + isSatisfied(p: PlayerObject): boolean { + let total = 0; + for (const node of iterateHacknet(p)) { + total += node.level; + if (total >= n) return true; + } + return false; + }, +}); + +export const haveBladeburnerRank = (n: number): JoinCondition => ({ + toString(): string { + return `Rank ${n} in the Bladeburner Division`; + }, + isSatisfied(p: PlayerObject): boolean { + const rank = p.bladeburner?.rank || 0; + return rank >= n; + }, +}); + +export const haveSourceFile = (...nodeNums: number[]): JoinCondition => ({ + toString(): string { + return `In BitNode ${joinList(nodeNums)} or have SourceFile ${joinList(nodeNums)}`; + }, + isSatisfied(p: PlayerObject): boolean { + for (const n of nodeNums) { + if (p.bitNodeN === n || p.sourceFileLvl(n) > 0) return true; + } + return false; + }, +}); + +export const haveFile = (fileName: LiteratureName | MessageFilename): JoinCondition => ({ + toString(): string { + return `Have the file '${fileName}'`; + }, + isSatisfied(p: PlayerObject): boolean { + const homeComputer = p.getHomeComputer(); + return homeComputer.messages.includes(fileName); + }, +}); + +export const someCondition = (...conditions: JoinCondition[]): JoinCondition => ({ + toString(): string { + return joinList(conditions.map((c) => c.toString())); + }, + isSatisfied(p: PlayerObject): boolean { + for (const condition of conditions) { + if (condition.isSatisfied(p)) return true; + } + return false; + }, +}); + +/* helpers */ + +function capitalize(s: string) { + return s.charAt(0).toUpperCase() + s.slice(1); +} + +function joinList(list: (string | number)[], conjunction = "or", separator = ", ") { + if (list.length < 3) { + return list.join(` ${conjunction} `); + } + list = [...list]; + list[list.length - 1] = `${conjunction} ${list[list.length - 1]}`; + return list.join(`${separator}`); +} + +function* iterateHacknet(p: PlayerObject) { + for (let i = 0; i < p.hacknetNodes.length; ++i) { + const v = p.hacknetNodes[i]; + if (typeof v === "string") { + const hserver = GetServer(v); + if (hserver === null || !(hserver instanceof HacknetServer)) + throw new Error("player hacknet server was not HacknetServer"); + yield { + ram: hserver.maxRam, + cores: hserver.cores, + level: hserver.level, + }; + } else { + yield { + ram: v.ram, + cores: v.cores, + level: v.level, + }; + } + } +} diff --git a/src/Faction/Factions.ts b/src/Faction/Factions.ts index a72967f85..74ad999f5 100644 --- a/src/Faction/Factions.ts +++ b/src/Faction/Factions.ts @@ -2,7 +2,7 @@ * Initialization and manipulation of the Factions object, which stores data * about all Factions in the game */ -import { FactionName } from "@enums"; +import { FactionName, FactionDiscovery } from "@enums"; import { Faction } from "./Faction"; import { Reviver, assertLoadingType } from "../utils/JSONReviver"; @@ -31,12 +31,15 @@ export function loadFactions(saveString: string): void { const faction = Factions[loadedFactionName]; if (typeof loadedFaction !== "object") continue; assertLoadingType(loadedFaction); - const { playerReputation: loadedRep, favor: loadedFavor } = 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; + 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; } } diff --git a/src/Faction/ui/FactionsRoot.tsx b/src/Faction/ui/FactionsRoot.tsx index 04e7dae76..d0f16fe98 100644 --- a/src/Faction/ui/FactionsRoot.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -3,13 +3,16 @@ import { Explore, Info, LastPage, LocalPolice, NewReleases, Report, SportsMma } import { Box, Button, Container, Paper, Tooltip, Typography, useTheme } from "@mui/material"; import { Player } from "@player"; -import { FactionName } from "@enums"; +import { FactionName, FactionDiscovery } from "@enums"; import { Settings } from "../../Settings/Settings"; import { formatFavor, formatReputation } from "../../ui/formatNumber"; import { Router } from "../../ui/GameRoot"; import { Page } from "../../ui/Router"; import { useRerender } from "../../ui/React/hooks"; +import { CorruptableText } from "../../ui/React/CorruptableText"; +import { Requirement } from "../../ui/Components/Requirement"; + import { Faction } from "../Faction"; import { getFactionAugmentationsFiltered, joinFaction } from "../FactionHelpers"; import { Factions } from "../Factions"; @@ -43,10 +46,25 @@ const WorkTypesOffered = (props: { faction: Faction }): React.ReactElement => { ); }; +const JoinChecklist = (props: { faction: Faction }): React.ReactElement => { + const info = props.faction.getInfo(); + return ( + <> + {info.inviteReqs.map((condition, i) => ( + + ))} + + ); +}; + interface FactionElementProps { faction: Faction; - /** Whether the player is a member of this faction already */ - joined: boolean; /** Rerender function to force the entire FactionsRoot to rerender */ rerender: () => void; } @@ -74,11 +92,11 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => { display: "grid", p: 1, alignItems: "center", - gridTemplateColumns: "minmax(0, 4fr)" + (props.joined ? " 1fr" : ""), + gridTemplateColumns: "minmax(0, 4fr)" + (props.faction.isMember ? " 1fr" : ""), }} > - {props.joined ? ( + {props.faction.isMember ? ( { - ) : ( + ) : props.faction.alreadyInvited ? ( - )} + ) : null} - + { alignItems: "center", }} > - - - {props.faction.name} - - + {props.faction.discovery == FactionDiscovery.known ? ( + + {props.faction.name} + + + } + > + + {props.faction.name} + + + ) : ( + + + + + + )} {Player.hasGangWith(props.faction.name) && ( @@ -127,7 +160,7 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => { )} - {!props.joined && facInfo.enemies.length > 0 && ( + {!props.faction.isMember && facInfo.enemies.length > 0 && ( @@ -148,13 +181,23 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => { - {!Player.hasGangWith(props.faction.name) && } - {`${augsLeft || "No"} Augmentations left`} + {props.faction.isMember || props.faction.alreadyInvited ? ( + <> + {!Player.hasGangWith(props.faction.name) && } + {`${ + augsLeft || "No" + } Augmentations left`} + + ) : ( + + {props.faction.getInfo().rumorText} + + )} - {props.joined && ( + {props.faction.isMember && ( {formatFavor(Math.floor(props.faction.favor))} favor @@ -178,9 +221,12 @@ export function FactionsRoot(): React.ReactElement { }, []); const allFactions = Object.values(FactionName).map((faction) => faction as string); - const allJoinedFactions = [...Player.factions]; - allJoinedFactions.sort((a, b) => allFactions.indexOf(a) - allFactions.indexOf(b)); - const invitations = Player.factionInvitations; + const allJoinedFactions = [...Player.factions].map((facName) => Factions[facName]).filter((faction) => !!faction); + allJoinedFactions.sort((a, b) => allFactions.indexOf(a.name) - allFactions.indexOf(b.name)); + + const invitations = Player.factionInvitations.map((facName) => Factions[facName]).filter((faction) => !!faction); + + const rumors = Player.factionRumors.map((facName) => Factions[facName]).filter((faction) => !!faction); return ( @@ -213,44 +259,53 @@ export function FactionsRoot(): React.ReactElement { }, }} > - {invitations.length > 0 && ( - - - Faction Invitations - - - {invitations.map((facName) => { - if (!Object.hasOwn(Factions, facName)) return null; - return ; - })} - - - )} + + {invitations.length > 0 && ( + <> + + Faction Invitations + + + {invitations.map((faction) => ( + + ))} + + + )} + + {rumors.length > 0 && ( + <> + + Rumors + +
+ {rumors.map((faction) => ( + + ))} +
+ + )} +
{Player.inGang() && ( - - Your Gang - - )} - {Player.inGang() && ( - - - + <> + + Your Gang + + + + + )} Your Factions {allJoinedFactions.length > 0 ? ( - allJoinedFactions.map((facName) => { - if (!Object.hasOwn(Factions, facName) || Player.getGangName() === facName) return null; - return ; + allJoinedFactions.map((faction) => { + if (Player.getGangName() === faction.name) return null; + return ; }) ) : ( You have not yet joined any Factions. diff --git a/src/Infiltration/ui/Game.tsx b/src/Infiltration/ui/Game.tsx index 0d028a9e5..855d80961 100644 --- a/src/Infiltration/ui/Game.tsx +++ b/src/Infiltration/ui/Game.tsx @@ -1,6 +1,6 @@ import { Button, Container, Paper, Typography } from "@mui/material"; import React, { useCallback, useState } from "react"; -import { AugmentationName } from "@enums"; +import { AugmentationName, FactionName } from "@enums"; import { Router } from "../../ui/GameRoot"; import { Page } from "../../ui/Router"; import { Player } from "@player"; @@ -89,6 +89,7 @@ export function Game(props: GameProps): React.ReactElement { (options?: { automated: boolean }) => { setStage(Stage.Countdown); pushResult(false); + Player.receiveRumor(FactionName.ShadowsOfAnarchy); // Kill the player immediately if they use automation, so // it's clear they're not meant to const damage = options?.automated diff --git a/src/Literature/Literature.ts b/src/Literature/Literature.ts index 579e51cb8..2ff2d1466 100644 --- a/src/Literature/Literature.ts +++ b/src/Literature/Literature.ts @@ -1,9 +1,10 @@ import { FilePath, asFilePath } from "../Paths/FilePath"; -import type { LiteratureName } from "@enums"; +import type { LiteratureName, FactionName } from "@enums"; interface LiteratureConstructorParams { title: string; filename: LiteratureName; + factionRumors?: FactionName[]; text: string; } /** @@ -13,11 +14,13 @@ interface LiteratureConstructorParams { export class Literature { title: string; filename: LiteratureName & FilePath; + factionRumors: FactionName[]; text: string; - constructor({ title, filename, text }: LiteratureConstructorParams) { + constructor({ title, filename, factionRumors, text }: LiteratureConstructorParams) { this.title = title; this.filename = asFilePath(filename); + this.factionRumors = factionRumors || []; this.text = text; } } diff --git a/src/Literature/LiteratureHelpers.ts b/src/Literature/LiteratureHelpers.ts index dff34e4ca..0bbbfbc88 100644 --- a/src/Literature/LiteratureHelpers.ts +++ b/src/Literature/LiteratureHelpers.ts @@ -1,12 +1,16 @@ import { Literatures } from "./Literatures"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { LiteratureName } from "@enums"; +import { Player } from "@player"; export function showLiterature(fn: LiteratureName): void { const litObj = Literatures[fn]; if (litObj == null) { return; } + for (const factionName of litObj.factionRumors) { + Player.receiveRumor(factionName); + } const txt = `${litObj.title}

${litObj.text}`; dialogBoxCreate(txt, true); } diff --git a/src/Literature/Literatures.ts b/src/Literature/Literatures.ts index b54a82241..c498fec1a 100644 --- a/src/Literature/Literatures.ts +++ b/src/Literature/Literatures.ts @@ -1,4 +1,4 @@ -import { CityName, FactionName, LiteratureName } from "@enums"; +import { CityName, FactionName, CompanyName, LiteratureName } from "@enums"; import { Literature } from "./Literature"; export const Literatures: Record = { @@ -77,6 +77,7 @@ export const Literatures: Record = { [LiteratureName.HistoryOfSynthoids]: new Literature({ title: "A Brief History of Synthoids", filename: LiteratureName.HistoryOfSynthoids, + factionRumors: [FactionName.OmniTekIncorporated, FactionName.Bladeburners], text: "Synthetic androids, or Synthoids for short, are genetically engineered robots and, short of Augmentations, " + "are composed entirely of organic substances. For this reason, Synthoids are virtually identical to " + @@ -195,10 +196,11 @@ export const Literatures: Record = { [LiteratureName.BrighterThanTheSun]: new Literature({ title: "Brighter than the Sun", filename: LiteratureName.BrighterThanTheSun, + factionRumors: [FactionName.KuaiGongInternational, FactionName.OmniTekIncorporated], text: - `When people think about the corporations that dominate the East, they typically think of ${FactionName.KuaiGongInternational}, which ` + - "holds a complete monopoly for manufacturing and commerce in Asia, or Global Pharmaceuticals, the world's largest " + - `drug company, or ${FactionName.OmniTekIncorporated}, the global leader in intelligent and autonomous robots. But there's one company ` + + `When people think about the corporations that dominate the East, they typically think of ${CompanyName.KuaiGongInternational}, which ` + + `holds a complete monopoly for manufacturing and commerce in Asia, or ${CompanyName.GlobalPharmaceuticals}, the world's largest ` + + `drug company, or ${CompanyName.OmniTekIncorporated}, the global leader in intelligent and autonomous robots. But there's one company ` + "that has seen a rapid rise in the last year and is poised to dominate not only the East, but the entire world: TaiYang Digital.

" + "TaiYang Digital is a Chinese internet-technology corporation that provides services such as " + "online advertising, search engines, gaming, media, entertainment, and cloud computing/storage. Its name TaiYang comes from the Chinese word " + @@ -210,7 +212,7 @@ export const Literatures: Record = { "TaiYang Digital's meteoric rise is extremely surprising in modern society. This sort of growth is " + "something you'd commonly see in the first half of the century, especially for tech companies. However in " + "the last two decades the number of corporations has significantly declined as the largest entities " + - `quickly took over the economy. Corporations such as ${FactionName.ECorp}, ${FactionName.MegaCorp}, and ${FactionName.KuaiGongInternational} have established ` + + `quickly took over the economy. Corporations such as ${CompanyName.ECorp}, ${CompanyName.MegaCorp}, and ${CompanyName.KuaiGongInternational} have established ` + "such strong monopolies in their market sectors that they have effectively killed off all " + "of the smaller and new corporations that have tried to start up over the years. This is what makes " + "the rise of TaiYang Digital so impressive. And if TaiYang continues down this path, then they have " + @@ -233,6 +235,7 @@ export const Literatures: Record = { [LiteratureName.Sector12Crime]: new Literature({ title: `Figures Show Rising Crime Rates in ${CityName.Sector12}`, filename: LiteratureName.Sector12Crime, + factionRumors: [FactionName.TheSyndicate, FactionName.SlumSnakes], text: "A recent study by analytics company Wilson Inc. shows a significant rise " + `in criminal activity in ${CityName.Sector12}. Perhaps the most alarming part of the statistic ` + @@ -267,6 +270,7 @@ export const Literatures: Record = { [LiteratureName.SecretSocieties]: new Literature({ title: "Secret Societies", filename: LiteratureName.SecretSocieties, + factionRumors: [FactionName.TheBlackHand, FactionName.NiteSec, FactionName.BitRunners], text: "The idea of secret societies has long intrigued the general public by inspiring curiosity, fascination, and " + "distrust. People have long wondered about who these secret society members are and what they do, with the " + @@ -304,13 +308,14 @@ export const Literatures: Record = { [LiteratureName.CodedIntelligence]: new Literature({ title: "Coded Intelligence: Myth or Reality?", filename: LiteratureName.CodedIntelligence, + factionRumors: [FactionName.OmniTekIncorporated], text: "Tremendous progress has been made in the field of Artificial Intelligence over the past few decades. " + "Our autonomous vehicles and transportation systems. The electronic personal assistants that control our everyday lives. " + "Medical, service, and manufacturing robots. All of these are examples of how far AI has come and how much it has " + "improved our daily lives. However, the question still remains of whether AI will ever be advanced enough to re-create " + "human intelligence.

" + - `We've certainly come close to artificial intelligence that is similar to humans. For example ${FactionName.OmniTekIncorporated}'s ` + + `We've certainly come close to artificial intelligence that is similar to humans. For example ${CompanyName.OmniTekIncorporated}'s ` + "CompanionBot, a robot meant to act as a comforting friend for lonely and grieving people, is eerily human-like " + "in its appearance, speech, mannerisms, and even movement. However its artificial intelligence isn't the same as " + "that of humans. Not yet. It doesn't have sentience or self-awareness or consciousness.

" + @@ -335,14 +340,15 @@ export const Literatures: Record = { [LiteratureName.TensionsInTechRace]: new Literature({ title: "Tensions rise in global tech race", filename: LiteratureName.TensionsInTechRace, + factionRumors: [FactionName.OmniTekIncorporated, FactionName.MegaCorp, FactionName.ECorp], text: "Have we entered a new Cold War? Is WWIII just beyond the horizon?

" + - `After rumors came out that ${FactionName.OmniTekIncorporated} had begun developing advanced robotic supersoldiers, ` + + `After rumors came out that ${CompanyName.OmniTekIncorporated} had begun developing advanced robotic supersoldiers, ` + "geopolitical tensions quickly flared between the USA, Russia, and several Asian superpowers. " + - `In a rare show of cooperation between corporations, ${FactionName.MegaCorp} and ${FactionName.ECorp} have ` + + `In a rare show of cooperation between corporations, ${CompanyName.MegaCorp} and ${CompanyName.ECorp} have ` + "reportedly launched hundreds of new surveillance and espionage satellites. " + "Defense contractors such as " + - "DeltaOne and AeroCorp have been working with the CIA and NSA to prepare " + + `${CompanyName.DeltaOne} and ${CompanyName.AeroCorp} have been working with the CIA and NSA to prepare ` + "for conflict. Meanwhile, the rest of the world sits in earnest " + "hoping that it never reaches full-scale war. With today's technology " + "and firepower, a World War would assuredly mean the end of human civilization.", @@ -375,6 +381,7 @@ export const Literatures: Record = { [LiteratureName.TheHiddenWorld]: new Literature({ title: "The Hidden World", filename: LiteratureName.TheHiddenWorld, + factionRumors: [FactionName.Illuminati], text: "WAKE UP SHEEPLE

" + "THE GOVERNMENT DOES NOT EXIST. CORPORATIONS DO NOT RUN SOCIETY

" + @@ -394,6 +401,7 @@ export const Literatures: Record = { [LiteratureName.TheNewGod]: new Literature({ title: "The New God", filename: LiteratureName.TheNewGod, + factionRumors: [FactionName.ChurchOfTheMachineGod], text: "Everyone has a moment in their life when they wonder about the bigger questions.

" + "What's the point of all this? What is my purpose?

" + @@ -408,6 +416,7 @@ export const Literatures: Record = { [LiteratureName.NewTriads]: new Literature({ title: "The New Triads", filename: LiteratureName.NewTriads, + factionRumors: [FactionName.Tetrads], text: "The Triads were an ancient transnational crime syndicate based in China, Hong Kong, and other Asian " + "territories. They were often considered one of the first and biggest criminal secret societies. " + diff --git a/src/Message/Message.ts b/src/Message/Message.ts index 8fea986c5..0bd99914d 100644 --- a/src/Message/Message.ts +++ b/src/Message/Message.ts @@ -1,5 +1,5 @@ import { FilePath, asFilePath } from "../Paths/FilePath"; -import { MessageFilename } from "@enums"; +import { MessageFilename, FactionName } from "@enums"; export class Message { // Name of Message file @@ -8,8 +8,12 @@ export class Message { // The text contains in the Message msg: string; - constructor(filename: MessageFilename, msg: string) { + // Faction hinted at by the message + factionRumors: FactionName[]; + + constructor(filename: MessageFilename, msg: string, factionRumor?: FactionName) { this.filename = asFilePath(filename); this.msg = msg; + this.factionRumors = factionRumor ? [factionRumor] : []; } } diff --git a/src/Message/MessageHelpers.tsx b/src/Message/MessageHelpers.tsx index 2eff254b5..077b03337 100644 --- a/src/Message/MessageHelpers.tsx +++ b/src/Message/MessageHelpers.tsx @@ -12,10 +12,14 @@ import { Server } from "../Server/Server"; //Sends message to player, including a pop up function sendMessage(name: MessageFilename, forced = false): void { + const msg = Messages[name]; if (forced || !Settings.SuppressMessages) { showMessage(name); } addMessageToServer(name); + for (const factionName of msg.factionRumors) { + Player.receiveRumor(factionName); + } } function showMessage(name: MessageFilename): void { @@ -113,6 +117,7 @@ const Messages: Record = { "exploit them for their Augmentations. But do not trust them. " + "They are not what they seem. No one is.\n\n" + "-jump3R", + FactionName.CyberSec, ), [MessageFilename.Jumper2]: new Message( @@ -121,6 +126,7 @@ const Messages: Record = { "you want to find the truth, worry only about yourself. Ethics and " + `morals will get you killed. \n\nWatch out for a hacking group known as ${FactionName.NiteSec}.` + "\n\n-jump3R", + FactionName.NiteSec, ), [MessageFilename.Jumper3]: new Message( @@ -128,6 +134,7 @@ const Messages: Record = { "You must learn to walk before you can run. And you must " + `run before you can fly. Look for ${FactionName.TheBlackHand}. \n\n` + "I.I.I.I \n\n-jump3R", + FactionName.TheBlackHand, ), [MessageFilename.Jumper4]: new Message( @@ -135,6 +142,7 @@ const Messages: Record = { "To find what you are searching for, you must understand the bits. " + "The bits are all around us. The runners will help you.\n\n" + "-jump3R", + FactionName.BitRunners, ), //Messages from hacking factions @@ -145,6 +153,7 @@ const Messages: Record = { "the world for the better. If you join us, we can unlock your full potential. \n\n" + "But first, you must pass our test. Find and install the backdoor on our server. \n\n" + `-${FactionName.CyberSec}`, + FactionName.CyberSec, ), [MessageFilename.NiteSecTest]: new Message( @@ -156,6 +165,7 @@ const Messages: Record = { "Join us, and people will fear you, too. \n\n" + "Find and install the backdoor on our server, avmnite-02h. Then, we will contact you again." + `\n\n-${FactionName.NiteSec}`, + FactionName.NiteSec, ), [MessageFilename.BitRunnersTest]: new Message( @@ -164,6 +174,7 @@ const Messages: Record = { "what you are looking for. \n\n " + "We can help you find the answers.\n\n" + "run4theh111z", + FactionName.BitRunners, ), //Messages to guide players to the daemon @@ -174,6 +185,7 @@ const Messages: Record = { "%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(\n" + "*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$\n" + "()@)#$*%)$#()$#__Y0UR__(*)$#()%(&(%)*!)($__GAZ3__#(", + FactionName.Daedalus, ), [MessageFilename.RedPill]: new Message( @@ -183,6 +195,7 @@ const Messages: Record = { ")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)\n" + "@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB\n" + "DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)", + FactionName.Daedalus, ), }; diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index f39ee4376..e410562e3 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -181,6 +181,7 @@ const singularity = { getCompanyRep: SF4Cost(RamCostConstants.SingularityFn2 / 3), getCompanyFavor: SF4Cost(RamCostConstants.SingularityFn2 / 3), getCompanyFavorGain: SF4Cost(RamCostConstants.SingularityFn2 / 4), + getFactionInviteRequirements: SF4Cost(RamCostConstants.SingularityFn2), checkFactionInvitations: SF4Cost(RamCostConstants.SingularityFn2), joinFaction: SF4Cost(RamCostConstants.SingularityFn2), workForFaction: SF4Cost(RamCostConstants.SingularityFn2), diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 8616612d6..7f8c79cef 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -816,6 +816,12 @@ export function NetscriptSingularity(): InternalAPI { const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName); return Companies[companyName].getFavorGain(); }, + getFactionInviteRequirements: (ctx) => (_facName) => { + helpers.checkSingularityAccess(ctx); + const facName = getEnumHelper("FactionName").nsGetMember(ctx, _facName); + const fac = Factions[facName]; + return fac.getInfo().inviteReqs.map((condition) => condition.toString()); + }, checkFactionInvitations: (ctx) => () => { helpers.checkSingularityAccess(ctx); // Manually trigger a check for faction invites @@ -835,13 +841,6 @@ export function NetscriptSingularity(): InternalAPI { const fac = Factions[facName]; joinFaction(fac); - // Update Faction Invitation list to account for joined + banned factions - for (let i = 0; i < Player.factionInvitations.length; ++i) { - if (Player.factionInvitations[i] == facName || Factions[Player.factionInvitations[i]].isBanned) { - Player.factionInvitations.splice(i, 1); - i--; - } - } Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain * 5); helpers.log(ctx, () => `Joined the '${facName}' faction.`); return true; diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts index 52ceb31ea..95b66b378 100644 --- a/src/PersonObjects/Player/PlayerObject.ts +++ b/src/PersonObjects/Player/PlayerObject.ts @@ -38,6 +38,7 @@ export class PlayerObject extends Person implements IPlayer { currentServer = ""; factions: FactionName[] = []; factionInvitations: FactionName[] = []; + factionRumors: FactionName[] = []; hacknetNodes: (HacknetNode | string)[] = []; // HacknetNode object or hostname of Hacknet Server has4SData = false; has4SDataTixApi = false; @@ -131,6 +132,7 @@ export class PlayerObject extends Person implements IPlayer { createHacknetServer = serverMethods.createHacknetServer; queueAugmentation = generalMethods.queueAugmentation; receiveInvite = generalMethods.receiveInvite; + receiveRumor = generalMethods.receiveRumor; gainCodingContractReward = generalMethods.gainCodingContractReward; stopFocusing = generalMethods.stopFocusing; prestigeAugmentation = generalMethods.prestigeAugmentation; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts index 00de0d37a..89f6e7f73 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts @@ -4,6 +4,7 @@ import { CompanyName, CompletedProgramName, FactionName, + FactionDiscovery, JobName, LocationName, ToastVariant, @@ -33,8 +34,7 @@ import { Locations } from "../../Locations/Locations"; import { Sleeve } from "../Sleeve/Sleeve"; import { isSleeveCompanyWork } from "../Sleeve/Work/SleeveCompanyWork"; import { calculateSkillProgress as calculateSkillProgressF, ISkillProgress } from "../formulas/skill"; -import { GetServer, AddToAllServers, createUniqueRandomIp } from "../../Server/AllServers"; -import { Server } from "../../Server/Server"; +import { AddToAllServers, createUniqueRandomIp } from "../../Server/AllServers"; import { safelyCreateUniqueServer } from "../../Server/ServerHelpers"; import { SpecialServers } from "../../Server/data/SpecialServers"; @@ -42,7 +42,6 @@ import { applySourceFile } from "../../SourceFile/applySourceFile"; import { applyExploit } from "../../Exploits/applyExploits"; import { SourceFiles } from "../../SourceFile/SourceFiles"; import { getHospitalizationCost } from "../../Hospital/Hospital"; -import { HacknetServer } from "../../Hacknet/HacknetServer"; import { formatMoney } from "../../ui/formatNumber"; import { MoneySource, MoneySourceTracker } from "../../utils/MoneySourceTracker"; @@ -52,7 +51,6 @@ import { SnackbarEvents } from "../../ui/React/Snackbar"; import { achievements } from "../../Achievements/Achievements"; import { isCompanyWork } from "../../Work/CompanyWork"; -import { serverMetadata } from "../../Server/data/servers"; import { getEnumHelper, isMember } from "../../utils/EnumHelper"; export function init(this: PlayerObject): void { @@ -105,6 +103,7 @@ export function prestigeAugmentation(this: PlayerObject): void { this.factions = []; this.factionInvitations = []; + this.factionRumors = []; // Clear any pending invitation modals InvitationEvent.emit(null); @@ -171,10 +170,37 @@ export function prestigeSourceFile(this: PlayerObject): void { } export function receiveInvite(this: PlayerObject, factionName: FactionName): void { - if (this.factionInvitations.includes(factionName) || this.factions.includes(factionName)) { + if ( + this.factionInvitations.includes(factionName) || + Factions[factionName].isMember || + Factions[factionName].isBanned + ) { return; } this.factionInvitations.push(factionName); + if (this.factionRumors.includes(factionName)) { + this.factionRumors.splice(this.factionRumors.indexOf(factionName), 1); + } + Factions[factionName].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); } //Calculates skill level progress based on experience. The same formula will be used for every skill @@ -609,478 +635,11 @@ export function reapplyAllSourceFiles(this: PlayerObject): void { //those requirements and will return an array of all factions that the Player should //receive an invitation to export function checkForFactionInvitations(this: PlayerObject): Faction[] { - const invitedFactions: Faction[] = []; //Array which will hold all Factions the player should be invited to - - const numAugmentations = this.augmentations.length; - - const allCompanies = Object.keys(this.jobs); - const allPositions = Object.values(this.jobs); - - // Given a company name, safely returns the reputation (returns 0 if invalid company is specified) - function getCompanyRep(companyName: CompanyName): number { - const company = Companies[companyName]; - return company.playerReputation; + const invitedFactions = []; + for (const faction of Object.values(Factions)) { + if (faction.checkForInvite(this)) invitedFactions.push(faction); + if (faction.checkForRumor(this)) this.receiveRumor(faction.name); } - - // Helper function that returns a boolean indicating whether the Player meets - // the requirements for the specified company. There are two requirements: - // 1. High enough reputation - // 2. Player is employed at the company - function checkMegacorpRequirements(companyName: CompanyName): boolean { - const serverMeta = serverMetadata.find((s) => s.specialName === companyName); - const server = GetServer(serverMeta ? serverMeta.hostname : ""); - const bonus = (server as Server).backdoorInstalled ? -100e3 : 0; - return ( - allCompanies.includes(companyName) && getCompanyRep(companyName) > CONSTANTS.CorpFactionRepRequirement + bonus - ); - } - - //Illuminati - const illuminatiFac = Factions[FactionName.Illuminati]; - if ( - !illuminatiFac.isBanned && - !illuminatiFac.isMember && - !illuminatiFac.alreadyInvited && - numAugmentations >= 30 && - this.money >= 150000000000 && - this.skills.hacking >= 1500 && - this.skills.strength >= 1200 && - this.skills.defense >= 1200 && - this.skills.dexterity >= 1200 && - this.skills.agility >= 1200 - ) { - invitedFactions.push(illuminatiFac); - } - - //Daedalus - const daedalusFac = Factions[FactionName.Daedalus]; - if ( - !daedalusFac.isBanned && - !daedalusFac.isMember && - !daedalusFac.alreadyInvited && - numAugmentations >= currentNodeMults.DaedalusAugsRequirement && - this.money >= 100000000000 && - (this.skills.hacking >= 2500 || - (this.skills.strength >= 1500 && - this.skills.defense >= 1500 && - this.skills.dexterity >= 1500 && - this.skills.agility >= 1500)) - ) { - invitedFactions.push(daedalusFac); - } - - //The Covenant - const covenantFac = Factions[FactionName.TheCovenant]; - if ( - !covenantFac.isBanned && - !covenantFac.isMember && - !covenantFac.alreadyInvited && - numAugmentations >= 20 && - this.money >= 75000000000 && - this.skills.hacking >= 850 && - this.skills.strength >= 850 && - this.skills.defense >= 850 && - this.skills.dexterity >= 850 && - this.skills.agility >= 850 - ) { - invitedFactions.push(covenantFac); - } - - //ECorp - const ecorpFac = Factions[FactionName.ECorp]; - if ( - !ecorpFac.isBanned && - !ecorpFac.isMember && - !ecorpFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.ECorp) - ) { - invitedFactions.push(ecorpFac); - } - - //MegaCorp - const megacorpFac = Factions[FactionName.MegaCorp]; - if ( - !megacorpFac.isBanned && - !megacorpFac.isMember && - !megacorpFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.MegaCorp) - ) { - invitedFactions.push(megacorpFac); - } - - //Bachman & Associates - const bachmanandassociatesFac = Factions[FactionName.BachmanAssociates]; - if ( - !bachmanandassociatesFac.isBanned && - !bachmanandassociatesFac.isMember && - !bachmanandassociatesFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.BachmanAndAssociates) - ) { - invitedFactions.push(bachmanandassociatesFac); - } - - //Blade Industries - const bladeindustriesFac = Factions[FactionName.BladeIndustries]; - if ( - !bladeindustriesFac.isBanned && - !bladeindustriesFac.isMember && - !bladeindustriesFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.BladeIndustries) - ) { - invitedFactions.push(bladeindustriesFac); - } - - //NWO - const nwoFac = Factions[FactionName.NWO]; - if (!nwoFac.isBanned && !nwoFac.isMember && !nwoFac.alreadyInvited && checkMegacorpRequirements(CompanyName.NWO)) { - invitedFactions.push(nwoFac); - } - - //Clarke Incorporated - const clarkeincorporatedFac = Factions[FactionName.ClarkeIncorporated]; - if ( - !clarkeincorporatedFac.isBanned && - !clarkeincorporatedFac.isMember && - !clarkeincorporatedFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.ClarkeIncorporated) - ) { - invitedFactions.push(clarkeincorporatedFac); - } - - //OmniTek Incorporated - const omnitekincorporatedFac = Factions[FactionName.OmniTekIncorporated]; - if ( - !omnitekincorporatedFac.isBanned && - !omnitekincorporatedFac.isMember && - !omnitekincorporatedFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.OmniTekIncorporated) - ) { - invitedFactions.push(omnitekincorporatedFac); - } - - //Four Sigma - const foursigmaFac = Factions[FactionName.FourSigma]; - if ( - !foursigmaFac.isBanned && - !foursigmaFac.isMember && - !foursigmaFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.FourSigma) - ) { - invitedFactions.push(foursigmaFac); - } - - //KuaiGong International - const kuaigonginternationalFac = Factions[FactionName.KuaiGongInternational]; - if ( - !kuaigonginternationalFac.isBanned && - !kuaigonginternationalFac.isMember && - !kuaigonginternationalFac.alreadyInvited && - checkMegacorpRequirements(CompanyName.KuaiGongInternational) - ) { - invitedFactions.push(kuaigonginternationalFac); - } - - //Fulcrum Secret Technologies - If you've unlocked fulcrum secret technologies server and have a high rep with the company - const fulcrumsecrettechonologiesFac = Factions[FactionName.FulcrumSecretTechnologies]; - const fulcrumSecretServer = GetServer(SpecialServers.FulcrumSecretTechnologies); - if (!(fulcrumSecretServer instanceof Server)) - throw new Error(`${FactionName.FulcrumSecretTechnologies} should be normal server`); - if (fulcrumSecretServer == null) { - console.error(`Could not find ${FactionName.FulcrumSecretTechnologies} Server`); - } else if ( - !fulcrumsecrettechonologiesFac.isBanned && - !fulcrumsecrettechonologiesFac.isMember && - !fulcrumsecrettechonologiesFac.alreadyInvited && - fulcrumSecretServer.backdoorInstalled && - checkMegacorpRequirements(CompanyName.FulcrumTechnologies) - ) { - invitedFactions.push(fulcrumsecrettechonologiesFac); - } - - //BitRunners - const bitrunnersFac = Factions[FactionName.BitRunners]; - const bitrunnersServer = GetServer(SpecialServers.BitRunnersServer); - if (!(bitrunnersServer instanceof Server)) throw new Error(`${FactionName.BitRunners} should be normal server`); - if (bitrunnersServer == null) { - console.error(`Could not find ${FactionName.BitRunners} Server`); - } else if ( - !bitrunnersFac.isBanned && - !bitrunnersFac.isMember && - bitrunnersServer.backdoorInstalled && - !bitrunnersFac.alreadyInvited - ) { - invitedFactions.push(bitrunnersFac); - } - - //The Black Hand - - const theblackhandFac = Factions[FactionName.TheBlackHand]; - const blackhandServer = GetServer(SpecialServers.TheBlackHandServer); - if (!(blackhandServer instanceof Server)) throw new Error(`${FactionName.TheBlackHand} should be normal server`); - if (blackhandServer == null) { - console.error(`Could not find ${FactionName.TheBlackHand} Server`); - } else if ( - !theblackhandFac.isBanned && - !theblackhandFac.isMember && - blackhandServer.backdoorInstalled && - !theblackhandFac.alreadyInvited - ) { - invitedFactions.push(theblackhandFac); - } - - //NiteSec - const nitesecFac = Factions[FactionName.NiteSec]; - const nitesecServer = GetServer(SpecialServers.NiteSecServer); - if (!(nitesecServer instanceof Server)) throw new Error(`${FactionName.NiteSec} should be normal server`); - if (nitesecServer == null) { - console.error(`Could not find ${FactionName.NiteSec} Server`); - } else if ( - !nitesecFac.isBanned && - !nitesecFac.isMember && - nitesecServer.backdoorInstalled && - !nitesecFac.alreadyInvited - ) { - invitedFactions.push(nitesecFac); - } - - //Chongqing - const chongqingFac = Factions[FactionName.Chongqing]; - if ( - !chongqingFac.isBanned && - !chongqingFac.isMember && - !chongqingFac.alreadyInvited && - this.money >= 20000000 && - this.city == CityName.Chongqing - ) { - invitedFactions.push(chongqingFac); - } - - //Sector-12 - const sector12Fac = Factions[FactionName.Sector12]; - if ( - !sector12Fac.isBanned && - !sector12Fac.isMember && - !sector12Fac.alreadyInvited && - this.money >= 15000000 && - this.city == CityName.Sector12 - ) { - invitedFactions.push(sector12Fac); - } - - //New Tokyo - const newtokyoFac = Factions[FactionName.NewTokyo]; - if ( - !newtokyoFac.isBanned && - !newtokyoFac.isMember && - !newtokyoFac.alreadyInvited && - this.money >= 20000000 && - this.city == CityName.NewTokyo - ) { - invitedFactions.push(newtokyoFac); - } - - //Aevum - const aevumFac = Factions[FactionName.Aevum]; - if ( - !aevumFac.isBanned && - !aevumFac.isMember && - !aevumFac.alreadyInvited && - this.money >= 40000000 && - this.city == CityName.Aevum - ) { - invitedFactions.push(aevumFac); - } - - //Ishima - const ishimaFac = Factions[FactionName.Ishima]; - if ( - !ishimaFac.isBanned && - !ishimaFac.isMember && - !ishimaFac.alreadyInvited && - this.money >= 30000000 && - this.city == CityName.Ishima - ) { - invitedFactions.push(ishimaFac); - } - - //Volhaven - const volhavenFac = Factions[FactionName.Volhaven]; - if ( - !volhavenFac.isBanned && - !volhavenFac.isMember && - !volhavenFac.alreadyInvited && - this.money >= 50000000 && - this.city == CityName.Volhaven - ) { - invitedFactions.push(volhavenFac); - } - - //Speakers for the Dead - const speakersforthedeadFac = Factions[FactionName.SpeakersForTheDead]; - if ( - !speakersforthedeadFac.isBanned && - !speakersforthedeadFac.isMember && - !speakersforthedeadFac.alreadyInvited && - this.skills.hacking >= 100 && - this.skills.strength >= 300 && - this.skills.defense >= 300 && - this.skills.dexterity >= 300 && - this.skills.agility >= 300 && - this.numPeopleKilled >= 30 && - this.karma <= -45 && - !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA) - ) { - invitedFactions.push(speakersforthedeadFac); - } - - //The Dark Army - const thedarkarmyFac = Factions[FactionName.TheDarkArmy]; - if ( - !thedarkarmyFac.isBanned && - !thedarkarmyFac.isMember && - !thedarkarmyFac.alreadyInvited && - this.skills.hacking >= 300 && - this.skills.strength >= 300 && - this.skills.defense >= 300 && - this.skills.dexterity >= 300 && - this.skills.agility >= 300 && - this.city == CityName.Chongqing && - this.numPeopleKilled >= 5 && - this.karma <= -45 && - !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA) - ) { - invitedFactions.push(thedarkarmyFac); - } - - //The Syndicate - const thesyndicateFac = Factions[FactionName.TheSyndicate]; - if ( - !thesyndicateFac.isBanned && - !thesyndicateFac.isMember && - !thesyndicateFac.alreadyInvited && - this.skills.hacking >= 200 && - this.skills.strength >= 200 && - this.skills.defense >= 200 && - this.skills.dexterity >= 200 && - this.skills.agility >= 200 && - (this.city == CityName.Aevum || this.city == CityName.Sector12) && - this.money >= 10000000 && - this.karma <= -90 && - !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA) - ) { - invitedFactions.push(thesyndicateFac); - } - - //Silhouette - const silhouetteFac = Factions[FactionName.Silhouette]; - if ( - !silhouetteFac.isBanned && - !silhouetteFac.isMember && - !silhouetteFac.alreadyInvited && - (allPositions.includes(JobName.software7) || // CTO - allPositions.includes(JobName.business4) || // CFO - allPositions.includes(JobName.business5)) && // CEO - this.money >= 15000000 && - this.karma <= -22 - ) { - invitedFactions.push(silhouetteFac); - } - - //Tetrads - const tetradsFac = Factions[FactionName.Tetrads]; - if ( - !tetradsFac.isBanned && - !tetradsFac.isMember && - !tetradsFac.alreadyInvited && - (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || this.city == CityName.Ishima) && - this.skills.strength >= 75 && - this.skills.defense >= 75 && - this.skills.dexterity >= 75 && - this.skills.agility >= 75 && - this.karma <= -18 - ) { - invitedFactions.push(tetradsFac); - } - - //SlumSnakes - const slumsnakesFac = Factions[FactionName.SlumSnakes]; - if ( - !slumsnakesFac.isBanned && - !slumsnakesFac.isMember && - !slumsnakesFac.alreadyInvited && - this.skills.strength >= 30 && - this.skills.defense >= 30 && - this.skills.dexterity >= 30 && - this.skills.agility >= 30 && - this.karma <= -9 && - this.money >= 1000000 - ) { - invitedFactions.push(slumsnakesFac); - } - - //Netburners - const netburnersFac = Factions[FactionName.Netburners]; - let totalHacknetRam = 0; - let totalHacknetCores = 0; - let totalHacknetLevels = 0; - for (let i = 0; i < this.hacknetNodes.length; ++i) { - const v = this.hacknetNodes[i]; - if (typeof v === "string") { - const hserver = GetServer(v); - if (hserver === null || !(hserver instanceof HacknetServer)) - throw new Error("player hacknet server was not HacknetServer"); - totalHacknetLevels += hserver.level; - totalHacknetRam += hserver.maxRam; - totalHacknetCores += hserver.cores; - } else { - totalHacknetLevels += v.level; - totalHacknetRam += v.ram; - totalHacknetCores += v.cores; - } - } - if ( - !netburnersFac.isBanned && - !netburnersFac.isMember && - !netburnersFac.alreadyInvited && - this.skills.hacking >= 80 && - totalHacknetRam >= 8 && - totalHacknetCores >= 4 && - totalHacknetLevels >= 100 - ) { - invitedFactions.push(netburnersFac); - } - - //Tian Di Hui - const tiandihuiFac = Factions[FactionName.TianDiHui]; - if ( - !tiandihuiFac.isBanned && - !tiandihuiFac.isMember && - !tiandihuiFac.alreadyInvited && - this.money >= 1000000 && - this.skills.hacking >= 50 && - (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || this.city == CityName.Ishima) - ) { - invitedFactions.push(tiandihuiFac); - } - - //CyberSec - const cybersecFac = Factions[FactionName.CyberSec]; - const cybersecServer = GetServer(SpecialServers.CyberSecServer); - if (!(cybersecServer instanceof Server)) throw new Error(`${FactionName.CyberSec} should be normal server`); - if (cybersecServer == null) { - console.error(`Could not find ${FactionName.CyberSec} Server`); - } else if ( - !cybersecFac.isBanned && - !cybersecFac.isMember && - cybersecServer.backdoorInstalled && - !cybersecFac.alreadyInvited - ) { - invitedFactions.push(cybersecFac); - } - return invitedFactions; } diff --git a/src/Prestige.ts b/src/Prestige.ts index d8e60cfce..9afb20595 100755 --- a/src/Prestige.ts +++ b/src/Prestige.ts @@ -1,4 +1,4 @@ -import { AugmentationName, CityName, CompletedProgramName, FactionName, LiteratureName } from "@enums"; +import { AugmentationName, CityName, CompletedProgramName, FactionName, LiteratureName, CompanyName } from "@enums"; import { initBitNodeMultipliers } from "./BitNode/BitNode"; import { Companies } from "./Company/Companies"; import { resetIndustryResearchTrees } from "./Corporation/data/IndustryData"; @@ -34,9 +34,17 @@ function delayedDialog(message: string) { export function prestigeAugmentation(): void { initBitNodeMultipliers(); - const maintainMembership = Player.factions.concat(Player.factionInvitations).filter(function (faction) { - return Factions[faction].getInfo().keep; - }); + // Maintain invites to factions with the 'keepOnInstall' flag, and rumors about others + const maintainInvites = []; + const maintainRumors = []; + for (const facName of [...Player.factions, ...Player.factionInvitations]) { + if (Factions[facName].getInfo().keep) { + maintainInvites.push(facName); + } else { + maintainRumors.push(facName); + } + } + Player.prestigeAugmentation(); // Delete all Worker Scripts objects @@ -84,8 +92,8 @@ export function prestigeAugmentation(): void { // Recalculate the bonus for circadian modulator aug initCircadianModulator(); - Player.factionInvitations = Player.factionInvitations.concat(maintainMembership); - for (const factionName of maintainMembership) Factions[factionName].alreadyInvited = true; + Player.factionInvitations = Player.factionInvitations.concat(maintainInvites); + for (const factionName of maintainInvites) Factions[factionName].alreadyInvited = true; Player.reapplyAllAugmentations(); Player.reapplyAllSourceFiles(); Player.hp.current = Player.hp.max; @@ -144,12 +152,19 @@ export function prestigeAugmentation(): void { } } + // Bitnode 13: Church of the Machine God if (Player.hasAugmentation(AugmentationName.StaneksGift1, true)) { joinFaction(Factions[FactionName.ChurchOfTheMachineGod]); + } else if (Player.bitNodeN != 13) { + if (Player.augmentations.some((a) => a.name !== AugmentationName.NeuroFluxGovernor)) { + Factions[FactionName.ChurchOfTheMachineGod].isBanned = true; + } } - staneksGift.prestigeAugmentation(); + // Hear rumors after all invites/bans + for (const factionName of maintainRumors) Player.receiveRumor(factionName); + resetPidCounter(); ProgramsSeen.clear(); InvitationsSeen.clear(); @@ -230,7 +245,7 @@ export function prestigeSourceFile(isFlume: boolean): void { // BitNode 6: Bladeburners and BitNode 7: Bladeburners 2079 if (Player.bitNodeN === 6 || Player.bitNodeN === 7) { - delayedDialog("NSA would like to have a word with you once you're ready."); + delayedDialog(`The ${CompanyName.NSA} would like to have a word with you once you're ready.`); } // BitNode 8: Ghost of Wall Street @@ -245,7 +260,7 @@ export function prestigeSourceFile(isFlume: boolean): void { // BitNode 10: Digital Carbon if (Player.bitNodeN === 10) { delayedDialog( - "Seek out The Covenant if you'd like to purchase a new sleeve or two! And see what VitaLife in New Tokyo has to offer for you", + `Seek out ${FactionName.TheCovenant} if you'd like to purchase a new sleeve or two! And see what ${CompanyName.VitaLife} in ${CityName.NewTokyo} has to offer for you`, ); } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index dcd0c8969..dce05292d 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -1934,6 +1934,31 @@ export interface Singularity { */ getCompanyFavorGain(companyName: CompanyName | `${CompanyName}`): number; + /** + * List conditions for being invited to a faction. + * @remarks + * RAM cost: 3 GB * 16/4/1 + * + * + * @param faction - Name of the faction. + * @returns Array of strings describing conditions for receiving an invitation to the faction. + * + * @example + * ```js + * ns.singularity.getFactionInviteRequirements("The Syndicate") + * [ + * "Located in Aevum or Sector-12", + * "Not working for the Central Intelligence Agency", + * "Not working for the National Security Agency", + * "-90 karma", + * "Have $10.000m", + * "Hacking level 200", + * "All combat skills level 200" + * ] + * ``` + */ + getFactionInviteRequirements(faction: string): string[]; + /** * List all current faction invitations. * @remarks diff --git a/src/ui/Components/Requirement.tsx b/src/ui/Components/Requirement.tsx new file mode 100644 index 000000000..29db8d91e --- /dev/null +++ b/src/ui/Components/Requirement.tsx @@ -0,0 +1,27 @@ +import { CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material"; +import { Typography } from "@mui/material"; +import React from "react"; +import { Settings } from "../../Settings/Settings"; + +Settings.theme.primary; + +export interface IReqProps { + value: string; + color?: string; + incompleteColor?: string; + fulfilled: boolean; +} + +export const Requirement = (props: IReqProps): React.ReactElement => { + const completeColor = props.color || Settings.theme.primary; + const incompleteColor = props.incompleteColor || Settings.theme.primarydark; + + return ( + + {props.fulfilled ? : } + {props.value} + + ); +}; diff --git a/test/jest/__snapshots__/FullSave.test.ts.snap b/test/jest/__snapshots__/FullSave.test.ts.snap index 8e4c78a7f..43775f50c 100644 --- a/test/jest/__snapshots__/FullSave.test.ts.snap +++ b/test/jest/__snapshots__/FullSave.test.ts.snap @@ -277,6 +277,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -287,6 +288,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -297,6 +299,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -307,6 +310,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -317,6 +321,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": true, @@ -327,6 +332,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -337,6 +343,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -347,6 +354,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -357,6 +365,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 20, "isBanned": false, "isMember": true, @@ -367,6 +376,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -377,6 +387,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -387,6 +398,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -397,6 +409,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -407,6 +420,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -417,6 +431,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -427,6 +442,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -437,6 +453,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -447,6 +464,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -457,6 +475,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -467,6 +486,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -477,6 +497,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -487,6 +508,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -497,6 +519,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -507,6 +530,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -517,6 +541,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -527,6 +552,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": true, @@ -537,6 +563,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -547,6 +574,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -557,6 +585,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -567,6 +596,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -577,6 +607,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -587,6 +618,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -597,6 +629,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -607,6 +640,7 @@ exports[`Check Save File Continuity FactionsSave continuity 1`] = ` "ctor": "Faction", "data": { "alreadyInvited": false, + "discovery": "unknown", "favor": 0, "isBanned": false, "isMember": false, @@ -1238,6 +1272,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = ` }, "exploits": [], "factionInvitations": [], + "factionRumors": [], "factions": [ "Slum Snakes", "CyberSec",