FACTIONS: Add "Rumors" system for learning about faction join requirements (#888)

This commit is contained in:
Jesse Clark 2023-11-02 07:20:24 -07:00 committed by GitHub
parent 023f32bce3
commit fdcb8306d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1049 additions and 601 deletions

@ -0,0 +1,46 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [bitburner](./bitburner.md) &gt; [Singularity](./bitburner.singularity.md) &gt; [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"
]
```

@ -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. |

@ -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 (
<Typography
sx={{ display: "flex", alignItems: "center", color: props.fulfilled ? props.color : Settings.theme.error }}
>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
);
};
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 && (
<Requirement
fulfilled={props.parent.rep >= repCost}
value={`${formatReputation(repCost)} rep`}
color={Settings.theme.rep}
incompleteColor={Settings.theme.error}
/>
)}
</Box>

@ -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<JobName>();
@ -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 {

@ -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<CompanyName, CompanyCtorParams> {
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
relatedFaction: FactionName.ECorp,
},
[CompanyName.MegaCorp]: {
name: CompanyName.MegaCorp,
@ -31,6 +32,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
relatedFaction: FactionName.MegaCorp,
},
[CompanyName.BachmanAndAssociates]: {
name: CompanyName.BachmanAndAssociates,
@ -38,6 +40,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.6,
salaryMultiplier: 2.6,
jobStatReqOffset: 224,
relatedFaction: FactionName.BachmanAssociates,
},
[CompanyName.BladeIndustries]: {
name: CompanyName.BladeIndustries,
@ -45,6 +48,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 224,
relatedFaction: FactionName.BladeIndustries,
},
[CompanyName.NWO]: {
name: CompanyName.NWO,
@ -52,6 +56,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 249,
relatedFaction: FactionName.NWO,
},
[CompanyName.ClarkeIncorporated]: {
name: CompanyName.ClarkeIncorporated,
@ -59,6 +64,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
relatedFaction: FactionName.ClarkeIncorporated,
},
[CompanyName.OmniTekIncorporated]: {
name: CompanyName.OmniTekIncorporated,
@ -66,6 +72,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
relatedFaction: FactionName.OmniTekIncorporated,
},
[CompanyName.FourSigma]: {
name: CompanyName.FourSigma,
@ -73,6 +80,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.5,
salaryMultiplier: 2.5,
jobStatReqOffset: 224,
relatedFaction: FactionName.FourSigma,
},
[CompanyName.KuaiGongInternational]: {
name: CompanyName.KuaiGongInternational,
@ -80,6 +88,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2.2,
salaryMultiplier: 2.2,
jobStatReqOffset: 224,
relatedFaction: FactionName.KuaiGongInternational,
},
[CompanyName.FulcrumTechnologies]: {
name: CompanyName.FulcrumTechnologies,
@ -87,6 +96,7 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 224,
relatedFaction: FactionName.FulcrumSecretTechnologies,
},
[CompanyName.StormTechnologies]: {
name: CompanyName.StormTechnologies,

@ -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<HTMLInputElement>, value: string): void {
const disco = value as FactionDiscovery;
Factions[factionName].discovery = disco;
setFactionDiscovery(disco);
}
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
@ -115,12 +150,16 @@ export function FactionsDev(): React.ReactElement {
value={factionName}
startAdornment={
<>
<IconButton onClick={receiveAllInvites} size="large" arial-label="receive-all-invitation">
<ReplyAllIcon />
</IconButton>
<IconButton onClick={receiveInvite} size="large" arial-label="receive-one-invitation">
<ReplyIcon />
</IconButton>
<Tooltip title={`Hear rumor about ${factionName}`}>
<IconButton onClick={receiveRumor} size="large">
<ChatIcon />
</IconButton>
</Tooltip>
<Tooltip title={`Receive invitation to ${factionName}`}>
<IconButton onClick={receiveInvite} size="large">
<ReplyIcon />
</IconButton>
</Tooltip>
</>
}
>
@ -133,6 +172,20 @@ export function FactionsDev(): React.ReactElement {
</FormControl>
</td>
</tr>
<tr>
<td>
<Typography>Discovery:</Typography>
</td>
<td>
<FormControl>
<RadioGroup onChange={setDiscovery} value={factionDiscovery} row>
{Object.entries(FactionDiscovery).map(([discoveryLabel, discovery]) => (
<FormControlLabel key={discovery} value={discovery} label={discoveryLabel} control={<Radio />} />
))}
</RadioGroup>
</FormControl>
</td>
</tr>
<tr>
<td>
<Typography>Reputation:</Typography>
@ -163,6 +216,28 @@ export function FactionsDev(): React.ReactElement {
/>
</td>
</tr>
<tr>
<td>
<Typography>All Factions:</Typography>
</td>
<td>
<Tooltip title="Forget all discovery">
<Button onClick={resetAllDiscovery} size="large">
<ChatBubbleIcon />
</Button>
</Tooltip>
<Tooltip title="Hear all rumors">
<Button onClick={receiveAllRumors} size="large">
<ChatIcon />
</Button>
</Tooltip>
<Tooltip title="Receive all invitations">
<Button onClick={receiveAllInvites} size="large">
<ReplyAllIcon />
</Button>
</Tooltip>
</td>
</tr>
<tr>
<td>
<Typography>All Reputation:</Typography>

@ -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",
}

@ -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. */

@ -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

@ -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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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, FactionInfo> = {
[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<FactionName, FactionInfo> = {
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, FactionInfo> = {
[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, FactionInfo> = {
[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<FactionName, FactionInfo> = {
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, FactionInfo> = {
[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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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:(<>
{" __..__ "}<br />
{" _.nITESECNIt. "}<br />
{" .-'NITESECNITESEc. "}<br />
@ -283,52 +418,79 @@ export const FactionInfos: Record<FactionName, FactionInfo> = {
{" d .dNITESEC $ | "}<br />
{" :bp.__.gNITESEC/$ :$ ; "}<br />
{" NITESECNITESECNIT /$b : "}<br /></>),
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<FactionName, FactionInfo> = {
// 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, FactionInfo> = {
[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<FactionName, FactionInfo> = {
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<FactionName, FactionInfo> = {
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, FactionInfo> = {
{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<FactionName, FactionInfo> = {
{" `..` "}<br /><br />
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<FactionName, FactionInfo> = {
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 => {

@ -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,
};
}
}
}

@ -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<Faction>(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;
}
}

@ -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) => (
<Requirement
key={i}
fulfilled={condition.isSatisfied(Player)}
value={condition.toString()}
color={Settings.theme.primary}
incompleteColor={Settings.theme.primarydark}
/>
))}
</>
);
};
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" : ""),
}}
>
<Box display="flex" sx={{ alignItems: "center" }}>
{props.joined ? (
{props.faction.isMember ? (
<Box
display="grid"
sx={{
@ -92,13 +110,13 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
<Button onClick={() => openFaction(props.faction)}>Details</Button>
<Button onClick={() => openFactionAugPage(props.faction)}>Augments</Button>
</Box>
) : (
) : props.faction.alreadyInvited ? (
<Button sx={{ height: "48px", mr: 1 }} onClick={(e) => acceptInvitation(e, props.faction.name)}>
Join!
</Button>
)}
) : null}
<span style={{ maxWidth: props.joined ? "70%" : "95%" }}>
<span style={{ maxWidth: props.faction.isMember ? "70%" : "95%", overflow: "hidden" }}>
<Typography
variant="h6"
sx={{
@ -108,11 +126,26 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
alignItems: "center",
}}
>
<Tooltip title={props.faction.name}>
<span style={{ overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }}>
{props.faction.name}
</span>
</Tooltip>
{props.faction.discovery == FactionDiscovery.known ? (
<Tooltip
title={
<>
<Typography sx={{ textAlign: "center" }}>{props.faction.name}</Typography>
<JoinChecklist faction={props.faction} />
</>
}
>
<span style={{ overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }}>
{props.faction.name}
</span>
</Tooltip>
) : (
<Tooltip title={"Rumored Faction"}>
<span style={{ overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis" }}>
<CorruptableText content={props.faction.name} />
</span>
</Tooltip>
)}
<span style={{ display: "flex", alignItems: "center" }}>
{Player.hasGangWith(props.faction.name) && (
@ -127,7 +160,7 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
</Tooltip>
)}
{!props.joined && facInfo.enemies.length > 0 && (
{!props.faction.isMember && facInfo.enemies.length > 0 && (
<Tooltip
title={
<Typography component="div">
@ -148,13 +181,23 @@ const FactionElement = (props: FactionElementProps): React.ReactElement => {
</Typography>
<span style={{ display: "flex", alignItems: "center" }}>
{!Player.hasGangWith(props.faction.name) && <WorkTypesOffered faction={props.faction} />}
<Typography variant="body2" sx={{ display: "flex" }}>{`${augsLeft || "No"} Augmentations left`}</Typography>
{props.faction.isMember || props.faction.alreadyInvited ? (
<>
{!Player.hasGangWith(props.faction.name) && <WorkTypesOffered faction={props.faction} />}
<Typography variant="body2" sx={{ display: "flex", whiteSpace: "nowrap" }}>{`${
augsLeft || "No"
} Augmentations left`}</Typography>
</>
) : (
<Typography variant="body2" component="div">
<i>{props.faction.getInfo().rumorText}</i>
</Typography>
)}
</span>
</span>
</Box>
{props.joined && (
{props.faction.isMember && (
<Box display="grid" sx={{ alignItems: "center", justifyItems: "left", gridAutoFlow: "row" }}>
<Typography sx={{ color: Settings.theme.rep }}>
{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 (
<Container disableGutters maxWidth="lg" sx={{ mx: 0, mb: 10 }}>
@ -213,44 +259,53 @@ export function FactionsRoot(): React.ReactElement {
},
}}
>
{invitations.length > 0 && (
<span>
<Typography variant="h5" color="primary">
Faction Invitations
</Typography>
<Box>
{invitations.map((facName) => {
if (!Object.hasOwn(Factions, facName)) return null;
return <FactionElement key={facName} faction={Factions[facName]} joined={false} rerender={rerender} />;
})}
</Box>
</span>
)}
<span>
{invitations.length > 0 && (
<>
<Typography variant="h5" color="primary">
Faction Invitations
</Typography>
<Box>
{invitations.map((faction) => (
<FactionElement key={faction.name} faction={faction} rerender={rerender} />
))}
</Box>
</>
)}
{rumors.length > 0 && (
<>
<Typography variant="h5" color="primary">
Rumors
</Typography>
<div style={{ display: "grid", gap: 1, gridAutoRows: "minmax(70px, auto)" }}>
{rumors.map((faction) => (
<FactionElement key={faction.name} faction={faction} rerender={rerender} />
))}
</div>
</>
)}
</span>
<span>
{Player.inGang() && (
<Typography variant="h5" color="primary">
Your Gang
</Typography>
)}
{Player.inGang() && (
<Box>
<FactionElement
key={Player.getGangName()}
faction={Player.getGangFaction()}
joined={true}
rerender={rerender}
/>
</Box>
<>
<Typography variant="h5" color="primary">
Your Gang
</Typography>
<Box>
<FactionElement key={Player.getGangName()} faction={Player.getGangFaction()} rerender={rerender} />
</Box>
</>
)}
<Typography variant="h5" color="primary">
Your Factions
</Typography>
<Box>
{allJoinedFactions.length > 0 ? (
allJoinedFactions.map((facName) => {
if (!Object.hasOwn(Factions, facName) || Player.getGangName() === facName) return null;
return <FactionElement key={facName} faction={Factions[facName]} joined={true} rerender={rerender} />;
allJoinedFactions.map((faction) => {
if (Player.getGangName() === faction.name) return null;
return <FactionElement key={faction.name} faction={faction} rerender={rerender} />;
})
) : (
<Typography>You have not yet joined any Factions.</Typography>

@ -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

@ -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;
}
}

@ -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 = `<i>${litObj.title}</i><br><br>${litObj.text}`;
dialogBoxCreate(txt, true);
}

@ -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<LiteratureName, Literature> = {
@ -77,6 +77,7 @@ export const Literatures: Record<LiteratureName, Literature> = {
[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, Literature> = {
[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.<br><br>" +
"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<LiteratureName, Literature> = {
"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, Literature> = {
[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, Literature> = {
[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, Literature> = {
[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.<br><br>" +
`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.<br><br>" +
@ -335,14 +340,15 @@ export const Literatures: Record<LiteratureName, Literature> = {
[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?<br><br>" +
`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, Literature> = {
[LiteratureName.TheHiddenWorld]: new Literature({
title: "The Hidden World",
filename: LiteratureName.TheHiddenWorld,
factionRumors: [FactionName.Illuminati],
text:
"WAKE UP SHEEPLE<br><br>" +
"THE GOVERNMENT DOES NOT EXIST. CORPORATIONS DO NOT RUN SOCIETY<br><br>" +
@ -394,6 +401,7 @@ export const Literatures: Record<LiteratureName, Literature> = {
[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.<br><br>" +
"What's the point of all this? What is my purpose?<br><br>" +
@ -408,6 +416,7 @@ export const Literatures: Record<LiteratureName, Literature> = {
[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. " +

@ -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] : [];
}
}

@ -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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"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<MessageFilename, Message> = {
"%@*$^$()@&$)$*@__CAN__()(@^#)@&@)#__N0__(#@&#)@&@&(\n" +
"*(__LON6ER__^#)@)(()*#@)@__ESCAP3__)#(@(#@*@()@(#*$\n" +
"()@)#$*%)$#()$#__Y0UR__(*)$#()%(&(%)*!)($__GAZ3__#(",
FactionName.Daedalus,
),
[MessageFilename.RedPill]: new Message(
@ -183,6 +195,7 @@ const Messages: Record<MessageFilename, Message> = {
")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)\n" +
"@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB\n" +
"DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)",
FactionName.Daedalus,
),
};

@ -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),

@ -816,6 +816,12 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
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<ISingularity> {
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;

@ -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;

@ -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;
}

@ -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`,
);
}

@ -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

@ -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 (
<Typography
sx={{ display: "flex", alignItems: "center", color: props.fulfilled ? completeColor : incompleteColor }}
>
{props.fulfilled ? <CheckBox sx={{ mr: 1 }} /> : <CheckBoxOutlineBlank sx={{ mr: 1 }} />}
{props.value}
</Typography>
);
};

@ -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",