This commit is contained in:
Olivier Gagnon
2021-09-17 19:43:08 -04:00
parent 1344a17482
commit 45f2f85a30
58 changed files with 1738 additions and 2307 deletions

View File

@ -1,16 +1,17 @@
import React from "react";
import { hackWorldDaemon } from "../../RedPill";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { removePopup } from "../../ui/React/createPopup";
interface IProps {
player: IPlayer;
router: IRouter;
popupId: string;
}
export function BitFlumePopup(props: IProps): React.ReactElement {
function flume(): void {
hackWorldDaemon(props.player.bitNodeN, true, false);
props.router.toBitVerse(true, false);
removePopup(props.popupId);
}
return (

View File

@ -1,18 +1,22 @@
import React, { useState } from "react";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode";
import { enterBitNode } from "../../RedPill";
import { PortalPopup } from "./PortalPopup";
import { createPopup } from "../../ui/React/createPopup";
import { CinematicText } from "../../ui/React/CinematicText";
import { use } from "../../ui/Context";
interface IPortalProps {
n: number;
level: number;
destroyedBitNode: number;
flume: boolean;
enter: (flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
}
function BitNodePortal(props: IPortalProps): React.ReactElement {
const router = use.Router();
const bitNode = BitNodes[`BitNode${props.n}`];
if (bitNode == null) {
return <>O</>;
@ -32,6 +36,7 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
n: props.n,
level: props.level,
enter: props.enter,
router: router,
destroyedBitNode: props.destroyedBitNode,
flume: props.flume,
popupId: popupId,
@ -57,18 +62,20 @@ function BitNodePortal(props: IPortalProps): React.ReactElement {
interface IProps {
flume: boolean;
destroyedBitNodeNum: number;
quick: boolean;
enter: (flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
}
export function BitverseRoot(props: IProps): React.ReactElement {
const player = use.Player();
const enter = enterBitNode;
const destroyed = player.bitNodeN;
const [destroySequence, setDestroySequence] = useState(true && !props.quick);
// Update NextSourceFileFlags
const nextSourceFileFlags = SourceFileFlags.slice();
if (!props.flume) {
if (nextSourceFileFlags[props.destroyedBitNodeNum] < 3) ++nextSourceFileFlags[props.destroyedBitNodeNum];
if (nextSourceFileFlags[destroyed] < 3) ++nextSourceFileFlags[destroyed];
}
if (destroySequence) {
@ -84,7 +91,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
"0020 7124696B 0000FF69 74652E6F FFFF1111",
"----------------------------------------",
"Failsafe initiated...",
`Restarting BitNode-${props.destroyedBitNodeNum}...`,
`Restarting BitNode-${destroyed}...`,
"...........",
"...........",
"[ERROR] FAILED TO AUTOMATICALLY REBOOT BITNODE",
@ -96,6 +103,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
"..............................................",
]}
onDone={() => setDestroySequence(false)}
auto={true}
/>
);
}
@ -116,16 +124,16 @@ export function BitverseRoot(props: IProps): React.ReactElement {
<pre> \| O | |_/ |\| \ O \__| \_| | O |/ </pre>
<pre> | | |_/ | | \| / | \_| | | </pre>
<pre> \| / \| | / / \ |/ </pre>
<pre> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | </pre>
<pre> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> </pre>
<pre> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </pre>
<pre> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </pre>
<pre> | | | / / \ \ | | | </pre>
<pre> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> \ | |/ </pre>
<pre> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </pre>
<pre> \ | / / | | \ \ | / </pre>
<pre> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> PMUJ/ / </pre>
<pre> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </pre>
<pre> \|| | | | | | | | | ||/ </pre>
<pre> \| \_ | | | | | | _/ |/ </pre>
<pre> \ \| / \ / \ |/ / </pre>
<pre> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={props.enter} flume={props.flume} destroyedBitNode={props.destroyedBitNodeNum} /> </pre>
<pre> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </pre>
<pre> | | | | | | | | </pre>
<pre> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </pre>
<br />

View File

@ -1,13 +1,15 @@
import React from "react";
import { BitNodes } from "../BitNode";
import { IRouter } from "../../ui/Router";
import { removePopup } from "../../ui/React/createPopup";
interface IProps {
n: number;
level: number;
destroyedBitNode: number;
flume: boolean;
enter: (flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
router: IRouter;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
popupId: string;
}
@ -33,7 +35,7 @@ export function PortalPopup(props: IProps): React.ReactElement {
<button
className="std-button"
onClick={() => {
props.enter(props.flume, props.destroyedBitNode, props.n);
props.enter(props.router, props.flume, props.destroyedBitNode, props.n);
removePopup(props.popupId);
}}
>

View File

@ -15,6 +15,7 @@ import { Skill } from "./Skill";
import { City } from "./City";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { ConsoleHelpText } from "./data/Help";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
@ -25,7 +26,7 @@ import { addOffset } from "../../utils/helpers/addOffset";
import { Faction } from "../Faction/Faction";
import { Factions, factionExists } from "../Faction/Factions";
import { calculateHospitalizationCost } from "../Hospital/Hospital";
import { hackWorldDaemon, redPillFlag } from "../RedPill";
import { redPillFlag } from "../RedPill";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { Settings } from "../Settings/Settings";
import { Augmentations } from "../Augmentation/Augmentations";
@ -1203,7 +1204,7 @@ export class Bladeburner implements IBladeburner {
}
}
completeAction(player: IPlayer): void {
completeAction(router: IRouter, player: IPlayer): void {
switch (this.action.type) {
case ActionTypes["Contract"]:
case ActionTypes["Operation"]: {
@ -1338,7 +1339,7 @@ export class Bladeburner implements IBladeburner {
// Operation Daedalus
if (action.name === "Operation Daedalus") {
this.resetAction();
return hackWorldDaemon(player.bitNodeN);
return router.toBitVerse(false, false);
}
if (this.logging.blackops) {
@ -1540,7 +1541,7 @@ export class Bladeburner implements IBladeburner {
}
}
processAction(player: IPlayer, seconds: number): void {
processAction(router: IRouter, player: IPlayer, seconds: number): void {
if (this.action.type === ActionTypes["Idle"]) return;
if (this.actionTimeToComplete <= 0) {
throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`);
@ -1555,7 +1556,7 @@ export class Bladeburner implements IBladeburner {
this.actionTimeOverflow = 0;
if (this.actionTimeCurrent >= this.actionTimeToComplete) {
this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete;
return this.completeAction(player);
return this.completeAction(router, player);
}
}
@ -1888,10 +1889,10 @@ export class Bladeburner implements IBladeburner {
});
}
process(player: IPlayer): void {
process(router: IRouter, player: IPlayer): void {
// Edge case condition...if Operation Daedalus is complete trigger the BitNode
if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) {
return hackWorldDaemon(player.bitNodeN);
return router.toBitVerse(false, false);
}
// If the Player starts doing some other actions, set action to idle and alert
@ -1958,7 +1959,7 @@ export class Bladeburner implements IBladeburner {
this.randomEventCounter += getRandomInt(240, 600);
}
this.processAction(player, seconds);
this.processAction(router, player, seconds);
// Automation
if (this.automateEnabled) {

View File

@ -3,6 +3,7 @@ import { City } from "./City";
import { Skill } from "./Skill";
import { IAction } from "./IAction";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
export interface IBladeburner {
@ -103,11 +104,11 @@ export interface IBladeburner {
completeOperation(success: boolean): void;
getActionObject(actionId: IActionIdentifier): IAction | null;
completeContract(success: boolean): void;
completeAction(player: IPlayer): void;
completeAction(router: IRouter, player: IPlayer): void;
changeRank(player: IPlayer, change: number): void;
processAction(player: IPlayer, seconds: number): void;
processAction(router: IRouter, player: IPlayer, seconds: number): void;
calculateStaminaGainPerSecond(player: IPlayer): number;
calculateMaxStamina(player: IPlayer): void;
create(): void;
process(player: IPlayer): void;
process(router: IRouter, player: IPlayer): void;
}

View File

@ -0,0 +1,42 @@
import React from "react";
import { use } from "../../ui/Context";
import { CinematicText } from "../../ui/React/CinematicText";
import { dialogBoxCreate } from "../../../utils/DialogBox";
export function BladeburnerCinematic(): React.ReactElement {
const router = use.Router();
return (
<CinematicText
lines={[
"In the middle of the 21st century, OmniTek Incorporated advanced robot evolution ",
"with their Synthoids (synthetic androids), a being virtually identical to a human.",
"------",
"Their sixth-generation Synthoids, called MK-VI, were stronger, faster, and more ",
"intelligent than humans. Many argued that the MK-VI Synthoids were the first ",
"example of sentient AI.",
"------",
"Unfortunately, in 2070 a terrorist group called Ascendis Totalis hacked into OmniTek and ",
"uploaded a rogue AI into their Synthoid manufacturing facilities.",
"------",
"The MK-VI Synthoids infected by the rogue AI turned hostile toward humanity, initiating ",
"the deadliest conflict in human history. This dark chapter is now known as the Synthoid Uprising.",
"------",
"In the aftermath of the Uprising, further manufacturing of Synthoids with advanced AI ",
"was banned. MK-VI Synthoids that did not have the rogue Ascendis Totalis AI were ",
"allowed to continue their existence.",
"------",
"The intelligence community believes that not all of the rogue MK-VI Synthoids from the Uprising were ",
"found and destroyed, and that many of them are blending in as normal humans in society today. ",
"As a result, many nations have created Bladeburner divisions, special units that are tasked with ",
"investigating and dealing with Synthoid threats.",
]}
onDone={() => {
router.toTerminal();
dialogBoxCreate(
"Visit the National Security Agency (NSA) to apply for their Bladeburner " +
"division! You will need 100 of each combat stat before doing this.",
);
}}
/>
);
}

View File

@ -3,17 +3,16 @@ import { Stats } from "./Stats";
import { Console } from "./Console";
import { AllPages } from "./AllPages";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { use } from "../../ui/Context";
import { IBladeburner } from "../IBladeburner";
interface IProps {
bladeburner: IBladeburner;
engine: IEngine;
player: IPlayer;
}
export function Root(props: IProps): React.ReactElement {
export function BladeburnerRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
return (
<div className="bladeburner-container">
<div style={{ height: "60%", display: "block", position: "relative" }}>
@ -25,9 +24,9 @@ export function Root(props: IProps): React.ReactElement {
border: "1px solid white",
}}
>
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} />
<Stats bladeburner={props.bladeburner} player={player} router={router} />
</div>
<Console bladeburner={props.bladeburner} player={props.player} />
<Console bladeburner={props.bladeburner} player={player} />
</div>
<div
style={{
@ -39,7 +38,7 @@ export function Root(props: IProps): React.ReactElement {
position: "relative",
}}
>
<AllPages bladeburner={props.bladeburner} player={props.player} />
<AllPages bladeburner={props.bladeburner} player={player} />
</div>
</div>
);

View File

@ -2,21 +2,21 @@ import React, { useState, useEffect } from "react";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { BladeburnerConstants } from "../data/Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Money } from "../../ui/React/Money";
import { StatsTable } from "../../ui/React/StatsTable";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createPopup } from "../../ui/React/createPopup";
import { Factions } from "../../Faction/Factions";
import { joinFaction, displayFactionContent } from "../../Faction/FactionHelpers";
import { IRouter } from "../../ui/Router";
import { joinFaction } from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner";
import { TravelPopup } from "./TravelPopup";
interface IProps {
bladeburner: IBladeburner;
engine: IEngine;
router: IRouter;
player: IPlayer;
}
@ -72,8 +72,7 @@ export function Stats(props: IProps): React.ReactElement {
function openFaction(): void {
const faction = Factions["Bladeburners"];
if (faction.isMember) {
props.engine.loadFactionContent();
displayFactionContent("Bladeburners");
props.router.toFaction(faction);
} else {
if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
joinFaction(faction);

View File

@ -1,6 +1,7 @@
import { IPlayer } from "./PersonObjects/IPlayer";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { IEngine } from "./IEngine";
import { IRouter } from "./ui/Router";
import React from "react";
import { TTheme as Theme } from "./ui/React/Theme";
@ -24,6 +25,7 @@ import { TimeSkip } from "./DevMenu/ui/TimeSkip";
interface IProps {
player: IPlayer;
engine: IEngine;
router: IRouter;
}
export function DevMenuRoot(props: IProps): React.ReactElement {
@ -31,7 +33,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
<Theme>
<>
<h1>Development Menu - Only meant to be used for testing/debugging</h1>
<General player={props.player} />
<General player={props.player} router={props.router} />
<Stats player={props.player} />
<Factions player={props.player} />
<Augmentations player={props.player} />

View File

@ -8,10 +8,11 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Button from "@mui/material/Button";
import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { hackWorldDaemon } from "../../RedPill";
import { IRouter } from "../../ui/Router";
interface IProps {
player: IPlayer;
router: IRouter;
}
export function General(props: IProps): React.ReactElement {
@ -26,19 +27,19 @@ export function General(props: IProps): React.ReactElement {
}
function quickB1tFlum3(): void {
hackWorldDaemon(props.player.bitNodeN, true, true);
props.router.toBitVerse(true, true);
}
function b1tflum3(): void {
hackWorldDaemon(props.player.bitNodeN, true);
props.router.toBitVerse(true, false);
}
function quickHackW0r1dD43m0n(): void {
hackWorldDaemon(props.player.bitNodeN, false, true);
props.router.toBitVerse(false, true);
}
function hackW0r1dD43m0n(): void {
hackWorldDaemon(props.player.bitNodeN);
props.router.toBitVerse(false, false);
}
return (

View File

@ -30,7 +30,7 @@ export function TimeSkip(props: IProps): React.ReactElement {
return (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<h2>Sleeves</h2>
<h2>Time skip</h2>
</AccordionSummary>
<AccordionDetails>
<Button onClick={timeskip(60 * 1000)}>1 minute</Button>

View File

@ -4,6 +4,5 @@ import { Faction } from "../Faction/Faction";
export declare function getNextNeurofluxLevel(): number;
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;
export declare function displayFactionContent(factionName: string, initiallyOnAugmentationsPage: boolean = false);
export declare function joinFaction(faction: Faction): void;
export declare function startHackingMission(faction: Faction): void;

View File

@ -1,14 +1,9 @@
import React from "react";
import ReactDOM from "react-dom";
import { FactionRoot } from "./ui/FactionRoot";
import { Augmentations } from "../Augmentation/Augmentations";
import { PlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Engine } from "../engine";
import { Faction } from "./Faction";
import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions";
@ -65,29 +60,6 @@ export function startHackingMission(faction) {
mission.init();
}
//Displays the HTML content for a specific faction
export function displayFactionContent(factionName, initiallyOnAugmentationsPage = false) {
const faction = Factions[factionName];
if (faction == null) {
throw new Error(`Invalid factionName passed into displayFactionContent(): ${factionName}`);
}
if (!faction.isMember) {
throw new Error(`Not a member of this faction. Cannot display faction information`);
}
ReactDOM.render(
<FactionRoot
engine={Engine}
initiallyOnAugmentationsPage={initiallyOnAugmentationsPage}
faction={faction}
p={Player}
startHackingMissionFn={startHackingMission}
/>,
Engine.Display.content,
);
}
//Returns a boolean indicating whether the player has the prerequisites for the
//specified Augmentation
export function hasAugmentationPrereqs(aug) {
@ -187,9 +159,6 @@ export function purchaseAugmentation(aug, fac, sing = false) {
);
}
}
// Force a rerender of the Augmentations page
displayFactionContent(fac.name, true);
} else {
dialogBoxCreate(
"Hmm, something went wrong when trying to purchase an Augmentation. " +

View File

@ -1,52 +1,37 @@
/**
* Root React Component for displaying a faction's "Purchase Augmentations" page
*/
import * as React from "react";
import React, { useState } from "react";
import { PurchaseableAugmentation } from "./PurchaseableAugmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { PurchaseAugmentationsOrderSetting } from "../../Settings/SettingEnums";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
type IProps = {
faction: Faction;
p: IPlayer;
routeToMainPage: () => void;
};
type IState = {
rerenderFlag: boolean;
sortOrder: PurchaseAugmentationsOrderSetting;
};
const infoStyleMarkup = {
width: "70%",
};
export class AugmentationsPage extends React.Component<IProps, IState> {
export function AugmentationsPage(props: IProps): React.ReactElement {
const player = use.Player();
// Flag for whether the player has a gang with this faction
isPlayersGang: boolean;
constructor(props: IProps) {
super(props);
const isPlayersGang = player.inGang() && player.getGangName() === props.faction.name;
this.isPlayersGang = props.p.inGang() && props.p.getGangName() === props.faction.name;
const setRerender = useState(false)[1];
this.state = {
rerenderFlag: false,
sortOrder: PurchaseAugmentationsOrderSetting.Default,
};
this.rerender = this.rerender.bind(this);
function rerender(): void {
setRerender((old) => !old);
}
getAugs(): string[] {
if (this.isPlayersGang) {
function getAugs(): string[] {
if (isPlayersGang) {
const augs: string[] = [];
for (const augName in Augmentations) {
const aug = Augmentations[augName];
@ -57,25 +42,25 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs;
} else {
return this.props.faction.augmentations.slice();
return props.faction.augmentations.slice();
}
}
getAugsSorted(): string[] {
function getAugsSorted(): string[] {
switch (Settings.PurchaseAugmentationsOrder) {
case PurchaseAugmentationsOrderSetting.Cost: {
return this.getAugsSortedByCost();
return getAugsSortedByCost();
}
case PurchaseAugmentationsOrderSetting.Reputation: {
return this.getAugsSortedByReputation();
return getAugsSortedByReputation();
}
default:
return this.getAugsSortedByDefault();
return getAugsSortedByDefault();
}
}
getAugsSortedByCost(): string[] {
const augs = this.getAugs();
function getAugsSortedByCost(): string[] {
const augs = getAugs();
augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
@ -89,8 +74,8 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs;
}
getAugsSortedByReputation(): string[] {
const augs = this.getAugs();
function getAugsSortedByReputation(): string[] {
const augs = getAugs();
augs.sort((augName1, augName2) => {
const aug1 = Augmentations[augName1],
aug2 = Augmentations[augName2];
@ -103,88 +88,70 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
return augs;
}
getAugsSortedByDefault(): string[] {
return this.getAugs();
function getAugsSortedByDefault(): string[] {
return getAugs();
}
switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void {
function switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void {
Settings.PurchaseAugmentationsOrder = newOrder;
this.rerender();
rerender();
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
};
});
}
const augs = getAugsSorted();
const purchasable = augs.filter(
(aug: string) =>
aug === AugmentationNames.NeuroFluxGovernor ||
(!player.augmentations.some((a) => a.name === aug) && !player.queuedAugmentations.some((a) => a.name === aug)),
);
render(): React.ReactNode {
const augs = this.getAugsSorted();
const purchasable = augs.filter(
(aug: string) => aug === AugmentationNames.NeuroFluxGovernor ||
(!this.props.p.augmentations.some((a) => a.name === aug) &&
!this.props.p.queuedAugmentations.some((a) => a.name === aug)),
);
const purchaseableAugmentation = (aug: string): React.ReactNode => {
return <PurchaseableAugmentation augName={aug} faction={props.faction} key={aug} p={player} rerender={rerender} />;
};
const purchaseableAugmentation = (aug: string): React.ReactNode => {
return (
<PurchaseableAugmentation
augName={aug}
faction={this.props.faction}
key={aug}
p={this.props.p}
rerender={this.rerender}
/>
);
};
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
const augListElems = purchasable.map((aug) => purchaseableAugmentation(aug));
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
<h2>Purchased Augmentations</h2>
<p style={infoStyleMarkup}>This factions also offers these augmentations but you already own them.</p>
{owned.map((aug) => purchaseableAugmentation(aug))}
</>
);
}
return (
<div>
<StdButton onClick={this.props.routeToMainPage} text={"Back"} />
<h1>Faction Augmentations</h1>
<p style={infoStyleMarkup}>
These are all of the Augmentations that are available to purchase from {this.props.faction.name}.
Augmentations are powerful upgrades that will enhance your abilities.
</p>
<StdButton onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} />
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => this.switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
let ownedElem = <></>;
const owned = augs.filter((aug: string) => !purchasable.includes(aug));
if (owned.length !== 0) {
ownedElem = (
<>
<br />
{augListElems}
{ownedElem}
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</div>
<h2>Purchased Augmentations</h2>
<p>This factions also offers these augmentations but you already own them.</p>
{owned.map((aug) => purchaseableAugmentation(aug))}
</>
);
}
return (
<div>
<StdButton onClick={props.routeToMainPage} text={"Back"} />
<h1>Faction Augmentations</h1>
<p>
These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are
powerful upgrades that will enhance your abilities.
</p>
<StdButton onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} />
<StdButton
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)}
text={"Sort by Reputation"}
/>
<StdButton
onClick={() => switchSortOrder(PurchaseAugmentationsOrderSetting.Default)}
text={"Sort by Default Order"}
/>
<br />
{augListElems}
{ownedElem}
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</div>
);
}

View File

@ -1,58 +1,66 @@
/**
* React Component for the popup used to create a new gang.
*/
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
import { IEngine } from "../../IEngine";
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context";
interface ICreateGangPopupProps {
popupId: string;
facName: string;
p: IPlayer;
engine: IEngine;
}
interface ICreateGangPopupProps {
popupId: string;
facName: string;
}
export function CreateGangPopup(props: ICreateGangPopupProps): React.ReactElement {
const combatGangText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
export function CreateGangPopup(props: ICreateGangPopupProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const combatGangText =
"This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.";
const hackingGangText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
const hackingGangText =
"This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.";
function isHacking(): boolean {
return ["NiteSec", "The Black Hand"].includes(props.facName);
}
function isHacking(): boolean {
return ["NiteSec", "The Black Hand"].includes(props.facName);
}
function createGang(): void {
props.p.startGang(props.facName, isHacking());
removePopup(props.popupId);
props.engine.loadGangContent();
}
function createGang(): void {
player.startGang(props.facName, isHacking());
removePopup(props.popupId);
router.toGang();
}
function onKeyUp(event: React.KeyboardEvent): void {
if (event.keyCode === 13) createGang();
}
function onKeyUp(event: React.KeyboardEvent): void {
if (event.keyCode === 13) createGang();
}
return (
<>
Would you like to create a new Gang with {props.facName}?
<br/>
<br/>
Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It also resets your reputation with this faction.
<br/>
<br/>
{ (isHacking()) ? hackingGangText : combatGangText }
<br/>
<br/>
Other than hacking vs combat, there are NO differences between the Factions you can create a Gang with, and each of these Factions have all Augmentations available.
<div className="popup-box-input-div" >
<StdButton onClick={createGang} onKeyUp={onKeyUp} text="Create Gang" style={{float: "right"}} autoFocus={true}/>
</div>
</>
);
}
return (
<>
Would you like to create a new Gang with {props.facName}?
<br />
<br />
Note that this will prevent you from creating a Gang with any other Faction until this BitNode is destroyed. It
also resets your reputation with this faction.
<br />
<br />
{isHacking() ? hackingGangText : combatGangText}
<br />
<br />
Other than hacking vs combat, there are NO differences between the Factions you can create a Gang with, and each
of these Factions have all Augmentations available.
<div className="popup-box-input-div">
<StdButton
onClick={createGang}
onKeyUp={onKeyUp}
text="Create Gang"
style={{ float: "right" }}
autoFocus={true}
/>
</div>
</>
);
}

View File

@ -3,7 +3,7 @@
* This is the component for displaying a single faction's UI, not the list of all
* accessible factions
*/
import * as React from "react";
import React, { useState } from "react";
import { AugmentationsPage } from "./AugmentationsPage";
import { DonateOption } from "./DonateOption";
@ -11,30 +11,21 @@ import { Info } from "./Info";
import { Option } from "./Option";
import { CONSTANTS } from "../../Constants";
import { IEngine } from "../../IEngine";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Faction } from "../../Faction/Faction";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Sleeve/SleeveCovenantPurchases";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { CreateGangPopup } from "./CreateGangPopup";
type IProps = {
engine: IEngine;
faction: Faction | null;
p: IPlayer;
startHackingMissionFn: (faction: Faction) => void;
};
type IState = {
rerenderFlag: boolean;
purchasingAugs: boolean;
faction: Faction;
};
// Info text for all options on the UI
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and " + "faction reputation";
const hackingMissionInfo =
@ -72,91 +63,68 @@ const GangNames = [
"The Black Hand",
];
export class FactionRoot extends React.Component<IProps, IState> {
constructor(props: IProps) {
if (props.faction === null) throw new Error("Trying to render the Faction page with null faction");
super(props);
export function FactionRoot(props: IProps): React.ReactElement {
const faction = props.faction;
if (faction === null) throw new Error("Trying to render the Faction page with null faction");
this.state = {
rerenderFlag: false,
purchasingAugs: false,
faction: props.faction,
};
const player = use.Player();
const router = use.Router();
const [, setRerenderFlag] = useState(false);
const [purchasingAugs, setPurchasingAugs] = useState(false);
this.manageGang = this.manageGang.bind(this);
this.rerender = this.rerender.bind(this);
this.routeToMain = this.routeToMain.bind(this);
this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this);
this.sleevePurchases = this.sleevePurchases.bind(this);
this.startFieldWork = this.startFieldWork.bind(this);
this.startHackingContracts = this.startHackingContracts.bind(this);
this.startHackingMission = this.startHackingMission.bind(this);
this.startSecurityWork = this.startSecurityWork.bind(this);
}
manageGang(): void {
function manageGang(faction: Faction): void {
// If player already has a gang, just go to the gang UI
if (this.props.p.inGang()) {
return this.props.engine.loadGangContent();
if (player.inGang()) {
return router.toGang();
}
const popupId = "create-gang-popup";
createPopup(popupId, CreateGangPopup, {
popupId: popupId,
facName: this.state.faction.name,
p: this.props.p,
engine: this.props.engine,
facName: faction.name,
});
}
rerender(): void {
this.setState((prevState) => {
return {
rerenderFlag: !prevState.rerenderFlag,
};
});
function rerender(): void {
setRerenderFlag((old) => !old);
}
// Route to the main faction page
routeToMain(): void {
this.setState({ purchasingAugs: false });
function routeToMain(): void {
setPurchasingAugs(false);
}
// Route to the purchase augmentation UI for this faction
routeToPurchaseAugs(): void {
this.setState({ purchasingAugs: true });
function routeToPurchaseAugs(): void {
setPurchasingAugs(true);
}
sleevePurchases(): void {
createSleevePurchasesFromCovenantPopup(this.props.p);
function sleevePurchases(): void {
createSleevePurchasesFromCovenantPopup(player);
}
startFieldWork(): void {
this.props.p.startFactionFieldWork(this.state.faction);
function startFieldWork(faction: Faction): void {
player.startFactionFieldWork(faction);
router.toWork();
}
startHackingContracts(): void {
this.props.p.startFactionHackWork(this.state.faction);
function startHackingContracts(faction: Faction): void {
player.startFactionHackWork(faction);
router.toWork();
}
startHackingMission(): void {
const fac = this.state.faction;
this.props.p.singularityStopWork();
this.props.engine.loadMissionContent();
this.props.startHackingMissionFn(fac);
function startHackingMission(faction: Faction): void {
player.singularityStopWork();
props.startHackingMissionFn(faction);
}
startSecurityWork(): void {
this.props.p.startFactionSecurityWork(this.state.faction);
function startSecurityWork(faction: Faction): void {
player.startFactionSecurityWork(faction);
router.toWork();
}
render(): React.ReactNode {
return this.state.purchasingAugs ? this.renderAugmentationsPage() : this.renderMainPage();
}
renderMainPage(): React.ReactNode {
const p = this.props.p;
const faction = this.state.faction;
function MainPage({ faction }: { faction: Faction }): React.ReactElement {
const p = player;
const factionInfo = faction.getInfo();
// We have a special flag for whether the player this faction is the player's
@ -183,49 +151,51 @@ export class FactionRoot extends React.Component<IProps, IState> {
<div className="faction-container">
<h1>{faction.name}</h1>
<Info faction={faction} factionInfo={factionInfo} />
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={this.manageGang} />}
{canAccessGang && <Option buttonText={"Manage Gang"} infoText={gangInfo} onClick={() => manageGang(faction)} />}
{!isPlayersGang && factionInfo.offerHackingMission && (
<Option buttonText={"Hacking Mission"} infoText={hackingMissionInfo} onClick={this.startHackingMission} />
<Option
buttonText={"Hacking Mission"}
infoText={hackingMissionInfo}
onClick={() => startHackingMission(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerHackingWork && (
<Option
buttonText={"Hacking Contracts"}
infoText={hackingContractsInfo}
onClick={this.startHackingContracts}
onClick={() => startHackingContracts(faction)}
/>
)}
{!isPlayersGang && factionInfo.offerFieldWork && (
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={this.startFieldWork} />
<Option buttonText={"Field Work"} infoText={fieldWorkInfo} onClick={() => startFieldWork(faction)} />
)}
{!isPlayersGang && factionInfo.offerSecurityWork && (
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={this.startSecurityWork} />
<Option buttonText={"Security Work"} infoText={securityWorkInfo} onClick={() => startSecurityWork(faction)} />
)}
{!isPlayersGang && factionInfo.offersWork() && (
<DonateOption
faction={this.state.faction}
p={this.props.p}
rerender={this.rerender}
faction={faction}
p={player}
rerender={rerender}
favorToDonate={favorToDonate}
disabled={!canDonate}
/>
)}
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={this.routeToPurchaseAugs} />
<Option buttonText={"Purchase Augmentations"} infoText={augmentationsInfo} onClick={routeToPurchaseAugs} />
{canPurchaseSleeves && (
<Option
buttonText={"Purchase & Upgrade Duplicate Sleeves"}
infoText={sleevePurchasesInfo}
onClick={this.sleevePurchases}
onClick={sleevePurchases}
/>
)}
</div>
);
}
renderAugmentationsPage(): React.ReactNode {
return (
<>
<AugmentationsPage faction={this.state.faction} p={this.props.p} routeToMainPage={this.routeToMain} />
</>
);
}
return purchasingAugs ? (
<AugmentationsPage faction={faction} routeToMainPage={routeToMain} />
) : (
<MainPage faction={faction} />
);
}

View File

@ -12,6 +12,7 @@ interface IProps {
player: IPlayer;
faction: Faction;
aug: Augmentation;
rerender: () => void;
popupId: string;
}
@ -24,6 +25,7 @@ export function PurchaseAugmentationPopup(props: IProps): React.ReactElement {
}
purchaseAugmentation(props.aug, props.faction);
props.rerender();
removePopup(props.popupId);
}

View File

@ -7,7 +7,6 @@ import * as React from "react";
import { getNextNeurofluxLevel, hasAugmentationPrereqs, purchaseAugmentation } from "../FactionHelpers";
import { PurchaseAugmentationPopup } from "./PurchaseAugmentationPopup";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Faction } from "../../Faction/Faction";
@ -28,63 +27,56 @@ type IProps = {
rerender: () => void;
};
export class PurchaseableAugmentation extends React.Component<IProps, any> {
aug: Augmentation;
export function PurchaseableAugmentation(props: IProps): React.ReactElement {
const aug = Augmentations[props.augName];
if (aug == null) throw new Error(`aug ${props.augName} does not exists`);
constructor(props: IProps) {
super(props);
const aug = Augmentations[this.props.augName];
if (aug == null) throw new Error(`aug ${this.props.augName} does not exists`);
this.aug = aug;
this.handleClick = this.handleClick.bind(this);
function getMoneyCost(): number {
return aug.baseCost * props.faction.getInfo().augmentationPriceMult;
}
getMoneyCost(): number {
return this.aug.baseCost * this.props.faction.getInfo().augmentationPriceMult;
function getRepCost(): number {
return aug.baseRepRequirement * props.faction.getInfo().augmentationRepRequirementMult;
}
getRepCost(): number {
return this.aug.baseRepRequirement * this.props.faction.getInfo().augmentationRepRequirementMult;
}
handleClick(): void {
function handleClick(): void {
if (!Settings.SuppressBuyAugmentationConfirmation) {
const popupId = "purchase-augmentation-popup";
createPopup(popupId, PurchaseAugmentationPopup, {
aug: this.aug,
faction: this.props.faction,
player: this.props.p,
aug: aug,
faction: props.faction,
player: props.p,
rerender: props.rerender,
popupId: popupId,
});
} else {
purchaseAugmentation(this.aug, this.props.faction);
purchaseAugmentation(aug, props.faction);
props.rerender();
}
}
// Whether the player has the prerequisite Augmentations
hasPrereqs(): boolean {
return hasAugmentationPrereqs(this.aug);
function hasPrereqs(): boolean {
return hasAugmentationPrereqs(aug);
}
// Whether the player has enough rep for this Augmentation
hasReputation(): boolean {
return this.props.faction.playerReputation >= this.getRepCost();
function hasReputation(): boolean {
return props.faction.playerReputation >= getRepCost();
}
// Whether the player has this augmentations (purchased OR installed)
owned(): boolean {
function owned(): boolean {
let owned = false;
for (const queuedAug of this.props.p.queuedAugmentations) {
if (queuedAug.name === this.props.augName) {
for (const queuedAug of props.p.queuedAugmentations) {
if (queuedAug.name === props.augName) {
owned = true;
break;
}
}
for (const installedAug of this.props.p.augmentations) {
if (installedAug.name === this.props.augName) {
for (const installedAug of props.p.augmentations) {
if (installedAug.name === props.augName) {
owned = true;
break;
}
@ -93,96 +85,94 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
return owned;
}
render(): React.ReactNode {
if (this.aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`,
);
return null;
}
const moneyCost = this.getMoneyCost();
const repCost = this.getRepCost();
// Determine UI properties
let disabled = false;
let status: JSX.Element = <></>;
let color = "";
if (!this.hasPrereqs()) {
disabled = true;
status = <>LOCKED (Requires {this.aug.prereqs.map((aug) => AugFormat(aug))} as prerequisite)</>;
color = "red";
} else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) {
disabled = true;
} else if (this.hasReputation()) {
status = (
<>
UNLOCKED (at {Reputation(repCost)} faction reputation) - <Money money={moneyCost} player={this.props.p} />
</>
);
} else {
disabled = true;
status = (
<>
LOCKED (Requires {Reputation(repCost)} faction reputation - <Money money={moneyCost} player={this.props.p} />)
</>
);
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
};
if (color !== "") {
txtStyle.color = color;
}
// Determine button txt
let btnTxt = this.aug.name;
if (this.aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
}
let tooltip = <></>;
if (typeof this.aug.info === "string")
tooltip = (
<>
<span dangerouslySetInnerHTML={{ __html: this.aug.info }} />
<br />
<br />
{this.aug.stats}
</>
);
else
tooltip = (
<>
{this.aug.info}
<br />
<br />
{this.aug.stats}
</>
);
return (
<li key={this.aug.name}>
<span
style={{
margin: "4px",
padding: "4px",
}}
>
<StdButton
disabled={disabled}
onClick={this.handleClick}
style={{
display: "inline-block",
}}
text={btnTxt}
tooltip={tooltip}
/>
<p style={txtStyle}>{status}</p>
</span>
</li>
if (aug == null) {
console.error(
`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${props.augName}`,
);
return <></>;
}
const moneyCost = getMoneyCost();
const repCost = getRepCost();
// Determine UI properties
let disabled = false;
let status: JSX.Element = <></>;
let color = "";
if (!hasPrereqs()) {
disabled = true;
status = <>LOCKED (Requires {aug.prereqs.map((aug) => AugFormat(aug))} as prerequisite)</>;
color = "red";
} else if (aug.name !== AugmentationNames.NeuroFluxGovernor && (aug.owned || owned())) {
disabled = true;
} else if (hasReputation()) {
status = (
<>
UNLOCKED (at {Reputation(repCost)} faction reputation) - <Money money={moneyCost} player={props.p} />
</>
);
} else {
disabled = true;
status = (
<>
LOCKED (Requires {Reputation(repCost)} faction reputation - <Money money={moneyCost} player={props.p} />)
</>
);
color = "red";
}
const txtStyle: IMap<string> = {
display: "inline-block",
};
if (color !== "") {
txtStyle.color = color;
}
// Determine button txt
let btnTxt = aug.name;
if (aug.name === AugmentationNames.NeuroFluxGovernor) {
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
}
let tooltip = <></>;
if (typeof aug.info === "string")
tooltip = (
<>
<span dangerouslySetInnerHTML={{ __html: aug.info }} />
<br />
<br />
{aug.stats}
</>
);
else
tooltip = (
<>
{aug.info}
<br />
<br />
{aug.stats}
</>
);
return (
<li key={aug.name}>
<span
style={{
margin: "4px",
padding: "4px",
}}
>
<StdButton
disabled={disabled}
onClick={handleClick}
style={{
display: "inline-block",
}}
text={btnTxt}
tooltip={tooltip}
/>
<p style={txtStyle}>{status}</p>
</span>
</li>
);
}

View File

@ -2,20 +2,19 @@
* React Component for all the gang stuff.
*/
import React, { useState, useEffect } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ManagementSubpage } from "./ManagementSubpage";
import { TerritorySubpage } from "./TerritorySubpage";
import { IEngine } from "../../IEngine";
import { use } from "../../ui/Context";
import { Factions } from "../../Faction/Factions";
import { Gang } from "../Gang";
import { displayFactionContent } from "../../Faction/FactionHelpers";
interface IProps {
gang: Gang;
player: IPlayer;
engine: IEngine;
}
export function Root(props: IProps): React.ReactElement {
export function GangRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [management, setManagement] = useState(true);
const setRerender = useState(false)[1];
@ -25,8 +24,7 @@ export function Root(props: IProps): React.ReactElement {
}, []);
function back(): void {
props.engine.loadFactionContent();
displayFactionContent(props.gang.facName);
router.toFaction(Factions[props.gang.facName]);
}
return (
@ -48,11 +46,7 @@ export function Root(props: IProps): React.ReactElement {
>
Gang Territory
</a>
{management ? (
<ManagementSubpage gang={props.gang} player={props.player} />
) : (
<TerritorySubpage gang={props.gang} />
)}
{management ? <ManagementSubpage gang={props.gang} player={player} /> : <TerritorySubpage gang={props.gang} />}
</div>
);
}

View File

@ -1,51 +0,0 @@
import { Page, routing } from ".././ui/navigationTracking";
import { Root } from "./ui/Root";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine";
import * as React from "react";
import * as ReactDOM from "react-dom";
let container: HTMLElement;
(function () {
function setContainer(): void {
const c = document.getElementById("infiltration-container");
if (c === null) throw new Error("huh?");
container = c;
document.removeEventListener("DOMContentLoaded", setContainer);
}
document.addEventListener("DOMContentLoaded", setContainer);
})();
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
const difficulty = startingDifficulty - Math.pow(totalStats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
export function displayInfiltrationContent(
engine: IEngine,
player: IPlayer,
location: string,
startingDifficulty: number,
maxLevel: number,
): void {
if (!routing.isOn(Page.Infiltration)) return;
const difficulty = calcDifficulty(player, startingDifficulty);
ReactDOM.render(
<Root
Engine={engine}
Player={player}
Location={location}
StartingDifficulty={startingDifficulty}
Difficulty={difficulty}
MaxLevel={maxLevel}
/>,
container,
);
}

View File

@ -1,5 +1,4 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { use } from "../../ui/Context";
import React, { useState } from "react";
import Grid from "@mui/material/Grid";
import { Countdown } from "./Countdown";
@ -14,8 +13,6 @@ import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
@ -40,6 +37,8 @@ const minigames = [
];
export function Game(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [level, setLevel] = useState(1);
const [stage, setStage] = useState(Stage.Countdown);
const [results, setResults] = useState("");
@ -89,12 +88,10 @@ export function Game(props: IProps): React.ReactElement {
pushResult(false);
// Kill the player immediately if they use automation, so
// it's clear they're not meant to
const damage = options?.automated ? props.Player.hp : props.StartingDifficulty * 3;
if (props.Player.takeDamage(damage)) {
const menu = document.getElementById("mainmenu-container");
if (menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
const damage = options?.automated ? player.hp : props.StartingDifficulty * 3;
if (player.takeDamage(damage)) {
router.toCity();
return;
}
setupNextGame();
}
@ -112,8 +109,6 @@ export function Game(props: IProps): React.ReactElement {
case Stage.Sell:
stageComponent = (
<Victory
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}

View File

@ -0,0 +1,53 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
import { LocationName } from "../../Locations/data/LocationNames";
import { Locations } from "../../Locations/Locations";
import { use } from "../../ui/Context";
interface IProps {
location: LocationName;
}
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;
const difficulty = startingDifficulty - Math.pow(totalStats, 0.9) / 250 - player.intelligence / 1600;
if (difficulty < 0) return 0;
if (difficulty > 3) return 3;
return difficulty;
}
export function InfiltrationRoot(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [start, setStart] = useState(false);
const loc = Locations[props.location];
if (loc.infiltrationData === undefined) throw new Error("Trying to do infiltration on invalid location.");
const startingDifficulty = loc.infiltrationData.startingSecurityLevel;
const difficulty = calcDifficulty(player, startingDifficulty);
function cancel(): void {
router.toCity();
}
if (!start) {
return (
<Intro
Location={props.location}
Difficulty={difficulty}
MaxLevel={loc.infiltrationData.maxClearanceLevel}
start={() => setStart(true)}
cancel={cancel}
/>
);
}
return (
<Game
StartingDifficulty={startingDifficulty}
Difficulty={difficulty}
MaxLevel={loc.infiltrationData.maxClearanceLevel}
/>
);
}

View File

@ -1,12 +1,8 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React from "react";
import { StdButton } from "../../ui/React/StdButton";
import Grid from "@mui/material/Grid";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
Difficulty: number;
MaxLevel: number;

View File

@ -1,49 +0,0 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from "react";
import { Intro } from "./Intro";
import { Game } from "./Game";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Root(props: IProps): React.ReactElement {
const [start, setStart] = useState(false);
function cancel(): void {
const menu = document.getElementById("mainmenu-container");
if (menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
if (!start) {
return (
<Intro
Player={props.Player}
Engine={props.Engine}
Location={props.Location}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
start={() => setStart(true)}
cancel={cancel}
/>
);
}
return (
<Game
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
/>
);
}

View File

@ -1,5 +1,3 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Factions } from "../../Faction/Factions";
import React, { useState } from "react";
import { StdButton } from "../../ui/React/StdButton";
@ -7,23 +5,21 @@ import Grid from "@mui/material/Grid";
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { use } from "../../ui/Context";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Victory(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
const [faction, setFaction] = useState("none");
function quitInfiltration(): void {
const menu = document.getElementById("mainmenu-container");
if (!menu) throw new Error("mainmenu-container somehow null");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
router.toCity();
}
const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel);
@ -43,8 +39,8 @@ export function Victory(props: IProps): React.ReactElement {
BitNodeMultipliers.InfiltrationMoney;
function sell(): void {
props.Player.gainMoney(moneyGain);
props.Player.recordMoneySource(moneyGain, "infiltration");
player.gainMoney(moneyGain);
player.recordMoneySource(moneyGain, "infiltration");
quitInfiltration();
}
@ -70,7 +66,7 @@ export function Victory(props: IProps): React.ReactElement {
<option key={"none"} value={"none"}>
{"none"}
</option>
{props.Player.factions
{player.factions
.filter((f) => Factions[f].getInfo().offersWork())
.map((f) => (
<option key={f} value={f}>

View File

@ -3,418 +3,347 @@
*
* This subcomponent renders all of the buttons for applying to jobs at a company
*/
import * as React from "react";
import React, { useState } from "react";
import { ApplyToJobButton } from "./ApplyToJobButton";
import { Location } from "../Location";
import { Locations } from "../Locations";
import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { CompanyPositions } from "../../Company/CompanyPositions";
import * as posNames from "../../Company/data/companypositionnames";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { createPopup } from "../../ui/React/createPopup";
import { use } from "../../ui/Context";
import { QuitJobPopup } from "../../Company/ui/QuitJobPopup";
type IProps = {
engine: IEngine;
locName: LocationName;
p: IPlayer;
};
type IState = {
employedHere: boolean;
};
const blockStyleMarkup = {
display: "block",
};
export class CompanyLocation extends React.Component<IProps, IState> {
export function CompanyLocation(props: IProps): React.ReactElement {
const p = use.Player();
const router = use.Router();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
/**
* We'll keep a reference to the Company that this component is being rendered for,
* so we don't have to look it up every time
*/
company: Company;
const company = Companies[props.locName];
if (company == null) throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);
/**
* Reference to the Location that this component is being rendered for
*/
const location = Locations[props.locName];
if (location == null) {
throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`);
}
/**
* Name of company position that player holds, if applicable
*/
const jobTitle = p.jobs[props.locName] ? p.jobs[props.locName] : null;
/**
* CompanyPosition object for the job that the player holds at this company
* (if he has one)
*/
companyPosition: CompanyPosition | null = null;
const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null;
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
p.location = props.locName;
/**
* Reference to the Location that this component is being rendered for
*/
location: Location;
/**
* Name of company position that player holds, if applicable
*/
jobTitle: string | null = null;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.quit = this.quit.bind(this);
this.applyForAgentJob = this.applyForAgentJob.bind(this);
this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this);
this.applyForBusinessJob = this.applyForBusinessJob.bind(this);
this.applyForEmployeeJob = this.applyForEmployeeJob.bind(this);
this.applyForItJob = this.applyForItJob.bind(this);
this.applyForPartTimeEmployeeJob = this.applyForPartTimeEmployeeJob.bind(this);
this.applyForPartTimeWaiterJob = this.applyForPartTimeWaiterJob.bind(this);
this.applyForSecurityJob = this.applyForSecurityJob.bind(this);
this.applyForSoftwareConsultantJob = this.applyForSoftwareConsultantJob.bind(this);
this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this);
this.applyForWaiterJob = this.applyForWaiterJob.bind(this);
this.startInfiltration = this.startInfiltration.bind(this);
this.work = this.work.bind(this);
this.location = Locations[props.locName];
if (this.location == null) {
throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`);
}
this.company = Companies[props.locName];
if (this.company == null) {
throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);
}
this.state = {
employedHere: false,
};
this.props.p.location = props.locName;
this.checkIfEmployedHere(false);
}
applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForAgentJob();
this.checkIfEmployedHere(true);
p.applyForAgentJob();
rerender();
}
applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void {
function applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForBusinessConsultantJob();
this.checkIfEmployedHere(true);
p.applyForBusinessConsultantJob();
rerender();
}
applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void {
function applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForBusinessJob();
this.checkIfEmployedHere(true);
p.applyForBusinessJob();
rerender();
}
applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
function applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForEmployeeJob();
this.checkIfEmployedHere(true);
p.applyForEmployeeJob();
rerender();
}
applyForItJob(e: React.MouseEvent<HTMLElement>): void {
function applyForItJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForItJob();
this.checkIfEmployedHere(true);
p.applyForItJob();
rerender();
}
applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
function applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForPartTimeEmployeeJob();
this.checkIfEmployedHere(true);
p.applyForPartTimeEmployeeJob();
rerender();
}
applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void {
function applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForPartTimeWaiterJob();
this.checkIfEmployedHere(true);
p.applyForPartTimeWaiterJob();
rerender();
}
applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void {
function applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForSecurityJob();
this.checkIfEmployedHere(true);
p.applyForSecurityJob();
rerender();
}
applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void {
function applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForSoftwareConsultantJob();
this.checkIfEmployedHere(true);
p.applyForSoftwareConsultantJob();
rerender();
}
applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void {
function applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForSoftwareJob();
this.checkIfEmployedHere(true);
p.applyForSoftwareJob();
rerender();
}
applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void {
function applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
this.props.p.applyForWaiterJob();
this.checkIfEmployedHere(true);
p.applyForWaiterJob();
rerender();
}
checkIfEmployedHere(updateState = false): void {
this.jobTitle = this.props.p.jobs[this.props.locName];
if (this.jobTitle != null) {
this.companyPosition = CompanyPositions[this.jobTitle];
}
if (updateState) {
this.setState({
employedHere: this.jobTitle != null,
});
}
}
startInfiltration(e: React.MouseEvent<HTMLElement>): void {
function startInfiltration(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
const loc = this.location;
if (!loc.infiltrationData) {
console.error(`trying to start infiltration at ${this.props.locName} but the infiltrationData is null`);
return;
}
const loc = location;
if (!loc.infiltrationData)
throw new Error(`trying to start infiltration at ${props.locName} but the infiltrationData is null`);
this.props.engine.loadInfiltrationContent(
this.props.locName,
loc.infiltrationData.startingSecurityLevel,
loc.infiltrationData.maxClearanceLevel,
);
const data = loc.infiltrationData;
if (data == null) {
return;
}
router.toInfiltration(props.locName);
}
work(e: React.MouseEvent<HTMLElement>): void {
function work(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
const pos = this.companyPosition;
const pos = companyPosition;
if (pos instanceof CompanyPosition) {
if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) {
this.props.p.startWorkPartTime(this.props.locName);
p.startWorkPartTime(props.locName);
} else {
this.props.p.startWork(this.props.locName);
p.startWork(props.locName);
}
router.toWork();
}
}
quit(e: React.MouseEvent<HTMLElement>): void {
function quit(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) return;
const popupId = `quit-job-popup`;
createPopup(popupId, QuitJobPopup, {
locName: this.props.locName,
company: this.company,
player: this.props.p,
onQuit: () => this.checkIfEmployedHere(true),
locName: props.locName,
company: company,
player: p,
onQuit: rerender,
popupId: popupId,
});
}
render(): React.ReactNode {
const isEmployedHere = this.jobTitle != null;
const favorGain = this.company.getFavorGain();
const isEmployedHere = jobTitle != null;
const favorGain = company.getFavorGain();
return (
<div>
{isEmployedHere && (
<div>
<p>Job Title: {this.jobTitle}</p>
<br />
<p style={blockStyleMarkup}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company reputation: {Reputation(this.company.playerReputation)}
<span className={"tooltiptext"}>
You will earn {Favor(favorGain[0])} company favor upon resetting after installing Augmentations
</span>
</p>
<br />
<br />
<p style={blockStyleMarkup}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company Favor: {Favor(this.company.favor)}
<span className={"tooltiptext"}>
Company favor increases the rate at which you earn reputation for this company by 1% per favor. Company
favor is gained whenever you reset after installing Augmentations. The amount of favor you gain depends
on how much reputation you have with the comapny.
</span>
</p>
<br />
<br />
<p style={blockStyleMarkup}>-------------------------</p>
<br />
<StdButton onClick={this.work} text={"Work"} />
&nbsp;&nbsp;&nbsp;&nbsp;
<StdButton onClick={this.quit} text={"Quit"} />
</div>
)}
{this.company.hasAgentPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
onClick={this.applyForAgentJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Agent Job"}
/>
)}
{this.company.hasBusinessConsultantPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
onClick={this.applyForBusinessConsultantJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Business Consultant Job"}
/>
)}
{this.company.hasBusinessPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
onClick={this.applyForBusinessJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Business Job"}
/>
)}
{this.company.hasEmployeePositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
onClick={this.applyForEmployeeJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be an Employee"}
/>
)}
{this.company.hasEmployeePositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
onClick={this.applyForPartTimeEmployeeJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a part-time Employee"}
/>
)}
{this.company.hasITPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
onClick={this.applyForItJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for IT Job"}
/>
)}
{this.company.hasSecurityPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
onClick={this.applyForSecurityJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Security Job"}
/>
)}
{this.company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
onClick={this.applyForSoftwareConsultantJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Software Consultant Job"}
/>
)}
{this.company.hasSoftwarePositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
onClick={this.applyForSoftwareJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply for Software Job"}
/>
)}
{this.company.hasWaiterPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
onClick={this.applyForWaiterJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a Waiter"}
/>
)}
{this.company.hasWaiterPositions() && (
<ApplyToJobButton
company={this.company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
onClick={this.applyForPartTimeWaiterJob}
p={this.props.p}
style={this.btnStyle}
text={"Apply to be a part-time Waiter"}
/>
)}
{this.location.infiltrationData != null && (
<StdButton onClick={this.startInfiltration} style={this.btnStyle} text={"Infiltrate Company"} />
)}
<br />
<br />
<br />
<br />
<br />
</div>
);
}
return (
<div>
{isEmployedHere && (
<div>
<p>Job Title: {jobTitle}</p>
<br />
<p style={{ display: "block" }}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company reputation: {Reputation(company.playerReputation)}
<span className={"tooltiptext"}>
You will earn {Favor(favorGain[0])} company favor upon resetting after installing Augmentations
</span>
</p>
<br />
<br />
<p style={{ display: "block" }}>-------------------------</p>
<br />
<p className={"tooltip"}>
Company Favor: {Favor(company.favor)}
<span className={"tooltiptext"}>
Company favor increases the rate at which you earn reputation for this company by 1% per favor. Company
favor is gained whenever you reset after installing Augmentations. The amount of favor you gain depends on
how much reputation you have with the comapny.
</span>
</p>
<br />
<br />
<p style={{ display: "block" }}>-------------------------</p>
<br />
<StdButton onClick={work} text={"Work"} />
&nbsp;&nbsp;&nbsp;&nbsp;
<StdButton onClick={quit} text={"Quit"} />
</div>
)}
{company.hasAgentPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
onClick={applyForAgentJob}
p={p}
style={{ display: "block" }}
text={"Apply for Agent Job"}
/>
)}
{company.hasBusinessConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
onClick={applyForBusinessConsultantJob}
p={p}
style={{ display: "block" }}
text={"Apply for Business Consultant Job"}
/>
)}
{company.hasBusinessPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
onClick={applyForBusinessJob}
p={p}
style={{ display: "block" }}
text={"Apply for Business Job"}
/>
)}
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
onClick={applyForEmployeeJob}
p={p}
style={{ display: "block" }}
text={"Apply to be an Employee"}
/>
)}
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
onClick={applyForPartTimeEmployeeJob}
p={p}
style={{ display: "block" }}
text={"Apply to be a part-time Employee"}
/>
)}
{company.hasITPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
onClick={applyForItJob}
p={p}
style={{ display: "block" }}
text={"Apply for IT Job"}
/>
)}
{company.hasSecurityPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
onClick={applyForSecurityJob}
p={p}
style={{ display: "block" }}
text={"Apply for Security Job"}
/>
)}
{company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
onClick={applyForSoftwareConsultantJob}
p={p}
style={{ display: "block" }}
text={"Apply for Software Consultant Job"}
/>
)}
{company.hasSoftwarePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
onClick={applyForSoftwareJob}
p={p}
style={{ display: "block" }}
text={"Apply for Software Job"}
/>
)}
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
onClick={applyForWaiterJob}
p={p}
style={{ display: "block" }}
text={"Apply to be a Waiter"}
/>
)}
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
onClick={applyForPartTimeWaiterJob}
p={p}
style={{ display: "block" }}
text={"Apply to be a part-time Waiter"}
/>
)}
{location.infiltrationData != null && (
<StdButton onClick={startInfiltration} style={{ display: "block" }} text={"Infiltrate Company"} />
)}
<br />
<br />
<br />
<br />
<br />
</div>
);
}

View File

@ -60,14 +60,7 @@ export class GenericLocation extends React.Component<IProps, any> {
const content: React.ReactNode[] = [];
if (this.props.loc.types.includes(LocationType.Company)) {
content.push(
<CompanyLocation
engine={this.props.engine}
key={"companylocation"}
locName={this.props.loc.name}
p={this.props.p}
/>,
);
content.push(<CompanyLocation key={"companylocation"} locName={this.props.loc.name} />);
}
if (this.props.loc.types.includes(LocationType.Gym)) {
@ -79,7 +72,7 @@ export class GenericLocation extends React.Component<IProps, any> {
}
if (this.props.loc.types.includes(LocationType.Slums)) {
content.push(<SlumsLocation key={"slumslocation"} p={this.props.p} />);
content.push(<SlumsLocation key={"slumslocation"} />);
}
if (this.props.loc.types.includes(LocationType.Special)) {
@ -97,7 +90,7 @@ export class GenericLocation extends React.Component<IProps, any> {
}
if (this.props.loc.types.includes(LocationType.University)) {
content.push(<UniversityLocation key={"universitylocation"} loc={this.props.loc} p={this.props.p} />);
content.push(<UniversityLocation key={"universitylocation"} loc={this.props.loc} />);
}
if (this.props.loc.types.includes(LocationType.Casino)) {

View File

@ -6,225 +6,209 @@
import * as React from "react";
import { Crimes } from "../../Crime/Crimes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
import { use } from "../../ui/Context";
type IProps = {
p: IPlayer;
};
export class SlumsLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.shoplift = this.shoplift.bind(this);
this.robStore = this.robStore.bind(this);
this.mug = this.mug.bind(this);
this.larceny = this.larceny.bind(this);
this.dealDrugs = this.dealDrugs.bind(this);
this.bondForgery = this.bondForgery.bind(this);
this.traffickArms = this.traffickArms.bind(this);
this.homicide = this.homicide.bind(this);
this.grandTheftAuto = this.grandTheftAuto.bind(this);
this.kidnap = this.kidnap.bind(this);
this.assassinate = this.assassinate.bind(this);
this.heist = this.heist.bind(this);
}
shoplift(e: React.MouseEvent<HTMLElement>): void {
export function SlumsLocation(): React.ReactElement {
const player = use.Player();
const router = use.Router();
function shoplift(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Shoplift.commit(this.props.p);
Crimes.Shoplift.commit(player);
router.toWork();
}
robStore(e: React.MouseEvent<HTMLElement>): void {
function robStore(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.RobStore.commit(this.props.p);
Crimes.RobStore.commit(player);
router.toWork();
}
mug(e: React.MouseEvent<HTMLElement>): void {
function mug(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Mug.commit(this.props.p);
Crimes.Mug.commit(player);
router.toWork();
}
larceny(e: React.MouseEvent<HTMLElement>): void {
function larceny(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Larceny.commit(this.props.p);
Crimes.Larceny.commit(player);
router.toWork();
}
dealDrugs(e: React.MouseEvent<HTMLElement>): void {
function dealDrugs(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.DealDrugs.commit(this.props.p);
Crimes.DealDrugs.commit(player);
router.toWork();
}
bondForgery(e: React.MouseEvent<HTMLElement>): void {
function bondForgery(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.BondForgery.commit(this.props.p);
Crimes.BondForgery.commit(player);
router.toWork();
}
traffickArms(e: React.MouseEvent<HTMLElement>): void {
function traffickArms(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.TraffickArms.commit(this.props.p);
Crimes.TraffickArms.commit(player);
router.toWork();
}
homicide(e: React.MouseEvent<HTMLElement>): void {
function homicide(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Homicide.commit(this.props.p);
Crimes.Homicide.commit(player);
router.toWork();
}
grandTheftAuto(e: React.MouseEvent<HTMLElement>): void {
function grandTheftAuto(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.GrandTheftAuto.commit(this.props.p);
Crimes.GrandTheftAuto.commit(player);
router.toWork();
}
kidnap(e: React.MouseEvent<HTMLElement>): void {
function kidnap(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Kidnap.commit(this.props.p);
Crimes.Kidnap.commit(player);
router.toWork();
}
assassinate(e: React.MouseEvent<HTMLElement>): void {
function assassinate(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Assassination.commit(this.props.p);
Crimes.Assassination.commit(player);
router.toWork();
}
heist(e: React.MouseEvent<HTMLElement>): void {
function heist(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Crimes.Heist.commit(this.props.p);
Crimes.Heist.commit(player);
router.toWork();
}
render(): React.ReactNode {
const shopliftChance = Crimes.Shoplift.successRate(this.props.p);
const robStoreChance = Crimes.RobStore.successRate(this.props.p);
const mugChance = Crimes.Mug.successRate(this.props.p);
const larcenyChance = Crimes.Larceny.successRate(this.props.p);
const drugsChance = Crimes.DealDrugs.successRate(this.props.p);
const bondChance = Crimes.BondForgery.successRate(this.props.p);
const armsChance = Crimes.TraffickArms.successRate(this.props.p);
const homicideChance = Crimes.Homicide.successRate(this.props.p);
const gtaChance = Crimes.GrandTheftAuto.successRate(this.props.p);
const kidnapChance = Crimes.Kidnap.successRate(this.props.p);
const assassinateChance = Crimes.Assassination.successRate(this.props.p);
const heistChance = Crimes.Heist.successRate(this.props.p);
const shopliftChance = Crimes.Shoplift.successRate(player);
const robStoreChance = Crimes.RobStore.successRate(player);
const mugChance = Crimes.Mug.successRate(player);
const larcenyChance = Crimes.Larceny.successRate(player);
const drugsChance = Crimes.DealDrugs.successRate(player);
const bondChance = Crimes.BondForgery.successRate(player);
const armsChance = Crimes.TraffickArms.successRate(player);
const homicideChance = Crimes.Homicide.successRate(player);
const gtaChance = Crimes.GrandTheftAuto.successRate(player);
const kidnapChance = Crimes.Kidnap.successRate(player);
const assassinateChance = Crimes.Assassination.successRate(player);
const heistChance = Crimes.Heist.successRate(player);
return (
<div>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.shoplift}
style={this.btnStyle}
text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
tooltip={"Attempt to shoplift from a low-end retailer"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.robStore}
style={this.btnStyle}
text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
tooltip={"Attempt to commit armed robbery on a high-end store"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.mug}
style={this.btnStyle}
text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
tooltip={"Attempt to mug a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.larceny}
style={this.btnStyle}
text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
tooltip={"Attempt to rob property from someone's house"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.dealDrugs}
style={this.btnStyle}
text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
tooltip={"Attempt to deal drugs"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.bondForgery}
style={this.btnStyle}
text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
tooltip={"Attempt to forge corporate bonds"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.traffickArms}
style={this.btnStyle}
text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
tooltip={"Attempt to smuggle illegal arms into the city"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.homicide}
style={this.btnStyle}
text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
tooltip={"Attempt to murder a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.grandTheftAuto}
style={this.btnStyle}
text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
tooltip={"Attempt to commit grand theft auto"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.kidnap}
style={this.btnStyle}
text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
tooltip={"Attempt to kidnap and ransom a high-profile-target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.assassinate}
style={this.btnStyle}
text={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
tooltip={"Attempt to assassinate a high-profile target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={this.heist}
style={this.btnStyle}
text={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`}
tooltip={"Attempt to pull off the ultimate heist"}
/>
</div>
);
}
return (
<div>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={shoplift}
style={{ display: "block" }}
text={`Shoplift (${numeralWrapper.formatPercentage(shopliftChance)} chance of success)`}
tooltip={"Attempt to shoplift from a low-end retailer"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={robStore}
style={{ display: "block" }}
text={`Rob store (${numeralWrapper.formatPercentage(robStoreChance)} chance of success)`}
tooltip={"Attempt to commit armed robbery on a high-end store"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={mug}
style={{ display: "block" }}
text={`Mug someone (${numeralWrapper.formatPercentage(mugChance)} chance of success)`}
tooltip={"Attempt to mug a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={larceny}
style={{ display: "block" }}
text={`Larceny (${numeralWrapper.formatPercentage(larcenyChance)} chance of success)`}
tooltip={"Attempt to rob property from someone's house"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={dealDrugs}
style={{ display: "block" }}
text={`Deal Drugs (${numeralWrapper.formatPercentage(drugsChance)} chance of success)`}
tooltip={"Attempt to deal drugs"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={bondForgery}
style={{ display: "block" }}
text={`Bond Forgery (${numeralWrapper.formatPercentage(bondChance)} chance of success)`}
tooltip={"Attempt to forge corporate bonds"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={traffickArms}
style={{ display: "block" }}
text={`Traffick illegal Arms (${numeralWrapper.formatPercentage(armsChance)} chance of success)`}
tooltip={"Attempt to smuggle illegal arms into the city"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={homicide}
style={{ display: "block" }}
text={`Homicide (${numeralWrapper.formatPercentage(homicideChance)} chance of success)`}
tooltip={"Attempt to murder a random person on the street"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={grandTheftAuto}
style={{ display: "block" }}
text={`Grand theft Auto (${numeralWrapper.formatPercentage(gtaChance)} chance of success)`}
tooltip={"Attempt to commit grand theft auto"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={kidnap}
style={{ display: "block" }}
text={`Kidnap and Ransom (${numeralWrapper.formatPercentage(kidnapChance)} chance of success)`}
tooltip={"Attempt to kidnap and ransom a high-profile-target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={assassinate}
style={{ display: "block" }}
text={`Assassinate (${numeralWrapper.formatPercentage(assassinateChance)} chance of success)`}
tooltip={"Attempt to assassinate a high-profile target"}
/>
<AutoupdatingStdButton
intervalTime={5e3}
onClick={heist}
style={{ display: "block" }}
text={`Heist (${numeralWrapper.formatPercentage(heistChance)} chance of success)`}
tooltip={"Attempt to pull off the ultimate heist"}
/>
</div>
);
}

View File

@ -8,154 +8,134 @@ import * as React from "react";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { StdButton } from "../../ui/React/StdButton";
import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context";
type IProps = {
loc: Location;
p: IPlayer;
};
export class UniversityLocation extends React.Component<IProps, any> {
/**
* Stores button styling that sets them all to block display
*/
btnStyle: any;
export function UniversityLocation(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
constructor(props: IProps) {
super(props);
this.btnStyle = { display: "block" };
this.take = this.take.bind(this);
this.study = this.study.bind(this);
this.dataStructures = this.dataStructures.bind(this);
this.networks = this.networks.bind(this);
this.algorithms = this.algorithms.bind(this);
this.management = this.management.bind(this);
this.leadership = this.leadership.bind(this);
this.calculateCost = this.calculateCost.bind(this);
}
calculateCost(): number {
const ip = SpecialServerIps.getIp(this.props.loc.name);
function calculateCost(): number {
const ip = SpecialServerIps.getIp(props.loc.name);
const server = getServer(ip);
if (server == null || !server.hasOwnProperty("backdoorInstalled")) return this.props.loc.costMult;
if (server == null || !server.hasOwnProperty("backdoorInstalled")) return props.loc.costMult;
const discount = (server as Server).backdoorInstalled ? 0.9 : 1;
return this.props.loc.costMult * discount;
return props.loc.costMult * discount;
}
take(stat: string): void {
const loc = this.props.loc;
this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
function take(stat: string): void {
const loc = props.loc;
player.startClass(calculateCost(), loc.expMult, stat);
router.toWork();
}
study(): void {
this.take(CONSTANTS.ClassStudyComputerScience);
function study(): void {
take(CONSTANTS.ClassStudyComputerScience);
}
dataStructures(): void {
this.take(CONSTANTS.ClassDataStructures);
function dataStructures(): void {
take(CONSTANTS.ClassDataStructures);
}
networks(): void {
this.take(CONSTANTS.ClassNetworks);
function networks(): void {
take(CONSTANTS.ClassNetworks);
}
algorithms(): void {
this.take(CONSTANTS.ClassAlgorithms);
function algorithms(): void {
take(CONSTANTS.ClassAlgorithms);
}
management(): void {
this.take(CONSTANTS.ClassManagement);
function management(): void {
take(CONSTANTS.ClassManagement);
}
leadership(): void {
this.take(CONSTANTS.ClassLeadership);
function leadership(): void {
take(CONSTANTS.ClassLeadership);
}
render(): React.ReactNode {
const costMult: number = this.calculateCost();
const costMult: number = calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const earnHackingExpTooltip = `Gain hacking experience!`;
const earnCharismaExpTooltip = `Gain charisma experience!`;
const earnHackingExpTooltip = `Gain hacking experience!`;
const earnCharismaExpTooltip = `Gain charisma experience!`;
return (
<div>
<StdButton
onClick={this.study}
style={this.btnStyle}
text={`Study Computer Science (free)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.dataStructures}
style={this.btnStyle}
text={
<>
Take Data Structures course (
<Money money={dataStructuresCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.networks}
style={this.btnStyle}
text={
<>
Take Networks course (
<Money money={networksCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.algorithms}
style={this.btnStyle}
text={
<>
Take Algorithms course (
<Money money={algorithmsCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.management}
style={this.btnStyle}
text={
<>
Take Management course (
<Money money={managementCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
<StdButton
onClick={this.leadership}
style={this.btnStyle}
text={
<>
Take Leadership course (
<Money money={leadershipCost} player={this.props.p} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
</div>
);
}
return (
<div>
<StdButton
onClick={study}
style={{ display: "block" }}
text={`Study Computer Science (free)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={dataStructures}
style={{ display: "block" }}
text={
<>
Take Data Structures course (
<Money money={dataStructuresCost} player={player} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={networks}
style={{ display: "block" }}
text={
<>
Take Networks course (
<Money money={networksCost} player={player} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={algorithms}
style={{ display: "block" }}
text={
<>
Take Algorithms course (
<Money money={algorithmsCost} player={player} /> / sec)
</>
}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={management}
style={{ display: "block" }}
text={
<>
Take Management course (
<Money money={managementCost} player={player} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
<StdButton
onClick={leadership}
style={{ display: "block" }}
text={
<>
Take Leadership course (
<Money money={leadershipCost} player={player} /> / sec)
</>
}
tooltip={earnCharismaExpTooltip}
/>
</div>
);
}

View File

@ -1,6 +1,4 @@
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { displayFactionContent } from "./Faction/FactionHelpers";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
@ -1187,7 +1185,7 @@ HackingMission.prototype.process = function (numCycles = 1) {
});
// Update timer and check if player lost
this.time -= storedCycles * Engine._idleSpeed;
this.time -= storedCycles * CONSTANTS._idleSpeed;
if (this.time <= 0) {
this.finishMission(false);
return;
@ -1595,18 +1593,6 @@ HackingMission.prototype.finishMission = function (win) {
} else {
dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation.");
}
// Clear mission container
var container = document.getElementById("mission-container");
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Return to Faction page
document.getElementById("mainmenu-container").style.visibility = "visible";
document.getElementById("character-overview").style.visibility = "visible";
Engine.loadFactionContent();
displayFactionContent(this.faction.name);
};
export { HackingMission, inMission, setInMission, currMission };

View File

@ -121,8 +121,33 @@ export interface IPlayer {
bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number;
workRepGained: number;
createProgramName: string;
timeWorkedCreateProgram: number;
crimeType: string;
timeNeededToCompleteWork: number;
focus: boolean;
className: string;
currentWorkFactionName: string;
workType: string;
currentWorkFactionDescription: string;
timeWorked: number;
workMoneyGained: number;
workMoneyGainRate: number;
workRepGained: number;
workRepGainRate: number;
workHackExpGained: number;
workHackExpGainRate: number;
workStrExpGained: number;
workStrExpGainRate: number;
workDefExpGained: number;
workDefExpGainRate: number;
workDexExpGained: number;
workDexExpGainRate: number;
workAgiExpGained: number;
workAgiExpGainRate: number;
workChaExpGained: number;
workChaExpGainRate: number;
workMoneyLossRate: number;
// Methods
applyForAgentJob(sing?: boolean): boolean | void;
@ -209,4 +234,12 @@ export interface IPlayer {
receiveInvite(factionName: string): void;
updateSkillLevels(): void;
gainCodingContractReward(reward: ICodingContractReward, difficulty?: number): string;
stopFocusing(): void;
finishFactionWork(cancelled: boolean, sing?: boolean): void;
finishClass(sing?: boolean): void;
finishWork(cancelled: boolean, sing?: boolean): void;
cancelationPenalty(): number;
finishWorkPartTime(sing?: boolean): void;
finishCrime(cancelled: boolean): void;
finishCreateProgramWork(cancelled: boolean): void;
}

View File

@ -14,10 +14,8 @@ import { CONSTANTS } from "../../Constants";
import { Programs } from "../../Programs/Programs";
import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
import { Crimes } from "../../Crime/Crimes";
import { Engine } from "../../engine";
import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions";
import { displayFactionContent } from "../../Faction/FactionHelpers";
import { resetGangs } from "../../Gang/AllGangs";
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
import { Cities } from "../../Locations/Cities";
@ -48,18 +46,12 @@ import Decimal from "decimal.js";
import { numeralWrapper } from "../../ui/numeralFormat";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { Reputation } from "../../ui/React/Reputation";
import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { ReputationRate } from "../../ui/React/ReputationRate";
import React from "react";
import ReactDOM from "react-dom";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
export function init() {
/* Initialize Player's home computer */
@ -518,8 +510,6 @@ export function resetWorkStatus(generalType, group, workType) {
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.className = "";
ReactDOM.unmountComponentAtNode(document.getElementById("work-in-progress-text"));
}
export function processWorkEarnings(numCycles = 1) {
@ -573,25 +563,6 @@ export function startWork(companyName) {
this.workMoneyGainRate = this.getWorkMoneyGain();
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours;
//Remove all old event listeners from Cancel button
var newCancelButton = clearEventListeners("work-in-progress-cancel-button");
newCancelButton.innerHTML = "Cancel Work";
newCancelButton.addEventListener("click", () => {
this.finishWork(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "visible";
focusButton.innerHTML = "Do something else simultaneously";
focusButton.addEventListener("click", () => {
this.stopFocusing();
return false;
});
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
}
export function cancelationPenalty() {
@ -607,11 +578,11 @@ export function work(numCycles) {
// Cap the number of cycles being processed to whatever would put you at
// the work time limit (8 hours)
var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
overMax = true;
numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed);
numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / CONSTANTS._idleSpeed);
}
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.workRepGainRate = this.getWorkRepGain();
this.processWorkEarnings(numCycles);
@ -622,63 +593,7 @@ export function work(numCycles) {
}
const comp = Companies[this.companyName];
let companyRep = "0";
if (comp == null || !(comp instanceof Company)) {
console.error(`Could not find Company: ${this.companyName}`);
} else {
companyRep = comp.playerReputation;
}
influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles);
const position = this.jobs[this.companyName];
const penalty = this.cancelationPenalty();
const penaltyString = penalty === 0.5 ? "half" : "three-quarters";
var elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently working as a {position} at {this.companyName} (Current Company Reputation:{" "}
{Reputation(companyRep)})<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={this.workMoneyGained} /> ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(this.workRepGained)} ({ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for this
company <br />
<br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{`${numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
<br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{`${numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{`${numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{`${numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
<br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, but you will only
gain {penaltyString} of the reputation you've earned so far.
</>,
elem,
);
}
export function finishWork(cancelled, sing = false) {
@ -731,10 +646,7 @@ export function finishWork(cancelled, sing = false) {
dialogBoxCreate(content);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) {
var res =
@ -781,27 +693,17 @@ export function startWorkPartTime(companyName) {
this.workMoneyGainRate = this.getWorkMoneyGain();
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours;
var newCancelButton = clearEventListeners("work-in-progress-cancel-button");
newCancelButton.innerHTML = "Stop Working";
newCancelButton.addEventListener("click", () => {
this.finishWorkPartTime();
return false;
});
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
}
export function workPartTime(numCycles) {
//Cap the number of cycles being processed to whatever would put you at the
//work time limit (8 hours)
var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer8Hours) {
overMax = true;
numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed);
numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / CONSTANTS._idleSpeed);
}
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.workRepGainRate = this.getWorkRepGain();
this.processWorkEarnings(numCycles);
@ -810,60 +712,6 @@ export function workPartTime(numCycles) {
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
return this.finishWorkPartTime();
}
var comp = Companies[this.companyName],
companyRep = "0";
if (comp == null || !(comp instanceof Company)) {
console.error(`Could not find Company: ${this.companyName}`);
} else {
companyRep = comp.playerReputation;
}
const position = this.jobs[this.companyName];
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently working as a {position} at {this.companyName} (Current Company Reputation:{" "}
{Reputation(companyRep)})<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={this.workMoneyGained} /> ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(this.workRepGained)} (
{Reputation(`${numeralWrapper.formatExp(this.workRepGainRate * CYCLES_PER_SEC)} / sec`)}
) reputation for this company <br />
<br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{`${numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
<br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{`${numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{`${numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{`${numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
<br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, and there will be no
penalty because this is a part-time job.
</>,
elem,
);
}
export function finishWorkPartTime(sing = false) {
@ -894,10 +742,7 @@ export function finishWorkPartTime(sing = false) {
dialogBoxCreate(content);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) {
var res =
"You worked for " +
@ -928,17 +773,11 @@ export function finishWorkPartTime(sing = false) {
}
export function startFocusing() {
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
this.focus = true;
Engine.loadWorkInProgressContent();
}
export function stopFocusing() {
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.focus = false;
Engine.loadTerminalContent();
}
/* Working for Faction */
@ -957,24 +796,6 @@ export function startFactionWork(faction) {
this.currentWorkFactionName = faction.name;
this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours;
const cancelButton = clearEventListeners("work-in-progress-cancel-button");
cancelButton.innerHTML = "Stop Faction Work";
cancelButton.addEventListener("click", () => {
this.finishFactionWork(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "visible";
focusButton.innerHTML = "Do something else simultaneously";
focusButton.addEventListener("click", () => {
this.stopFocusing();
return false;
});
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
}
export function startFactionHackWork(faction) {
@ -1046,11 +867,11 @@ export function workForFaction(numCycles) {
//Cap the number of cycles being processed to whatever would put you at limit (20 hours)
var overMax = false;
if (this.timeWorked + Engine._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) {
if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) {
overMax = true;
numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed);
numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / CONSTANTS._idleSpeed);
}
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.processWorkEarnings(numCycles);
@ -1058,44 +879,6 @@ export function workForFaction(numCycles) {
if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) {
return this.finishFactionWork(false);
}
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently {this.currentWorkFactionDescription} for your faction {faction.name}
<br />
(Current Faction Reputation: {Reputation(faction.playerReputation)}). <br />
You have been doing this for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={this.workMoneyGained} /> ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(this.workRepGained)} ({ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for this
faction <br />
<br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
<br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
<br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
<br />
You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
<br />
There is no penalty for cancelling earlier.
</>,
elem,
);
}
export function finishFactionWork(cancelled, sing = false) {
@ -1125,13 +908,8 @@ export function finishFactionWork(cancelled, sing = false) {
);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadFactionContent();
displayFactionContent(faction.name);
if (sing) {
var res =
"You worked for your faction " +
@ -1419,19 +1197,6 @@ export function startCreateProgramWork(programName, time, reqLevel) {
}
this.createProgramName = programName;
var cancelButton = clearEventListeners("work-in-progress-cancel-button");
cancelButton.innerHTML = "Cancel work on creating program";
cancelButton.addEventListener("click", () => {
this.finishCreateProgramWork(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
}
export function createProgramWork(numCycles) {
@ -1441,28 +1206,12 @@ export function createProgramWork(numCycles) {
skillMult = 1 + (skillMult - 1) / 5; //The divider constant can be adjusted as necessary
//Skill multiplier directly applied to "time worked"
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorkedCreateProgram += Engine._idleSpeed * numCycles * skillMult;
var programName = this.createProgramName;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.timeWorkedCreateProgram += CONSTANTS._idleSpeed * numCycles * skillMult;
if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) {
this.finishCreateProgramWork(false);
}
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You are currently working on coding {programName}.<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
The program is {((this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 100).toFixed(2)}
% complete. <br />
If you cancel, your work will be saved and you can come back to complete the program later.
</>,
elem,
);
}
export function finishCreateProgramWork(cancelled) {
@ -1483,12 +1232,8 @@ export function finishCreateProgramWork(cancelled) {
this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadTerminalContent();
this.resetWorkStatus();
}
@ -1501,7 +1246,7 @@ export function startClass(costMult, expMult, className) {
this.className = className;
const gameCPS = 1000 / Engine._idleSpeed;
const gameCPS = 1000 / CONSTANTS._idleSpeed;
//Find cost and exp gain per game cycle
var cost = 0;
@ -1564,62 +1309,11 @@ export function startClass(costMult, expMult, className) {
this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain;
this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain;
this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain;
var cancelButton = clearEventListeners("work-in-progress-cancel-button");
if (
className == CONSTANTS.ClassGymStrength ||
className == CONSTANTS.ClassGymDefense ||
className == CONSTANTS.ClassGymDexterity ||
className == CONSTANTS.ClassGymAgility
) {
cancelButton.innerHTML = "Stop training at gym";
} else {
cancelButton.innerHTML = "Stop taking course";
}
cancelButton.addEventListener("click", () => {
this.finishClass();
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
}
export function takeClass(numCycles) {
this.timeWorked += Engine._idleSpeed * numCycles;
var className = this.className;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.processWorkEarnings(numCycles);
const elem = document.getElementById("work-in-progress-text");
ReactDOM.render(
<>
You have been {className} for {convertTimeMsToTimeElapsedString(this.timeWorked)}
<br />
<br />
This has cost you: <br />
<Money money={-this.workMoneyGained} /> ({MoneyRate(this.workMoneyLossRate * CYCLES_PER_SEC)}) <br />
<br />
You have gained: <br />
{numeralWrapper.formatExp(this.workHackExpGained)} (
{numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
{numeralWrapper.formatExp(this.workStrExpGained)} (
{numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} (
{numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} (
{numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} (
{numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
{numeralWrapper.formatExp(this.workChaExpGained)} (
{numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
You may cancel at any time
</>,
elem,
);
}
//The 'sing' argument defines whether or not this function was called
@ -1650,12 +1344,8 @@ export function finishClass(sing = false) {
);
}
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
Engine.loadLocationContent(false);
if (sing) {
var res =
"After " +
@ -1708,49 +1398,12 @@ export function startCrime(crimeType, hackExp, strExp, defExp, dexExp, agiExp, c
this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.timeNeededToCompleteWork = time;
//Remove all old event listeners from Cancel button
const newCancelButton = clearEventListeners("work-in-progress-cancel-button");
newCancelButton.innerHTML = "Cancel crime";
newCancelButton.addEventListener("click", () => {
this.finishCrime(true);
return false;
});
const focusButton = clearEventListeners("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
//Display Work In Progress Screen
Engine.loadWorkInProgressContent();
}
export function commitCrime(numCycles) {
this.timeWorked += Engine._idleSpeed * numCycles;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
if (this.timeWorked >= this.timeNeededToCompleteWork) {
this.finishCrime(false);
return;
}
var percent = Math.round((this.timeWorked / this.timeNeededToCompleteWork) * 100);
var numBars = Math.round(percent / 5);
if (numBars < 0) {
numBars = 0;
}
if (numBars > 20) {
numBars = 20;
}
var progressBar = "[" + Array(numBars + 1).join("|") + Array(20 - numBars + 1).join(" ") + "]";
var txt = document.getElementById("work-in-progress-text");
txt.innerHTML =
"You are attempting to " +
this.crimeType +
".<br>" +
"Time remaining: " +
convertTimeMsToTimeElapsedString(this.timeNeededToCompleteWork - this.timeWorked) +
"<br>" +
progressBar.replace(/ /g, "&nbsp;");
if (this.timeWorked >= this.timeNeededToCompleteWork) this.finishCrime(false);
}
export function finishCrime(cancelled) {
@ -1892,11 +1545,9 @@ export function finishCrime(cancelled) {
}
this.committingCrimeThruSingFn = false;
this.singFnCrimeWorkerScript = null;
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
this.isWorking = false;
this.crimeType = "";
this.resetWorkStatus();
Engine.loadLocationContent(false);
}
//Cancels the player's current "work" assignment and gives the proper rewards

View File

@ -3,7 +3,6 @@ import { augmentationExists, initAugmentations } from "./Augmentation/Augmentati
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { initBitNodeMultipliers } from "./BitNode/BitNode";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { writeCinematicText } from "./CinematicText";
import { Companies, initCompanies } from "./Company/Companies";
import { resetIndustryResearchTrees } from "./Corporation/IndustryData";
import { Programs } from "./Programs/Programs";
@ -25,13 +24,7 @@ import { SpecialServerIps, prestigeSpecialServerIps, SpecialServerNames } from "
import { deleteStockMarket, initStockMarket, initSymbolToStockMap } from "./StockMarket/StockMarket";
import { Terminal } from "./Terminal";
import { Page, routing } from "./ui/navigationTracking";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import Decimal from "decimal.js";
@ -39,9 +32,6 @@ const BitNode8StartingMoney = 250e6;
// Prestige by purchasing augmentation
function prestigeAugmentation() {
// Set Navigation to Terminal screen, for any logic that depends on it
routing.navigateTo(Page.Terminal);
initBitNodeMultipliers(Player);
const megaCorpFactions = [
@ -62,9 +52,6 @@ function prestigeAugmentation() {
});
Player.prestigeAugmentation();
Terminal.clear();
Engine.loadTerminalContent();
// Delete all Worker Scripts objects
prestigeWorkerScripts();
@ -244,11 +231,6 @@ function prestigeSourceFile(flume) {
// Messages
initMessages();
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "visible";
Terminal.clear();
Engine.loadTerminalContent();
// BitNode 3: Corporatocracy
if (Player.bitNodeN === 3) {
homeComp.messages.push(LiteratureNames.CorporationManagementHandbook);
@ -258,56 +240,6 @@ function prestigeSourceFile(flume) {
);
}
// BitNode 6: Bladeburner
if (Player.bitNodeN === 6) {
var cinematicText = [
"In the middle of the 21st century, OmniTek Incorporated advanced robot evolution " +
"with their Synthoids (synthetic androids), a being virtually identical to a human.",
"------",
"Their sixth-generation Synthoids, called MK-VI, were stronger, faster, and more " +
"intelligent than humans. Many argued that the MK-VI Synthoids were the first " +
"example of sentient AI.",
"------",
"Unfortunately, in 2070 a terrorist group called Ascendis Totalis hacked into OmniTek and " +
"uploaded a rogue AI into their Synthoid manufacturing facilities.",
"------",
"The MK-VI Synthoids infected by the rogue AI turned hostile toward humanity, initiating " +
"the deadliest conflict in human history. This dark chapter is now known as the Synthoid Uprising.",
"------",
"In the aftermath of the Uprising, further manufacturing of Synthoids with advanced AI " +
"was banned. MK-VI Synthoids that did not have the rogue Ascendis Totalis AI were " +
"allowed to continue their existence.",
"------",
"The intelligence community believes that not all of the rogue MK-VI Synthoids from the Uprising were " +
"found and destroyed, and that many of them are blending in as normal humans in society today. " +
"As a result, many nations have created Bladeburner divisions, special units that are tasked with " +
"investigating and dealing with Synthoid threats.",
];
writeCinematicText(cinematicText)
.then(function () {
var popupId = "bladeburner-bitnode-start-nsa-notification";
var txt = createElement("p", {
innerText:
"Visit the National Security Agency (NSA) to apply for their Bladeburner " +
"division! You will need 100 of each combat stat before doing this.",
});
var brEl = createElement("br");
var okBtn = createElement("a", {
class: "a-link-button",
innerText: "Got it!",
padding: "8px",
clickListener: () => {
removeElementById(popupId);
return false;
},
});
createPopup(popupId, [txt, brEl, okBtn]);
})
.catch(function (e) {
exceptionAlert(e);
});
}
// BitNode 8: Ghost of Wall Street
if (Player.bitNodeN === 8) {
Player.money = new Decimal(BitNode8StartingMoney);

View File

@ -1,6 +1,7 @@
import { BaseServer } from "../Server/BaseServer";
import { ITerminal } from "../Terminal/ITerminal";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "../ui/Router";
export interface IProgramCreate {
level: number;
@ -12,12 +13,12 @@ export interface IProgramCreate {
export class Program {
name = "";
create: IProgramCreate | null;
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;
constructor(
name: string,
create: IProgramCreate | null,
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void,
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void,
) {
this.name = name;
this.create = create;

View File

@ -3,6 +3,7 @@ import { CONSTANTS } from "../../Constants";
import { BaseServer } from "../../Server/BaseServer";
import { Server } from "../../Server/Server";
import { ITerminal } from "../../Terminal/ITerminal";
import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { HacknetServer } from "../../Hacknet/HacknetServer";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
@ -30,7 +31,7 @@ export interface IProgramCreationParams {
key: string;
name: string;
create: IProgramCreate | null;
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;
}
export const programsMetadata: IProgramCreationParams[] = [
@ -43,7 +44,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(1),
time: CONSTANTS.MillisecondsPerFiveMinutes,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot nuke this kind of server.");
return;
@ -72,7 +73,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(50),
time: CONSTANTS.MillisecondsPerFiveMinutes * 2,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run BruteSSH.exe on this kind of server.");
return;
@ -96,7 +97,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(100),
time: CONSTANTS.MillisecondsPerHalfHour,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run FTPCrack.exe on this kind of server.");
return;
@ -120,7 +121,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(250),
time: CONSTANTS.MillisecondsPer2Hours,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run relaySMTP.exe on this kind of server.");
return;
@ -144,7 +145,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(500),
time: CONSTANTS.MillisecondsPer4Hours,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run HTTPWorm.exe on this kind of server.");
return;
@ -168,7 +169,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(750),
time: CONSTANTS.MillisecondsPer8Hours,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {
if (!(server instanceof Server)) {
terminal.error("Cannot run SQLInject.exe on this kind of server.");
return;
@ -192,7 +193,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(75),
time: CONSTANTS.MillisecondsPerQuarterHour,
},
run: (terminal: ITerminal): void => {
run: (router: IRouter, terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.print("DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5.");
},
@ -206,7 +207,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(400),
time: CONSTANTS.MillisecondsPer2Hours,
},
run: (terminal: ITerminal): void => {
run: (router: IRouter, terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.print("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.");
},
@ -220,7 +221,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(75),
time: CONSTANTS.MillisecondsPerHalfHour,
},
run: (terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]): void => {
if (args.length !== 1) {
terminal.print("Must pass a server hostname or IP as an argument for ServerProfiler.exe");
return;
@ -270,7 +271,7 @@ export const programsMetadata: IProgramCreationParams[] = [
req: requireHackingLevel(25),
time: CONSTANTS.MillisecondsPerQuarterHour,
},
run: (terminal: ITerminal): void => {
run: (router: IRouter, terminal: ITerminal): void => {
terminal.print("This executable cannot be run.");
terminal.print("AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'.");
terminal.print("When using scan-analyze, click on a server's hostname to connect to it.");
@ -285,10 +286,11 @@ export const programsMetadata: IProgramCreationParams[] = [
req: bitFlumeRequirements(),
time: CONSTANTS.MillisecondsPerFiveMinutes / 20,
},
run: (terminal: ITerminal, player: IPlayer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer): void => {
const popupId = "bitflume-popup";
createPopup(popupId, BitFlumePopup, {
player: player,
router: router,
popupId: popupId,
});
},
@ -297,7 +299,7 @@ export const programsMetadata: IProgramCreationParams[] = [
key: "Flight",
name: "fl1ght.exe",
create: null,
run: (terminal: ITerminal, player: IPlayer): void => {
run: (router: IRouter, terminal: ITerminal, player: IPlayer): void => {
const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement * 30);
const fulfilled =
player.augmentations.length >= numAugReq && player.money.gt(1e11) && player.hacking_skill >= 2500;

View File

@ -1,15 +1,13 @@
import React, { useState, useEffect } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { use } from "../../ui/Context";
import { getAvailableCreatePrograms } from "../ProgramHelpers";
import { Box, ButtonGroup, Tooltip, Typography } from "@mui/material";
import Button from "@mui/material/Button";
interface IProps {
player: IPlayer;
}
export function ProgramsRoot(props: IProps): React.ReactElement {
export function ProgramsRoot(): React.ReactElement {
const player = use.Player();
const router = use.Router();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
@ -31,13 +29,18 @@ export function ProgramsRoot(props: IProps): React.ReactElement {
</Typography>
</Box>
<ButtonGroup>
{getAvailableCreatePrograms(props.player).map((program) => {
{getAvailableCreatePrograms(player).map((program) => {
const create = program.create;
if (create === null) return <></>;
return (
<Tooltip key={program.name} title={create.tooltip}>
<Button onClick={() => props.player.startCreateProgramWork(program.name, create.time, create.level)}>
<Button
onClick={() => {
player.startCreateProgramWork(program.name, create.time, create.level);
router.toWork();
}}
>
{program.name}
</Button>
</Tooltip>

6
src/RedPill.d.ts vendored
View File

@ -1,6 +1,2 @@
export declare let redPillFlag: boolean;
export declare function hackWorldDaemon(
currentNodeNumber: number,
flume: boolean = false,
quick: boolean = false,
): void;
export declare function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number);

View File

@ -1,7 +1,6 @@
/**
* Implementation for what happens when you destroy a BitNode
*/
import { Engine } from "./engine";
import { Player } from "./Player";
import { prestigeSourceFile } from "./Prestige";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
@ -9,20 +8,10 @@ import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SourceFiles } from "./SourceFile/SourceFiles";
import { dialogBoxCreate } from "../utils/DialogBox";
import { BitverseRoot } from "./BitNode/ui/BitverseRoot";
import React from "react";
import ReactDOM from "react-dom";
let redPillFlag = false;
function hackWorldDaemon(currentNodeNumber, flume = false, quick = false) {
// Clear the screen
const container = document.getElementById("red-pill-container");
ReactDOM.unmountComponentAtNode(container);
Engine.loadRedPillContent();
ReactDOM.render(
<BitverseRoot destroyedBitNodeNum={currentNodeNumber} flume={flume} enter={enterBitNode} quick={quick} />,
container,
);
function hackWorldDaemon(router, flume = false, quick = false) {
router.toBitVerse(flume, quick);
redPillFlag = true;
}
@ -68,12 +57,12 @@ function giveSourceFile(bitNodeNumber) {
Player.intelligence = 1;
}
dialogBoxCreate(
"You received a Source-File for destroying a Bit Node!<br><br>" + sourceFile.name + "<br><br>" + sourceFile.info,
"You received a Source-File for destroying a BitNode!<br><br>" + sourceFile.name + "<br><br>" + sourceFile.info,
);
}
}
function enterBitNode(flume, destroyedBitNode, newBitNode) {
export function enterBitNode(router, flume, destroyedBitNode, newBitNode) {
if (!flume) {
giveSourceFile(destroyedBitNode);
} else {
@ -86,12 +75,14 @@ function enterBitNode(flume, destroyedBitNode, newBitNode) {
Player.intelligence = 1;
}
redPillFlag = false;
const container = document.getElementById("red-pill-container");
ReactDOM.unmountComponentAtNode(container);
// Set new Bit Node
Player.bitNodeN = newBitNode;
if (newBitNode === 6) {
router.toBladeburnerCinematic();
} else {
router.toTerminal();
}
prestigeSourceFile(flume);
}

View File

@ -6,7 +6,6 @@
*/
import { calculateRamUsage } from "./RamCalculations";
import { ScriptUrl } from "./ScriptUrl";
import { Page, routing } from "../ui/navigationTracking";
import { setTimeoutRef } from "../utils/SetTimeoutRef";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import { styled, useTheme, Theme, CSSObject } from "@mui/material/styles";
import { styled, Theme, CSSObject } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import MuiDrawer from "@mui/material/Drawer";
@ -15,8 +15,6 @@ import Typography from "@mui/material/Typography";
import Collapse from "@mui/material/Collapse";
import Badge from "@mui/material/Badge";
import { TTheme as BBTheme, colors } from "../../ui/React/Theme";
import ComputerIcon from "@mui/icons-material/Computer";
import LastPageIcon from "@mui/icons-material/LastPage"; // Terminal
import CreateIcon from "@mui/icons-material/Create"; // Create Script
@ -57,8 +55,6 @@ import { cinematicTextFlag } from "../../CinematicText";
import { KEY } from "../../../utils/helpers/keyCodes";
import { FconfSettings } from "../../Fconf/FconfSettings";
const drawerWidth = 240;
const openedMixin = (theme: Theme): CSSObject => ({
width: theme.spacing(31),
transition: theme.transitions.create("width", {
@ -97,7 +93,7 @@ const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== "open"
const useStyles = makeStyles((theme: Theme) =>
createStyles({
active: {
borderLeft: "3px solid " + colors.primary,
borderLeft: "3px solid " + theme.palette.primary.main,
},
listitem: {},
}),

View File

@ -1,4 +1,3 @@
import { buyStock, sellStock, shortStock, sellShort } from "./BuyingAndSelling";
import { IOrderBook } from "./IOrderBook";
import { IStockMarket } from "./IStockMarket";
import { Order } from "./Order";
@ -9,23 +8,17 @@ import { InitStockMetadata } from "./data/InitStockMetadata";
import { OrderTypes } from "./data/OrderTypes";
import { PositionTypes } from "./data/PositionTypes";
import { StockSymbols } from "./data/StockSymbols";
import { StockMarketRoot } from "./ui/StockMarketRoot";
import { CONSTANTS } from "../Constants";
import { WorkerScript } from "../Netscript/WorkerScript";
import { Player } from "../Player";
import { IMap } from "../types";
import { EventEmitter } from "../utils/EventEmitter";
import { Page, routing } from ".././ui/navigationTracking";
import { numeralWrapper } from ".././ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { Reviver } from "../../utils/JSONReviver";
import * as React from "react";
import * as ReactDOM from "react-dom";
export let StockMarket: IStockMarket = {
lastUpdate: 0,
Orders: {},
@ -150,7 +143,6 @@ export function loadStockMarket(saveString: string): void {
ticksUntilCycle: 0,
} as IStockMarket;
} else {
console.log(JSON.parse(saveString, Reviver));
StockMarket = JSON.parse(saveString, Reviver);
}
}
@ -314,14 +306,6 @@ export function processStockPrices(numCycles = 1): void {
}
}
let stockMarketContainer: HTMLElement | null = null;
function setStockMarketContainer(): void {
stockMarketContainer = document.getElementById("generic-react-container");
document.removeEventListener("DOMContentLoaded", setStockMarketContainer);
}
document.addEventListener("DOMContentLoaded", setStockMarketContainer);
export function initStockMarketFnForReact(): void {
initStockMarket();
initSymbolToStockMap();

View File

@ -36,7 +36,7 @@ type IProps = {
stockMarket: IStockMarket;
};
export function StockMarketRoot(props: IProps) {
export function StockMarketRoot(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);

View File

@ -56,10 +56,10 @@ export interface ITerminal {
startAnalyze(): void;
startBackdoor(player: IPlayer): void;
startHack(player: IPlayer): void;
finishHack(player: IPlayer, cancelled?: boolean): void;
finishBackdoor(player: IPlayer, cancelled?: boolean): void;
finishHack(router: IRouter, player: IPlayer, cancelled?: boolean): void;
finishBackdoor(router: IRouter, player: IPlayer, cancelled?: boolean): void;
finishAnalyze(player: IPlayer, cancelled?: boolean): void;
finishAction(player: IPlayer, cancelled?: boolean): void;
finishAction(router: IRouter, player: IPlayer, cancelled?: boolean): void;
getFilepath(filename: string): string;
getFile(player: IPlayer, filename: string): Script | TextFile | string | null;
getScript(player: IPlayer, filename: string): Script | null;
@ -74,7 +74,7 @@ export interface ITerminal {
executeCommands(router: IRouter, player: IPlayer, commands: string): void;
// If there was any changes, will return true, once.
pollChanges(): boolean;
process(player: IPlayer, cycles: number): void;
process(router: IRouter, player: IPlayer, cycles: number): void;
prestige(): void;
getProgressText(): string;
}

View File

@ -3,7 +3,6 @@ import { IRouter } from "../ui/Router";
import { IPlayer } from "../PersonObjects/IPlayer";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { BaseServer } from "../Server/BaseServer";
import { hackWorldDaemon } from "../RedPill";
import { Programs } from "../Programs/Programs";
import { CodingContractResult } from "../CodingContracts";
@ -86,11 +85,11 @@ export class Terminal implements ITerminal {
// Excludes the trailing forward slash
currDir = "/";
process(player: IPlayer, cycles: number): void {
process(router: IRouter, player: IPlayer, cycles: number): void {
if (this.action === null) return;
this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000;
this.hasChanges = true;
if (this.action.timeLeft < 0) this.finishAction(player, false);
if (this.action.timeLeft < 0) this.finishAction(router, player, false);
}
pollChanges(): boolean {
@ -138,7 +137,7 @@ export class Terminal implements ITerminal {
}
// Complete the hack/analyze command
finishHack(player: IPlayer, cancelled = false): void {
finishHack(router: IRouter, player: IPlayer, cancelled = false): void {
if (cancelled) return;
const server = player.getCurrentServer();
@ -156,7 +155,7 @@ export class Terminal implements ITerminal {
if (player.bitNodeN == null) {
player.bitNodeN = 1;
}
hackWorldDaemon(player.bitNodeN);
router.toBitVerse(false, false);
return;
}
server.backdoorInstalled = true;
@ -190,7 +189,7 @@ export class Terminal implements ITerminal {
}
}
finishBackdoor(player: IPlayer, cancelled = false): void {
finishBackdoor(router: IRouter, player: IPlayer, cancelled = false): void {
if (!cancelled) {
const server = player.getCurrentServer();
if (
@ -200,7 +199,7 @@ export class Terminal implements ITerminal {
if (player.bitNodeN == null) {
player.bitNodeN = 1;
}
hackWorldDaemon(player.bitNodeN);
router.toBitVerse(false, false);
return;
}
server.backdoorInstalled = true;
@ -238,16 +237,16 @@ export class Terminal implements ITerminal {
}
}
finishAction(player: IPlayer, cancelled = false): void {
finishAction(router: IRouter, player: IPlayer, cancelled = false): void {
if (this.action === null) {
if (!cancelled) throw new Error("Finish action called when there was no action");
return;
}
this.print(this.getProgressText());
if (this.action.action === "h") {
this.finishHack(player, cancelled);
this.finishHack(router, player, cancelled);
} else if (this.action.action === "b") {
this.finishBackdoor(player, cancelled);
this.finishBackdoor(router, player, cancelled);
} else if (this.action.action === "a") {
this.finishAnalyze(player, cancelled);
}

View File

@ -31,6 +31,7 @@ export function runProgram(
for (const program of Object.values(Programs)) {
if (program.name === programName) {
program.run(
router,
terminal,
player,
server,

View File

@ -17,21 +17,21 @@ import { FconfSettings } from "../../Fconf/FconfSettings";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
textfield: {
margin: 0,
margin: theme.spacing(0),
width: "100%",
},
input: {
backgroundColor: "#000",
},
nopadding: {
padding: 0,
padding: theme.spacing(0),
},
preformatted: {
whiteSpace: "pre-wrap",
margin: 0,
margin: theme.spacing(0),
},
list: {
padding: 0,
padding: theme.spacing(0),
height: "100%",
},
}),
@ -147,7 +147,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
if (ref) ref.focus();
// Cancel action
if (event.keyCode === KEY.C && event.ctrlKey) {
terminal.finishAction(player, true);
terminal.finishAction(router, player, true);
}
}
document.addEventListener("keydown", keyDown);

View File

@ -1,74 +1,44 @@
/**
* Game engine. Handles the main game loop as well as the main UI pages
*
* TODO: Separate UI functionality into its own component
* Game engine. Handles the main game loop.
*/
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { Augmentations } from "./Augmentation/Augmentations";
import { initAugmentations, installAugmentations } from "./Augmentation/AugmentationHelpers";
import { onExport } from "./ExportBonus";
import { AugmentationsRoot } from "./Augmentation/ui/Root";
import { initAugmentations } from "./Augmentation/AugmentationHelpers";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { initBitNodeMultipliers } from "./BitNode/BitNode";
import { Bladeburner } from "./Bladeburner/Bladeburner";
import { CharacterOverview } from "./ui/React/CharacterOverview";
import { generateRandomContract } from "./CodingContractGenerator";
import { initCompanies } from "./Company/Companies";
import { Corporation } from "./Corporation/Corporation";
import { CONSTANTS } from "./Constants";
import { DevMenuRoot } from "./DevMenu";
import { Factions, initFactions } from "./Faction/Factions";
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
import { FactionsRoot } from "./Faction/ui/FactionsRoot";
import { Root as BladeburnerRoot } from "./Bladeburner/ui/Root";
import { Root as GangRoot } from "./Gang/ui/Root";
import { SidebarRoot } from "./Sidebar/ui/SidebarRoot";
import { CorporationRoot } from "./Corporation/ui/CorporationRoot";
import { ResleeveRoot } from "./PersonObjects/Resleeving/ui/ResleeveRoot";
import { GameOptionsRoot } from "./ui/React/GameOptionsRoot";
import { GameRoot } from "./ui/GameRoot";
import { GameRoot, Router } from "./ui/GameRoot";
import { TTheme as Theme } from "./ui/React/Theme";
import { SleeveRoot } from "./PersonObjects/Sleeve/ui/SleeveRoot";
import { displayInfiltrationContent } from "./Infiltration/Helper";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from "./PersonObjects/formulas/reputation";
import { hasHacknetServers, processHacknetEarnings } from "./Hacknet/HacknetHelpers";
import { HacknetRoot } from "./Hacknet/ui/HacknetRoot";
import { iTutorialStart } from "./InteractiveTutorial";
import { LocationName } from "./Locations/data/LocationNames";
import { LocationRoot } from "./Locations/ui/Root";
import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers";
import { inMission, currMission } from "./Missions";
import { workerScripts } from "./Netscript/WorkerScripts";
import { loadAllRunningScripts, updateOnlineScriptTimes } from "./NetscriptWorker";
import { Player } from "./Player";
import { prestigeAugmentation } from "./Prestige";
import { ProgramsRoot } from "./Programs/ui/ProgramsRoot";
import { saveObject, loadGame } from "./SaveObject";
import { Root as ScriptEditorRoot } from "./ScriptEditor/ui/Root";
import { initForeignServers, AllServers } from "./Server/AllServers";
import { initForeignServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags";
import { initSpecialServerIps } from "./Server/SpecialServerIps";
import { initSymbolToStockMap, processStockPrices } from "./StockMarket/StockMarket";
import { MilestonesRoot } from "./Milestones/ui/MilestonesRoot";
import { TerminalRoot } from "./Terminal/ui/TerminalRoot";
import { Terminal } from "./Terminal";
import { TutorialRoot } from "./Tutorial/ui/TutorialRoot";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import { CharacterInfo } from "./ui/CharacterInfo";
import { Page, routing } from "./ui/navigationTracking";
import { Money } from "./ui/React/Money";
import { Hashes } from "./ui/React/Hashes";
import { Reputation } from "./ui/React/Reputation";
import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root";
import { MainMenuLinks } from "./ui/MainMenu/Links";
import { dialogBoxCreate } from "../utils/DialogBox";
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen";
@ -79,347 +49,19 @@ import React from "react";
import ReactDOM from "react-dom";
const Engine = {
// Clickable objects
Clickables: {
// Main menu buttons
saveMainMenuButton: null,
deleteMainMenuButton: null,
},
// Display objects
// TODO-Refactor this into its own component
Display: {
// Generic page that most react loads into.
content: null,
// Main menu content
infiltrationContent: null,
workInProgressContent: null,
redPillContent: null,
cinematicTextContent: null,
missionContent: null,
overview: null,
},
indexedDb: undefined,
// Time variables (milliseconds unix epoch time)
_lastUpdate: new Date().getTime(),
_idleSpeed: 200, // Speed (in ms) at which the main loop is updated
loadTerminalContent: function () {
// Engine.hideAllContent();
// Engine.Display.content.style.display = "block";
// routing.navigateTo(Page.CharacterInfo);
// ReactDOM.render(
// <Theme>
// <TerminalRoot terminal={Terminal} engine={this} player={Player} />
// </Theme>,
// Engine.Display.content,
// );
// MainMenuLinks.Stats.classList.add("active");
},
loadCharacterContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.CharacterInfo);
ReactDOM.render(<CharacterInfo player={Player} />, Engine.Display.content);
MainMenuLinks.Stats.classList.add("active");
},
loadScriptEditorContent: function (filename = "", code = "") {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.ScriptEditor);
MainMenuLinks.ScriptEditor.classList.add("active");
ReactDOM.render(
<ScriptEditorRoot filename={filename} code={code} player={Player} engine={this} />,
Engine.Display.content,
);
},
loadActiveScriptsContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.ActiveScripts);
MainMenuLinks.ActiveScripts.classList.add("active");
ReactDOM.render(<ActiveScriptsRoot p={Player} workerScripts={workerScripts} />, Engine.Display.content);
},
loadHacknetNodesContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.HacknetNodes);
MainMenuLinks.HacknetNodes.classList.add("active");
ReactDOM.render(<HacknetRoot player={Player} />, Engine.Display.content);
},
loadCreateProgramContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.CreateProgram);
MainMenuLinks.CreateProgram.classList.add("active");
ReactDOM.render(
<Theme>
<ProgramsRoot player={Player} />
</Theme>,
Engine.Display.content,
);
},
loadFactionsContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Factions);
MainMenuLinks.Factions.classList.add("active");
ReactDOM.render(<FactionsRoot player={Player} engine={this} />, Engine.Display.content);
},
// TODO reactify
loadFactionContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Faction);
},
loadAugmentationsContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Augmentations);
MainMenuLinks.Augmentations.classList.add("active");
function backup() {
saveObject.exportGame();
onExport(Player);
}
ReactDOM.render(
<AugmentationsRoot exportGameFn={backup} installAugmentationsFn={installAugmentations} />,
Engine.Display.content,
);
},
loadMilestonesContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Milestones);
MainMenuLinks.Milestones.classList.add("active");
ReactDOM.render(<MilestonesRoot player={Player} />, Engine.Display.content);
},
loadLocationContent: function (initiallyInCity = true) {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Location);
MainMenuLinks.City.classList.add("active");
ReactDOM.render(
<LocationRoot initiallyInCity={initiallyInCity} engine={Engine} p={Player} />,
Engine.Display.content,
);
},
loadTravelContent: function () {
// Same as loadLocationContent() except first set the location to the travel agency,
// and make sure that the 'City' main menu link doesnt become 'active'
Engine.hideAllContent();
Player.gotoLocation(LocationName.TravelAgency);
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Location);
MainMenuLinks.Travel.classList.add("active");
ReactDOM.render(<LocationRoot initiallyInCity={false} engine={Engine} p={Player} />, Engine.Display.content);
},
loadJobContent: function () {
// Same as loadLocationContent(), except first set the location to the job.
// Make sure that the 'City' main menu link doesnt become 'active'
if (Player.companyName == "") {
dialogBoxCreate(
"You do not currently have a job! You can visit various companies " + "in the city and try to find a job.",
);
return;
}
Engine.hideAllContent();
Player.gotoLocation(Player.companyName);
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Location);
MainMenuLinks.Job.classList.add("active");
ReactDOM.render(<LocationRoot initiallyInCity={false} engine={Engine} p={Player} />, Engine.Display.content);
},
// TODO reactify
loadWorkInProgressContent: function () {
Engine.hideAllContent();
const mainMenu = document.getElementById("mainmenu-container");
console.log("hiding loadWorkInProgressContent");
mainMenu.style.visibility = "hidden";
Engine.Display.workInProgressContent.style.display = "block";
console.log(Engine.Display.workInProgressContent);
routing.navigateTo(Page.WorkInProgress);
},
loadRedPillContent: function () {
Engine.hideAllContent();
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
Engine.Display.redPillContent.style.display = "block";
routing.navigateTo(Page.RedPill);
},
// TODO reactify
loadCinematicTextContent: function () {
Engine.hideAllContent();
var mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
Engine.Display.cinematicTextContent.style.display = "block";
routing.navigateTo(Page.CinematicText);
},
// TODO reactify
loadInfiltrationContent: function (name, difficulty, maxLevel) {
Engine.hideAllContent();
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
Engine.Display.infiltrationContent.style.display = "block";
routing.navigateTo(Page.Infiltration);
displayInfiltrationContent(this, Player, name, difficulty, maxLevel);
},
loadStockMarketContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.StockMarket);
MainMenuLinks.StockMarket.classList.add("active");
//displayStockMarketContent();
},
loadGangContent: function () {
if (!Player.inGang()) return;
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Gang);
MainMenuLinks.Gang.classList.add("active");
ReactDOM.render(<GangRoot engine={this} gang={Player.gang} player={Player} />, Engine.Display.content);
},
loadMissionContent: function () {
Engine.hideAllContent();
document.getElementById("mainmenu-container").style.visibility = "hidden";
document.getElementById("character-overview").style.visibility = "hidden";
Engine.Display.missionContent.style.display = "block";
routing.navigateTo(Page.Mission);
},
loadCorporationContent: function () {
if (!(Player.corporation instanceof Corporation)) return;
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Corporation);
MainMenuLinks.Corporation.classList.add("active");
ReactDOM.render(<CorporationRoot corp={Player.corporation} player={Player} />, Engine.Display.content);
},
loadBladeburnerContent: function () {
if (!(Player.bladeburner instanceof Bladeburner)) return;
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Bladeburner);
MainMenuLinks.Bladeburner.classList.add("active");
ReactDOM.render(
<BladeburnerRoot bladeburner={Player.bladeburner} player={Player} engine={this} />,
Engine.Display.content,
);
},
loadSleevesContent: function () {
Engine.hideAllContent();
Engine.Display.content.style.display = "block";
routing.navigateTo(Page.Sleeves);
ReactDOM.render(<SleeveRoot player={Player} />, Engine.Display.content);
},
loadResleevingContent: function () {
Engine.hideAllContent();
routing.navigateTo(Page.Resleeves);
Engine.Display.content.style.display = "block";
MainMenuLinks.City.classList.add("active");
ReactDOM.render(<ResleeveRoot player={Player} />, Engine.Display.content);
},
loadGameOptionsContent: function () {
Engine.hideAllContent();
routing.navigateTo(Page.GameOptions);
Engine.Display.content.style.display = "block";
MainMenuLinks.City.classList.add("active");
ReactDOM.render(
<Theme>
<GameOptionsRoot
player={Player}
save={() => saveObject.saveGame(Engine.indexedDb)}
delete={() => saveObject.deleteGame(Engine.indexedDb)}
export={() => saveObject.exportGame()}
import={() => saveObject.importGame()}
forceKill={() => {
for (const hostname of Object.keys(AllServers)) {
AllServers[hostname].runningScripts = [];
}
dialogBoxCreate("Forcefully deleted all running scripts. Please save and refresh page.");
}}
softReset={() => {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
}}
/>
</Theme>,
Engine.Display.content,
);
},
// Helper function that hides all content
hideAllContent: function () {
// Engine.Display.content.style.display = "none";
// Engine.Display.content.scrollTop = 0;
// ReactDOM.unmountComponentAtNode(Engine.Display.content);
Engine.Display.infiltrationContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.infiltrationContent);
Engine.Display.workInProgressContent.style.display = "none";
Engine.Display.redPillContent.style.display = "none";
Engine.Display.cinematicTextContent.style.display = "none";
Engine.Display.missionContent.style.display = "none";
},
displayCharacterOverviewInfo: function () {
ReactDOM.render(
<Theme>
<CharacterOverview player={Player} save={() => saveObject.saveGame(Engine.indexedDb)} />
</Theme>,
document.getElementById("character-overview"),
);
},
// Main Game Loop
idleTimer: function () {
// Get time difference
const _thisUpdate = new Date().getTime();
let diff = _thisUpdate - Engine._lastUpdate;
const offset = diff % Engine._idleSpeed;
// Divide this by cycle time to determine how many cycles have elapsed since last update
diff = Math.floor(diff / Engine._idleSpeed);
if (diff > 0) {
// Update the game engine by the calculated number of cycles
Engine._lastUpdate = _thisUpdate - offset;
Player.lastUpdate = _thisUpdate - offset;
Engine.updateGame(diff);
}
window.requestAnimationFrame(Engine.idleTimer);
},
updateGame: function (numCycles = 1) {
const time = numCycles * Engine._idleSpeed;
const time = numCycles * CONSTANTS._idleSpeed;
if (Player.totalPlaytime == null) {
Player.totalPlaytime = 0;
}
@ -433,7 +75,7 @@ const Engine = {
Player.playtimeSinceLastAug += time;
Player.playtimeSinceLastBitnode += time;
Terminal.process(Player, numCycles);
Terminal.process(Router, Player, numCycles);
// Working
if (Player.isWorking) {
@ -581,7 +223,7 @@ const Engine = {
}
if (Player.bladeburner instanceof Bladeburner) {
try {
Player.bladeburner.process(Player);
Player.bladeburner.process(Router, Player);
} catch (e) {
exceptionAlert("Exception caught in Bladeburner.process(): " + e);
}
@ -598,42 +240,11 @@ const Engine = {
}
},
/**
* Used in game when clicking on a main menu header (NOT used for initialization)
* @param open {boolean} Whether header is being opened or closed
* @param elems {HTMLElement[]} li Elements under header
* @param links {HTMLElement[]} a elements under header
*/
toggleMainMenuHeader: function (open, elems, links) {
for (var i = 0; i < elems.length; ++i) {
if (open) {
elems[i].style.opacity = 1;
elems[i].style.maxHeight = elems[i].scrollHeight + "px";
} else {
elems[i].style.opacity = 0;
elems[i].style.maxHeight = null;
}
}
for (var i = 0; i < links.length; ++i) {
if (open) {
links[i].style.opacity = 1;
links[i].style.maxHeight = links[i].scrollHeight + "px";
links[i].style.pointerEvents = "auto";
} else {
links[i].style.opacity = 0;
links[i].style.maxHeight = null;
links[i].style.pointerEvents = "none";
}
}
},
load: function (saveString) {
// Load game from save or create new game
if (loadGame(saveString)) {
initBitNodeMultipliers(Player);
Engine.setDisplayElements(); // Sets variables for important DOM elements
Engine.init(); // Initialize buttons, work, etc.
updateSourceFileFlags(Player);
initAugmentations(); // Also calls Player.reapplyAllAugmentations()
Player.reapplyAllSourceFiles();
@ -645,7 +256,7 @@ const Engine = {
Engine._lastUpdate = new Date().getTime();
const lastUpdate = Player.lastUpdate;
const timeOffline = Engine._lastUpdate - lastUpdate;
const numCyclesOffline = Math.floor(timeOffline / Engine._idleSpeed);
const numCyclesOffline = Math.floor(timeOffline / CONSTANTS._idleSpeed);
let offlineReputation = 0;
const offlineHackingIncome = (Player.moneySourceA.hacking / Player.playtimeSinceLastAug) * timeOffline * 0.75;
@ -667,6 +278,7 @@ const Engine = {
} else {
Player.work(numCyclesOffline);
}
Player.focus = false;
} else {
for (let i = 0; i < Player.factions.length; i++) {
const facName = Player.factions[i];
@ -741,7 +353,7 @@ const Engine = {
}
// Update total playtime
var time = numCyclesOffline * Engine._idleSpeed;
var time = numCyclesOffline * CONSTANTS._idleSpeed;
if (Player.totalPlaytime == null) {
Player.totalPlaytime = 0;
}
@ -794,72 +406,27 @@ const Engine = {
},
setDisplayElements: function () {
// Engine.Display.content = document.getElementById("generic-react-container");
// Engine.Display.content.style.display = "none";
Engine.Display.missionContent = document.getElementById("mission-container");
Engine.Display.missionContent.style.display = "none";
// Work In Progress
Engine.Display.workInProgressContent = document.getElementById("work-in-progress-container");
Engine.Display.workInProgressContent.style.display = "none";
// Red Pill / Hack World Daemon
Engine.Display.redPillContent = document.getElementById("red-pill-container");
Engine.Display.redPillContent.style.display = "none";
Engine.Display.infiltrationContent = document.getElementById("infiltration-container");
Engine.Display.infiltrationContent.style.display = "none";
// Cinematic Text
Engine.Display.cinematicTextContent = document.getElementById("cinematic-text-container");
Engine.Display.cinematicTextContent.style.display = "none";
Engine.Display.overview = document.getElementById("character-overview");
},
// Initialization
init: function () {
// Player was working cancel button
Engine.displayCharacterOverviewInfo();
if (Player.isWorking) {
var cancelButton = document.getElementById("work-in-progress-cancel-button");
cancelButton.addEventListener("click", function () {
if (Player.workType == CONSTANTS.WorkTypeFaction) {
Player.finishFactionWork(true);
} else if (Player.workType == CONSTANTS.WorkTypeCreateProgram) {
Player.finishCreateProgramWork(true);
} else if (Player.workType == CONSTANTS.WorkTypeStudyClass) {
Player.finishClass();
} else if (Player.workType == CONSTANTS.WorkTypeCrime) {
Player.finishCrime(true);
} else if (Player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
Player.finishWorkPartTime();
} else {
Player.finishWork(true);
}
});
const focusButton = document.getElementById("work-in-progress-something-else-button");
focusButton.style.visibility = "hidden";
const focusable = [CONSTANTS.WorkTypeFaction, CONSTANTS.WorkTypeCompanyPartTime, CONSTANTS.WorkTypeCompany];
if (focusable.includes(Player.workType)) {
focusButton.style.visibility = "visible";
focusButton.addEventListener("click", function () {
Player.stopFocusing();
});
}
Engine.loadWorkInProgressContent();
} else {
Engine.loadTerminalContent();
}
},
start: function () {
// Run main loop
Engine.idleTimer();
// Get time difference
const _thisUpdate = new Date().getTime();
let diff = _thisUpdate - Engine._lastUpdate;
const offset = diff % CONSTANTS._idleSpeed;
// Divide this by cycle time to determine how many cycles have elapsed since last update
diff = Math.floor(diff / CONSTANTS._idleSpeed);
if (diff > 0) {
// Update the game engine by the calculated number of cycles
Engine._lastUpdate = _thisUpdate - offset;
Player.lastUpdate = _thisUpdate - offset;
Engine.updateGame(diff);
}
window.requestAnimationFrame(Engine.start);
},
};

View File

@ -45,25 +45,8 @@
<div id="generic-react-container"></div>
</div>
<div id="infiltration-container" class="generic-fullscreen-container"></div>
<div id="mission-container" class="generic-fullscreen-container"></div>
<!-- Work in progress screen -->
<div id="work-in-progress-container" class="generic-fullscreen-container">
<p id="work-in-progress-text"></p>
<button id="work-in-progress-cancel-button" class="work-button">Cancel Work</button>
<button id="work-in-progress-something-else-button" class="work-button">
Do something else simultaneously
</button>
</div>
<!-- Red Pill Container -->
<div id="red-pill-container" class="generic-fullscreen-container"></div>
<!-- Cinematic Text Container -->
<div id="cinematic-text-container" class="generic-fullscreen-container"></div>
<!-- Interactive Tutorial Text Screen -->
<div id="interactive-tutorial-wrapper">
<div id="interactive-tutorial-container">
@ -74,9 +57,6 @@
</div>
</div>
<!-- Character Overview Screen -->
<div id="character-overview"></div>
<!-- Status text -->
<div id="status-text-container">
<p id="status-text"></p>

19
src/ui/Context.ts Normal file
View File

@ -0,0 +1,19 @@
import React, { useContext } from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IRouter } from "./Router";
export const Context: {
Player: React.Context<IPlayer>;
Router: React.Context<IRouter>;
} = {
Player: React.createContext<IPlayer>({} as IPlayer),
Router: React.createContext<IRouter>({} as IRouter),
};
export const use: {
Player: () => IPlayer;
Router: () => IRouter;
} = {
Player: () => useContext(Context.Player),
Router: () => useContext(Context.Router),
};

View File

@ -7,11 +7,11 @@ import { installAugmentations } from "../Augmentation/AugmentationHelpers";
import { saveObject } from "../SaveObject";
import { onExport } from "../ExportBonus";
import { LocationName } from "../Locations/data/LocationNames";
import { CityName } from "../Locations/data/CityNames";
import { Faction } from "../Faction/Faction";
import { prestigeAugmentation } from "../Prestige";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { AllServers } from "../Server/AllServers";
import { Factions } from "../Faction/Factions";
import { buyStock, sellStock, shortStock, sellShort } from "../StockMarket/BuyingAndSelling";
import {
cancelOrder,
@ -29,12 +29,14 @@ import Typography from "@mui/material/Typography";
import { Page, IRouter } from "./Router";
import { SidebarRoot } from "../Sidebar/ui/SidebarRoot";
import { AugmentationsRoot } from "../Augmentation/ui/Root";
import { AugmentationsRoot } from "../Augmentation/ui/AugmentationsRoot";
import { DevMenuRoot } from "../DevMenu";
import { Root as BladeburnerRoot } from "../Bladeburner/ui/Root";
import { Root as GangRoot } from "../Gang/ui/Root";
import { BladeburnerRoot } from "../Bladeburner/ui/BladeburnerRoot";
import { GangRoot } from "../Gang/ui/GangRoot";
import { CorporationRoot } from "../Corporation/ui/CorporationRoot";
import { InfiltrationRoot } from "../Infiltration/ui/InfiltrationRoot";
import { ResleeveRoot } from "../PersonObjects/Resleeving/ui/ResleeveRoot";
import { WorkInProgressRoot } from "./WorkInProgressRoot";
import { GameOptionsRoot } from "../ui/React/GameOptionsRoot";
import { SleeveRoot } from "../PersonObjects/Sleeve/ui/SleeveRoot";
import { HacknetRoot } from "../Hacknet/ui/HacknetRoot";
@ -44,15 +46,20 @@ import { Root as ScriptEditorRoot } from "../ScriptEditor/ui/Root";
import { MilestonesRoot } from "../Milestones/ui/MilestonesRoot";
import { TerminalRoot } from "../Terminal/ui/TerminalRoot";
import { TutorialRoot } from "../Tutorial/ui/TutorialRoot";
import { ActiveScriptsRoot } from "../ui/ActiveScripts/Root";
import { ActiveScriptsRoot } from "../ui/ActiveScripts/ActiveScriptsRoot";
import { FactionsRoot } from "../Faction/ui/FactionsRoot";
import { FactionRoot } from "../Faction/ui/FactionRoot";
import { CharacterInfo } from "./CharacterInfo";
import { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot";
import { StockMarketRoot } from "../StockMarket/ui/StockMarketRoot";
import { BitverseRoot } from "../BitNode/ui/BitverseRoot";
import { CharacterOverview } from "./React/CharacterOverview";
import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
import { workerScripts } from "../Netscript/WorkerScripts";
import { startHackingMission } from "../Faction/FactionHelpers";
import { enterBitNode } from "../RedPill";
import { Context } from "./Context";
interface IProps {
terminal: ITerminal;
@ -65,6 +72,7 @@ const useStyles = makeStyles((theme: Theme) =>
root: {
"-ms-overflow-style": "none" /* for Internet Explorer, Edge */,
"scrollbar-width": "none" /* for Firefox */,
margin: theme.spacing(0),
},
}),
);
@ -72,13 +80,111 @@ const useStyles = makeStyles((theme: Theme) =>
let filename = "";
let code = "";
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
const contentRef = useRef<HTMLDivElement>(null);
const [faction, setFaction] = useState<Faction | null>(null);
const [page, setPage] = useState(Page.Terminal);
const classes = useStyles();
export let Router: IRouter = {
page: () => {
throw new Error("Router called before initialization");
},
toActiveScripts: () => {
throw new Error("Router called before initialization");
},
toAugmentations: () => {
throw new Error("Router called before initialization");
},
toBitVerse: () => {
throw new Error("Router called before initialization");
},
toBladeburner: () => {
throw new Error("Router called before initialization");
},
toCharacterInfo: () => {
throw new Error("Router called before initialization");
},
toCity: () => {
throw new Error("Router called before initialization");
},
toCorporation: () => {
throw new Error("Router called before initialization");
},
toCreateProgram: () => {
throw new Error("Router called before initialization");
},
toDevMenu: () => {
throw new Error("Router called before initialization");
},
toFaction: () => {
throw new Error("Router called before initialization");
},
toFactions: () => {
throw new Error("Router called before initialization");
},
toGameOptions: () => {
throw new Error("Router called before initialization");
},
toGang: () => {
throw new Error("Router called before initialization");
},
toHacknetNodes: () => {
throw new Error("Router called before initialization");
},
toInfiltration: () => {
throw new Error("Router called before initialization");
},
toJob: () => {
throw new Error("Router called before initialization");
},
toMilestones: () => {
throw new Error("Router called before initialization");
},
toResleeves: () => {
throw new Error("Router called before initialization");
},
toScriptEditor: () => {
throw new Error("Router called before initialization");
},
toSleeves: () => {
throw new Error("Router called before initialization");
},
toStockMarket: () => {
throw new Error("Router called before initialization");
},
toTerminal: () => {
throw new Error("Router called before initialization");
},
toTravel: () => {
throw new Error("Router called before initialization");
},
toTutorial: () => {
throw new Error("Router called before initialization");
},
toWork: () => {
throw new Error("Router called before initialization");
},
toBladeburnerCinematic: () => {
throw new Error("Router called before initialization");
},
};
const router = {
function determineStartPage(player: IPlayer): Page {
if (player.isWorking) return Page.Work;
return Page.Terminal;
}
export function GameRoot({ player, engine, terminal }: IProps): React.ReactElement {
const classes = useStyles();
const contentRef = useRef<HTMLDivElement>(null);
const [faction, setFaction] = useState<Faction | null>(
player.currentWorkFactionName ? Factions[player.currentWorkFactionName] : null,
);
const [page, setPage] = useState(determineStartPage(player));
const [flume, setFlume] = useState<boolean>(false);
const [quick, setQuick] = useState<boolean>(false);
const [location, setLocation] = useState<LocationName>(LocationName.Sector12);
const [cinematicText, setCinematicText] = useState("");
Router = {
page: () => page,
toActiveScripts: () => setPage(Page.ActiveScripts),
toAugmentations: () => setPage(Page.Augmentations),
toBladeburner: () => setPage(Page.Bladeburner),
@ -86,9 +192,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
toCorporation: () => setPage(Page.Corporation),
toCreateProgram: () => setPage(Page.CreateProgram),
toDevMenu: () => setPage(Page.DevMenu),
toFaction: (faction: Faction) => {
toFaction: (faction?: Faction) => {
setPage(Page.Faction);
setFaction(faction);
if (faction) setFaction(faction);
},
toFactions: () => setPage(Page.Factions),
toGameOptions: () => setPage(Page.Options),
@ -110,7 +216,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
setPage(Page.Job);
},
toCity: () => {
// TODO This is bad.
// TODO This conversion is bad.
player.gotoLocation(player.city as unknown as LocationName);
setPage(Page.City);
},
@ -118,6 +224,20 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
player.gotoLocation(LocationName.TravelAgency);
setPage(Page.Travel);
},
toBitVerse: (flume: boolean, quick: boolean) => {
setFlume(flume);
setQuick(quick);
setPage(Page.BitVerse);
},
toInfiltration: (location: LocationName) => {
setLocation(location);
setPage(Page.Infiltration);
},
toWork: () => setPage(Page.Work),
toBladeburnerCinematic: () => {
setPage(Page.BladeburnerCinematic);
setCinematicText(cinematicText);
},
};
useEffect(() => {
@ -126,94 +246,119 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
});
return (
<>
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot player={player} router={router} page={page} />
<Box ref={contentRef} className={classes.root} flexGrow={1} display="block" width="100%" px={1} height="100vh">
{page === Page.Terminal ? (
<TerminalRoot terminal={terminal} router={router} player={player} />
) : page === Page.Sleeves ? (
<SleeveRoot player={player} />
) : page === Page.Stats ? (
<CharacterInfo player={player} />
) : page === Page.CreateScript ? (
<ScriptEditorRoot filename={filename} code={code} player={player} router={router} />
) : page === Page.ActiveScripts ? (
<ActiveScriptsRoot p={player} workerScripts={workerScripts} />
) : page === Page.Hacknet ? (
<HacknetRoot player={player} />
) : page === Page.CreateProgram ? (
<ProgramsRoot player={player} />
) : page === Page.Factions ? (
<FactionsRoot player={player} router={router} />
) : page === Page.Faction ? (
<FactionRoot engine={engine} faction={faction} p={player} startHackingMissionFn={startHackingMission} />
) : page === Page.Milestones ? (
<MilestonesRoot player={player} />
) : page === Page.Tutorial ? (
<TutorialRoot />
) : page === Page.DevMenu ? (
<DevMenuRoot player={player} engine={engine} />
) : page === Page.Gang ? (
<GangRoot engine={engine} gang={player.gang} player={player} />
) : page === Page.Corporation ? (
<CorporationRoot corp={player.corporation} player={player} />
) : page === Page.Bladeburner ? (
<BladeburnerRoot bladeburner={player.bladeburner} player={player} engine={engine} />
) : page === Page.Resleeves ? (
<ResleeveRoot player={player} />
) : page === Page.Travel ? (
<TravelAgencyRoot p={player} router={router} />
) : page === Page.StockMarket ? (
<StockMarketRoot
buyStockLong={buyStock}
buyStockShort={shortStock}
cancelOrder={cancelOrder}
eventEmitterForReset={eventEmitterForUiReset}
initStockMarket={initStockMarketFnForReact}
p={player}
placeOrder={placeOrder}
sellStockLong={sellStock}
sellStockShort={sellShort}
stockMarket={StockMarket}
/>
) : page === Page.City ? (
<LocationRoot initiallyInCity={true} engine={engine} p={player} router={router} />
) : page === Page.Job ? (
<LocationRoot initiallyInCity={false} engine={engine} p={player} router={router} />
) : page === Page.Options ? (
<GameOptionsRoot
player={player}
save={() => saveObject.saveGame(engine.indexedDb)}
delete={() => saveObject.deleteGame(engine.indexedDb)}
export={() => saveObject.exportGame()}
import={() => saveObject.importGame()}
forceKill={() => {
for (const hostname of Object.keys(AllServers)) {
AllServers[hostname].runningScripts = [];
}
dialogBoxCreate("Forcefully deleted all running scripts. Please save and refresh page.");
}}
softReset={() => {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
}}
/>
) : page === Page.Augmentations ? (
<AugmentationsRoot
exportGameFn={() => {
saveObject.exportGame();
onExport(player);
}}
installAugmentationsFn={installAugmentations}
/>
) : (
<>
<Typography>Cannot load</Typography>
</>
)}
</Box>
</Box>
</>
<Context.Player.Provider value={player}>
<Context.Router.Provider value={Router}>
<CharacterOverview save={() => saveObject.saveGame(engine.indexedDb)} />
{page === Page.BitVerse ? (
<BitverseRoot flume={flume} enter={enterBitNode} quick={quick} />
) : page === Page.Infiltration ? (
<InfiltrationRoot location={location} />
) : page === Page.BladeburnerCinematic ? (
<BladeburnerCinematic />
) : page === Page.Work ? (
<WorkInProgressRoot />
) : (
<Box display="flex" flexDirection="row" width="100%">
<SidebarRoot player={player} router={Router} page={page} />
<Box
ref={contentRef}
className={classes.root}
flexGrow={1}
display="block"
width="100%"
px={1}
height="100vh"
>
{page === Page.Terminal ? (
<TerminalRoot terminal={terminal} router={Router} player={player} />
) : page === Page.Sleeves ? (
<SleeveRoot player={player} />
) : page === Page.Stats ? (
<CharacterInfo player={player} />
) : page === Page.CreateScript ? (
<ScriptEditorRoot filename={filename} code={code} player={player} router={Router} />
) : page === Page.ActiveScripts ? (
<ActiveScriptsRoot p={player} workerScripts={workerScripts} />
) : page === Page.Hacknet ? (
<HacknetRoot player={player} />
) : page === Page.CreateProgram ? (
<ProgramsRoot />
) : page === Page.Factions ? (
<FactionsRoot player={player} router={Router} />
) : page === Page.Faction ? (
<FactionRoot faction={faction} startHackingMissionFn={startHackingMission} />
) : page === Page.Milestones ? (
<MilestonesRoot player={player} />
) : page === Page.Tutorial ? (
<TutorialRoot />
) : page === Page.DevMenu ? (
<DevMenuRoot player={player} engine={engine} router={Router} />
) : page === Page.Gang ? (
<GangRoot gang={player.gang} />
) : page === Page.Corporation ? (
<CorporationRoot corp={player.corporation} player={player} />
) : page === Page.Bladeburner ? (
<BladeburnerRoot bladeburner={player.bladeburner} />
) : page === Page.Resleeves ? (
<ResleeveRoot player={player} />
) : page === Page.Travel ? (
<TravelAgencyRoot p={player} router={Router} />
) : page === Page.StockMarket ? (
<StockMarketRoot
buyStockLong={buyStock}
buyStockShort={shortStock}
cancelOrder={cancelOrder}
eventEmitterForReset={eventEmitterForUiReset}
initStockMarket={initStockMarketFnForReact}
p={player}
placeOrder={placeOrder}
sellStockLong={sellStock}
sellStockShort={sellShort}
stockMarket={StockMarket}
/>
) : page === Page.City ? (
<LocationRoot initiallyInCity={true} engine={engine} p={player} router={Router} />
) : page === Page.Job ? (
<LocationRoot initiallyInCity={false} engine={engine} p={player} router={Router} />
) : page === Page.Options ? (
<GameOptionsRoot
player={player}
save={() => saveObject.saveGame(engine.indexedDb)}
delete={() => saveObject.deleteGame(engine.indexedDb)}
export={() => saveObject.exportGame()}
import={() => saveObject.importGame()}
forceKill={() => {
for (const hostname of Object.keys(AllServers)) {
AllServers[hostname].runningScripts = [];
}
dialogBoxCreate("Forcefully deleted all running scripts. Please save and refresh page.");
}}
softReset={() => {
dialogBoxCreate("Soft Reset!");
prestigeAugmentation();
Router.toTerminal();
}}
/>
) : page === Page.Augmentations ? (
<AugmentationsRoot
exportGameFn={() => {
saveObject.exportGame();
onExport(player);
}}
installAugmentationsFn={() => {
installAugmentations();
Router.toTerminal();
}}
/>
) : (
<>
<Typography>Cannot load</Typography>
</>
)}
</Box>
</Box>
)}
</Context.Router.Provider>
</Context.Player.Provider>
);
}

View File

@ -2,20 +2,16 @@
import React, { useState, useEffect } from "react";
import makeStyles from "@mui/styles/makeStyles";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Reputation } from "./Reputation";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Divider from "@mui/material/Divider";
import Button from "@mui/material/Button";
import Collapse from "@mui/material/Collapse";
import Fab from "@mui/material/Fab";
@ -23,15 +19,17 @@ import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import { colors } from "./Theme";
import { Settings } from "../../Settings/Settings";
import { use } from "../../ui/Context";
import { Page } from "../../ui/Router";
interface IProps {
player: IPlayer;
save: () => void;
}
function Intelligence({ player }: { player: IPlayer }): React.ReactElement {
if (player.intelligence === 0) return <></>;
function Intelligence(): React.ReactElement {
const player = use.Player();
const classes = useStyles();
if (player.intelligence === 0) return <></>;
return (
<TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}>
@ -44,9 +42,11 @@ function Intelligence({ player }: { player: IPlayer }): React.ReactElement {
);
}
function Work({ player }: { player: IPlayer }): React.ReactElement {
if (!player.isWorking || player.focus) return <></>;
function Work(): React.ReactElement {
const player = use.Player();
const router = use.Router();
const classes = useStyles();
if (!player.isWorking || player.focus) return <></>;
return (
<>
<TableRow>
@ -61,7 +61,14 @@ function Work({ player }: { player: IPlayer }): React.ReactElement {
</TableRow>
<TableRow>
<TableCell component="th" scope="row" align="center" colSpan={2} classes={{ root: classes.cellNone }}>
<Button onClick={() => player.startFocusing()}>Focus</Button>
<Button
onClick={() => {
player.startFocusing();
router.toWork();
}}
>
Focus
</Button>
</TableCell>
</TableRow>
</>
@ -101,7 +108,12 @@ const useStyles = makeStyles({
},
});
export function CharacterOverview({ player, save }: IProps): React.ReactElement {
export function CharacterOverview({ save }: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
if (router.page() === Page.BitVerse) return <></>;
const setRerender = useState(false)[1];
const [open, setOpen] = useState(true);
@ -112,7 +124,7 @@ export function CharacterOverview({ player, save }: IProps): React.ReactElement
const classes = useStyles();
return (
<>
<div style={{ position: "fixed", top: 0, right: 0, zIndex: 1500 }}>
<Box display="flex" justifyContent="flex-end" flexDirection={"column"}>
<Collapse in={open}>
<Paper square>
@ -205,8 +217,8 @@ export function CharacterOverview({ player, save }: IProps): React.ReactElement
</Typography>
</TableCell>
</TableRow>
<Intelligence player={player} />
<Work player={player} />
<Intelligence />
<Work />
<TableRow>
<TableCell align="center" colSpan={2} classes={{ root: classes.cellNone }}>
@ -226,6 +238,6 @@ export function CharacterOverview({ player, save }: IProps): React.ReactElement
</Fab>
</Box>
</Box>
</>
</div>
);
}

View File

@ -4,24 +4,34 @@ import { CinematicLine } from "./CinematicLine";
interface IProps {
lines: string[];
auto?: boolean;
onDone?: () => void;
}
export function CinematicText(props: IProps): React.ReactElement {
const [i, setI] = useState(0);
const [done, setDone] = useState(false);
function advance(): void {
const newI = i + 1;
setI(newI);
if (newI >= props.lines.length && props.onDone) props.onDone();
if (newI >= props.lines.length) {
if (props.onDone && props.auto) props.onDone();
setDone(true);
}
}
return (
<>
<div>
{props.lines.slice(0, i).map((line, i) => (
<pre key={i}>{line}</pre>
))}
{props.lines.length > i && <CinematicLine key={i} text={props.lines[i]} onDone={advance} />}
</>
{!props.auto && props.onDone && done && (
<button className="std-button" onClick={props.onDone}>
Continue ...
</button>
)}
</div>
);
}

View File

@ -28,12 +28,9 @@ const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: 50,
padding: 2,
padding: theme.spacing(2),
userSelect: "none",
},
pad: {
padding: 2,
},
}),
);

View File

@ -1,5 +1,5 @@
import React from "react";
import { createTheme, ThemeProvider, Theme, StyledEngineProvider, adaptV4Theme } from "@mui/material/styles";
import { createTheme, ThemeProvider, Theme, StyledEngineProvider } from "@mui/material/styles";
declare module "@mui/styles/defaultTheme" {
// eslint-disable-next-line @typescript-eslint/no-empty-interface

View File

@ -1,4 +1,5 @@
import { Faction } from "../Faction/Faction";
import { LocationName } from "../Locations/data/LocationNames";
/**
* The full-screen page the player is currently be on.
* These pages are mutually exclusive.
@ -6,26 +7,30 @@ import { Faction } from "../Faction/Faction";
export enum Page {
ActiveScripts,
Augmentations,
BitVerse,
Bladeburner,
Stats,
City,
Corporation,
CreateProgram,
CreateScript,
DevMenu,
Faction,
Factions,
Options,
Gang,
Hacknet,
Infiltration,
Job,
Milestones,
Options,
Resleeves,
CreateScript,
Sleeves,
Stats,
StockMarket,
Terminal,
Travel,
Tutorial,
Work,
BladeburnerCinematic,
}
/**
@ -37,19 +42,22 @@ export interface IRouter {
// toMission(): void;
// toRedPill(): void;
// toworkInProgress(): void;
page(): Page;
toActiveScripts(): void;
toAugmentations(): void;
toBitVerse(flume: boolean, quick: boolean): void;
toBladeburner(): void;
toCharacterInfo(): void;
toCity(): void; // travel ? city ?
toCorporation(): void;
toCreateProgram(): void;
toDevMenu(): void;
toFaction(faction: Faction): void; // faction name
toFaction(faction?: Faction): void; // faction name
toFactions(): void;
toGameOptions(): void;
toGang(): void;
toHacknetNodes(): void;
toCity(): void; // travel ? city ?
toInfiltration(location: LocationName): void;
toJob(): void;
toMilestones(): void;
toResleeves(): void;
@ -59,4 +67,6 @@ export interface IRouter {
toTerminal(): void;
toTravel(): void;
toTutorial(): void;
toWork(): void;
toBladeburnerCinematic(): void;
}

View File

@ -0,0 +1,336 @@
import React, { useState, useEffect } from "react";
import { use } from "./Context";
import { CONSTANTS } from "../Constants";
import { numeralWrapper } from "./numeralFormat";
import { Reputation } from "./React/Reputation";
import { ReputationRate } from "./React/ReputationRate";
import { MoneyRate } from "./React/MoneyRate";
import { Money } from "./React/Money";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { Factions } from "../Faction/Factions";
import { Company } from "../Company/Company";
import { Companies } from "../Company/Companies";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
export function WorkInProgressRoot(): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, CONSTANTS.MilliPerCycle);
return () => clearInterval(id);
}, []);
const player = use.Player();
const router = use.Router();
const faction = Factions[player.currentWorkFactionName];
if (player.workType == CONSTANTS.WorkTypeFaction) {
function cancel(): void {
router.toFaction();
player.finishFactionWork(true);
}
function unfocus(): void {
router.toFaction();
player.stopFocusing();
}
return (
<div>
<p>
You are currently {player.currentWorkFactionDescription} for your faction {faction.name}
<br />
(Current Faction Reputation: {Reputation(faction.playerReputation)}). <br />
You have been doing this for {convertTimeMsToTimeElapsedString(player.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={player.workMoneyGained} /> ({MoneyRate(player.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(player.workRepGained)} ({ReputationRate(player.workRepGainRate * CYCLES_PER_SEC)}) reputation for
this faction <br />
<br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
<br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
{numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
{numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
{numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
<br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
<br />
You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
<br />
There is no penalty for cancelling earlier.
</p>
<button onClick={cancel} className="work-button">
Stop Faction Work
</button>
<button onClick={unfocus} className="work-button">
Do something else simultaneously
</button>
</div>
);
}
const className = player.className;
if (player.className !== "") {
function cancel(): void {
player.finishClass(true);
router.toCity();
}
function unfocus(): void {
player.stopFocusing();
router.toCity();
}
let stopText = "";
if (
className == CONSTANTS.ClassGymStrength ||
className == CONSTANTS.ClassGymDefense ||
className == CONSTANTS.ClassGymDexterity ||
className == CONSTANTS.ClassGymAgility
) {
stopText = "Stop training at gym";
} else {
stopText = "Stop taking course";
}
return (
<div>
<p>
You have been {className} for {convertTimeMsToTimeElapsedString(player.timeWorked)}
<br />
<br />
This has cost you: <br />
<Money money={-player.workMoneyGained} /> ({MoneyRate(player.workMoneyLossRate * CYCLES_PER_SEC)}) <br />
<br />
You have gained: <br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp <br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
{numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
{numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
{numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp <br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp <br />
You may cancel at any time
</p>
<button onClick={cancel} className="work-button">
{stopText}
</button>
<button onClick={unfocus} className="work-button">
Do something else simultaneously
</button>
</div>
);
}
if (player.workType == CONSTANTS.WorkTypeCompany) {
const comp = Companies[player.companyName];
let companyRep = 0;
if (comp == null || !(comp instanceof Company)) {
throw new Error(`Could not find Company: ${player.companyName}`);
}
companyRep = comp.playerReputation;
function cancel(): void {
player.finishWork(true);
router.toJob();
}
function unfocus(): void {
player.stopFocusing();
router.toJob();
}
const position = player.jobs[player.companyName];
const penalty = player.cancelationPenalty();
const penaltyString = penalty === 0.5 ? "half" : "three-quarters";
return (
<div>
<p>
You are currently working as a {position} at {player.companyName} (Current Company Reputation:{" "}
{Reputation(companyRep)})<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={player.workMoneyGained} /> ({MoneyRate(player.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(player.workRepGained)} ({ReputationRate(player.workRepGainRate * CYCLES_PER_SEC)}) reputation for
this company <br />
<br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
<br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{`${numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
{`${numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
{`${numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
{`${numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
<br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{`${numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, but you will only
gain {penaltyString} of the reputation you've earned so far.
</p>
<button onClick={cancel} className="work-button">
Stop Working
</button>
<button onClick={unfocus} className="work-button">
Do something else simultaneously
</button>
</div>
);
}
if (player.workType == CONSTANTS.WorkTypeCompanyPartTime) {
function cancel(): void {
player.finishWork(true);
router.toJob();
}
function unfocus(): void {
player.stopFocusing();
router.toJob();
}
const comp = Companies[player.companyName];
let companyRep = 0;
if (comp == null || !(comp instanceof Company)) {
throw new Error(`Could not find Company: ${player.companyName}`);
}
companyRep = comp.playerReputation;
const position = player.jobs[player.companyName];
return (
<div>
<p>
You are currently working as a {position} at {player.companyName} (Current Company Reputation:{" "}
{Reputation(companyRep)})<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
<br />
<br />
You have earned: <br />
<br />
<Money money={player.workMoneyGained} /> ({MoneyRate(player.workMoneyGainRate * CYCLES_PER_SEC)}) <br />
<br />
{Reputation(player.workRepGained)} (
{Reputation(`${numeralWrapper.formatExp(player.workRepGainRate * CYCLES_PER_SEC)} / sec`)}
) reputation for this company <br />
<br />
{numeralWrapper.formatExp(player.workHackExpGained)} (
{`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}
) hacking exp <br />
<br />
{numeralWrapper.formatExp(player.workStrExpGained)} (
{`${numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}
) strength exp <br />
{numeralWrapper.formatExp(player.workDefExpGained)} (
{`${numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}
) defense exp <br />
{numeralWrapper.formatExp(player.workDexExpGained)} (
{`${numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}
) dexterity exp <br />
{numeralWrapper.formatExp(player.workAgiExpGained)} (
{`${numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}
) agility exp <br />
<br />
{numeralWrapper.formatExp(player.workChaExpGained)} (
{`${numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}
) charisma exp <br />
<br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, and there will be
no penalty because this is a part-time job.
</p>
<button onClick={cancel} className="work-button">
Stop Working
</button>
<button onClick={unfocus} className="work-button">
Do something else simultaneously
</button>
</div>
);
}
if (player.crimeType !== "") {
const percent = Math.round((player.timeWorked / player.timeNeededToCompleteWork) * 100);
let numBars = Math.round(percent / 5);
if (numBars < 0) {
numBars = 0;
}
if (numBars > 20) {
numBars = 20;
}
// const progressBar = "[" + Array(numBars + 1).join("|") + Array(20 - numBars + 1).join(" ") + "]";
const progressBar = createProgressBarText({ progress: (numBars + 1) / 20, totalTicks: 20 });
return (
<div>
<p>You are attempting to {player.crimeType}.</p>
<br />
<p>Time remaining: {convertTimeMsToTimeElapsedString(player.timeNeededToCompleteWork - player.timeWorked)}</p>
<br />
<pre>{progressBar}</pre>
<button className="work-button" onClick={() => player.finishCrime(true)}>
Cancel crime
</button>
</div>
);
}
if (player.createProgramName !== "") {
return (
<div>
<p>
You are currently working on coding {player.createProgramName}.<br />
<br />
You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}
<br />
<br />
The program is {((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}
% complete. <br />
If you cancel, your work will be saved and you can come back to complete the program later.
</p>
<button className="work-button" onClick={() => player.finishCreateProgramWork(true)}>
Cancel work on creating program
</button>
</div>
);
}
setTimeout(() => router.toCity(), 50);
return <></>;
}