diff --git a/css/activescripts.scss b/css/activescripts.scss index 35e959558..f5f7593ba 100644 --- a/css/activescripts.scss +++ b/css/activescripts.scss @@ -6,7 +6,6 @@ .active-scripts-container { > p { - width: 70%; margin: 6px; padding: 4px; } diff --git a/css/augmentations.scss b/css/augmentations.scss index 2615aa4d6..53193ae1e 100644 --- a/css/augmentations.scss +++ b/css/augmentations.scss @@ -8,7 +8,6 @@ .augmentations-content { > p { font-size: $defaultFontSize * 0.875; - width: 70%; } } diff --git a/css/hacknetnodes.scss b/css/hacknetnodes.scss index f06b0b79a..97bf22122 100644 --- a/css/hacknetnodes.scss +++ b/css/hacknetnodes.scss @@ -7,7 +7,6 @@ .hacknet-general-info { margin: 10px; - width: 70vw; } #hacknet-nodes-container li { diff --git a/src/Augmentation/AugmentationHelpers.d.ts b/src/Augmentation/AugmentationHelpers.d.ts index 9e2bb7935..c20c69364 100644 --- a/src/Augmentation/AugmentationHelpers.d.ts +++ b/src/Augmentation/AugmentationHelpers.d.ts @@ -1 +1,2 @@ export declare function isRepeatableAug(aug: Augmentation): boolean; +export declare function installAugmentations(): void; diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/AugmentationsRoot.tsx similarity index 100% rename from src/Augmentation/ui/Root.tsx rename to src/Augmentation/ui/AugmentationsRoot.tsx diff --git a/src/BitNode/ui/BitFlumePopup.tsx b/src/BitNode/ui/BitFlumePopup.tsx index c8b9ffc4c..a915f356b 100644 --- a/src/BitNode/ui/BitFlumePopup.tsx +++ b/src/BitNode/ui/BitFlumePopup.tsx @@ -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 ( diff --git a/src/BitNode/ui/BitverseRoot.tsx b/src/BitNode/ui/BitverseRoot.tsx index 1e6c2c1da..ecdd16fb7 100644 --- a/src/BitNode/ui/BitverseRoot.tsx +++ b/src/BitNode/ui/BitverseRoot.tsx @@ -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 {
 \| O   |  |_/    |\|   \ O    \__|    \_|  |   O |/ 
  | |   |_/       | |    \|    /  |       \_|   | |  
   \|   /          \|     |   /  /          \   |/   
-
    |              |     |  /  |              |    
-
   |  |            |     |     |            |  |   
+
    |              |     |  /  |              |    
+
   |  |            |     |     |            |  |   
  | |  |            /    / \    \            |  | |  
-
   \|  |           /   /   \   \           |  |/   
+
   \|  |           /   /   \   \           |  |/   
    \  |          /  / |     | \  \          |  /    
-
     \ \JUMP 3R |  |  |     |  |  | R3 PMUJ/ /     
+
     \ \JUMP 3R |  |  |     |  |  | R3 PMUJ/ /     
      \||    |   |  |  |     |  |  |   |    ||/      
       \|     \_ |  |  |     |  |  | _/     |/       
        \       \| /    \   /    \ |/       /        
-
                |/     | |     \|                
+
                |/     | |     \|                
         |       |    |  | |  |    |       |         
          \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/          

diff --git a/src/BitNode/ui/PortalPopup.tsx b/src/BitNode/ui/PortalPopup.tsx index 600d632ff..dbcbfb3cf 100644 --- a/src/BitNode/ui/PortalPopup.tsx +++ b/src/BitNode/ui/PortalPopup.tsx @@ -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 { diff --git a/src/Faction/FactionHelpers.d.ts b/src/Faction/FactionHelpers.d.ts index be7642c9a..8d719b231 100644 --- a/src/Faction/FactionHelpers.d.ts +++ b/src/Faction/FactionHelpers.d.ts @@ -4,5 +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; diff --git a/src/Faction/FactionHelpers.jsx b/src/Faction/FactionHelpers.jsx index 772444402..5459bff8c 100644 --- a/src/Faction/FactionHelpers.jsx +++ b/src/Faction/FactionHelpers.jsx @@ -1,14 +1,9 @@ -import React from "react"; -import ReactDOM from "react-dom"; - -import { FactionRoot } from "./ui/Root"; - 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( - , - 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. " + diff --git a/src/Faction/ui/AugmentationsPage.tsx b/src/Faction/ui/AugmentationsPage.tsx index 6a406d51f..063ebfacb 100644 --- a/src/Faction/ui/AugmentationsPage.tsx +++ b/src/Faction/ui/AugmentationsPage.tsx @@ -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 { +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 { 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 { 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 { 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 ; + }; - const purchaseableAugmentation = (aug: string): React.ReactNode => { - return ( - - ); - }; + 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 = ( - <> -
-

Purchased Augmentations

-

This factions also offers these augmentations but you already own them.

- {owned.map((aug) => purchaseableAugmentation(aug))} - - ); - } - - return ( -
- -

Faction Augmentations

-

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

- this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} /> - this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)} - text={"Sort by Reputation"} - /> - 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 = ( + <>
- {augListElems} - {ownedElem} -
-
-
-
-
-
-
-
-
-
+

Purchased Augmentations

+

This factions also offers these augmentations but you already own them.

+ {owned.map((aug) => purchaseableAugmentation(aug))} + ); } + + return ( +
+ +

Faction Augmentations

+

+ These are all of the Augmentations that are available to purchase from {props.faction.name}. Augmentations are + powerful upgrades that will enhance your abilities. +

+ switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} text={"Sort by Cost"} /> + switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)} + text={"Sort by Reputation"} + /> + switchSortOrder(PurchaseAugmentationsOrderSetting.Default)} + text={"Sort by Default Order"} + /> +
+ {augListElems} + {ownedElem} +
+
+
+
+
+
+
+
+
+
+ ); } diff --git a/src/Faction/ui/CreateGangPopup.tsx b/src/Faction/ui/CreateGangPopup.tsx index 5423d6348..50e14ad2e 100644 --- a/src/Faction/ui/CreateGangPopup.tsx +++ b/src/Faction/ui/CreateGangPopup.tsx @@ -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}? -
-
- 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. -
-
- { (isHacking()) ? hackingGangText : combatGangText } -
-
- 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. -
- -
- - ); - } \ No newline at end of file + return ( + <> + Would you like to create a new Gang with {props.facName}? +
+
+ 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. +
+
+ {isHacking() ? hackingGangText : combatGangText} +
+
+ 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. +
+ +
+ + ); +} diff --git a/src/Faction/ui/Root.tsx b/src/Faction/ui/FactionRoot.tsx similarity index 61% rename from src/Faction/ui/Root.tsx rename to src/Faction/ui/FactionRoot.tsx index 694fa9059..b4cf1b7d9 100644 --- a/src/Faction/ui/Root.tsx +++ b/src/Faction/ui/FactionRoot.tsx @@ -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; - initiallyOnAugmentationsPage?: boolean; - faction: Faction; - p: IPlayer; + faction: Faction | null; startHackingMissionFn: (faction: Faction) => void; }; -type IState = { - rerenderFlag: boolean; - purchasingAugs: boolean; -}; - // 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,89 +63,68 @@ const GangNames = [ "The Black Hand", ]; -export class FactionRoot extends React.Component { - constructor(props: IProps) { - 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: props.initiallyOnAugmentationsPage ? props.initiallyOnAugmentationsPage : false, - }; + 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.props.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.props.faction); + function startFieldWork(faction: Faction): void { + player.startFactionFieldWork(faction); + router.toWork(); } - startHackingContracts(): void { - this.props.p.startFactionHackWork(this.props.faction); + function startHackingContracts(faction: Faction): void { + player.startFactionHackWork(faction); + router.toWork(); } - startHackingMission(): void { - const fac = this.props.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.props.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.props.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 @@ -181,49 +151,51 @@ export class FactionRoot extends React.Component {

{faction.name}

- {canAccessGang &&
); } - renderAugmentationsPage(): React.ReactNode { - return ( - <> - - - ); - } + return purchasingAugs ? ( + + ) : ( + + ); } diff --git a/src/Faction/ui/FactionList.tsx b/src/Faction/ui/FactionsRoot.tsx similarity index 81% rename from src/Faction/ui/FactionList.tsx rename to src/Faction/ui/FactionsRoot.tsx index 358771ed2..fbed0ae67 100644 --- a/src/Faction/ui/FactionList.tsx +++ b/src/Faction/ui/FactionsRoot.tsx @@ -1,20 +1,20 @@ import React, { useState } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; -import { IEngine } from "../../IEngine"; +import { IRouter } from "../../ui/Router"; import { Factions } from "../Factions"; -import { displayFactionContent, joinFaction } from "../FactionHelpers"; +import { Faction } from "../Faction"; +import { joinFaction } from "../FactionHelpers"; interface IProps { player: IPlayer; - engine: IEngine; + router: IRouter; } -export function FactionList(props: IProps): React.ReactElement { +export function FactionsRoot(props: IProps): React.ReactElement { const setRerender = useState(false)[1]; - function openFaction(faction: string): void { - props.engine.loadFactionContent(); - displayFactionContent(faction); + function openFaction(faction: Faction): void { + props.router.toFaction(faction); } function acceptInvitation(event: React.MouseEvent, faction: string): void { @@ -33,7 +33,7 @@ export function FactionList(props: IProps): React.ReactElement {
  • openFaction(faction)} + onClick={() => openFaction(Factions[faction])} style={{ padding: "4px", margin: "4px", display: "inline-block" }} > {faction} diff --git a/src/Faction/ui/PurchaseAugmentationPopup.tsx b/src/Faction/ui/PurchaseAugmentationPopup.tsx index a4053b480..8b253a500 100644 --- a/src/Faction/ui/PurchaseAugmentationPopup.tsx +++ b/src/Faction/ui/PurchaseAugmentationPopup.tsx @@ -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); } diff --git a/src/Faction/ui/PurchaseableAugmentation.tsx b/src/Faction/ui/PurchaseableAugmentation.tsx index 9dfe4bc79..ad38a5b3d 100644 --- a/src/Faction/ui/PurchaseableAugmentation.tsx +++ b/src/Faction/ui/PurchaseableAugmentation.tsx @@ -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 { - 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 { 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) - - - ); - } else { - disabled = true; - status = ( - <> - LOCKED (Requires {Reputation(repCost)} faction reputation - ) - - ); - color = "red"; - } - - const txtStyle: IMap = { - 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 = ( - <> - -
    -
    - {this.aug.stats} - - ); - else - tooltip = ( - <> - {this.aug.info} -
    -
    - {this.aug.stats} - - ); - - return ( -
  • - - -

    {status}

    -
    -
  • + 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) - + + ); + } else { + disabled = true; + status = ( + <> + LOCKED (Requires {Reputation(repCost)} faction reputation - ) + + ); + color = "red"; + } + + const txtStyle: IMap = { + 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 = ( + <> + +
    +
    + {aug.stats} + + ); + else + tooltip = ( + <> + {aug.info} +
    +
    + {aug.stats} + + ); + + return ( +
  • + + +

    {status}

    +
    +
  • + ); } diff --git a/src/Gang/IGang.ts b/src/Gang/IGang.ts index b99f52f1d..00d7911cc 100644 --- a/src/Gang/IGang.ts +++ b/src/Gang/IGang.ts @@ -2,6 +2,7 @@ import { GangMemberUpgrade } from "./GangMemberUpgrade"; import { GangMember } from "./GangMember"; import { WorkerScript } from "../Netscript/WorkerScript"; import { IPlayer } from "../PersonObjects/IPlayer"; +import { IAscensionResult } from "./IAscensionResult"; export interface IGang { facName: string; @@ -37,8 +38,9 @@ export interface IGang { getWantedPenalty(): number; calculatePower(): number; killMember(member: GangMember): void; - ascendMember(member: GangMember, workerScript: WorkerScript): void; + ascendMember(member: GangMember, workerScript: WorkerScript): IAscensionResult; getDiscount(): number; getAllTaskNames(): string[]; getUpgradeCost(upg: GangMemberUpgrade): number; + toJSON(): any; } diff --git a/src/Gang/ui/Root.tsx b/src/Gang/ui/GangRoot.tsx similarity index 69% rename from src/Gang/ui/Root.tsx rename to src/Gang/ui/GangRoot.tsx index 3d53c9277..d746d7626 100644 --- a/src/Gang/ui/Root.tsx +++ b/src/Gang/ui/GangRoot.tsx @@ -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
    - {management ? ( - - ) : ( - - )} + {management ? : } ); } diff --git a/src/Infiltration/Helper.tsx b/src/Infiltration/Helper.tsx deleted file mode 100644 index a15fae91e..000000000 --- a/src/Infiltration/Helper.tsx +++ /dev/null @@ -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( - , - container, - ); -} diff --git a/src/Infiltration/ui/Game.tsx b/src/Infiltration/ui/Game.tsx index 59386f9d1..378d3f9e4 100644 --- a/src/Infiltration/ui/Game.tsx +++ b/src/Infiltration/ui/Game.tsx @@ -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 = ( 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 ( + setStart(true)} + cancel={cancel} + /> + ); + } + + return ( + + ); +} diff --git a/src/Infiltration/ui/Intro.tsx b/src/Infiltration/ui/Intro.tsx index 6c089a607..a7201c350 100644 --- a/src/Infiltration/ui/Intro.tsx +++ b/src/Infiltration/ui/Intro.tsx @@ -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; diff --git a/src/Infiltration/ui/Root.tsx b/src/Infiltration/ui/Root.tsx deleted file mode 100644 index 67631fe0f..000000000 --- a/src/Infiltration/ui/Root.tsx +++ /dev/null @@ -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 ( - setStart(true)} - cancel={cancel} - /> - ); - } - - return ( - - ); -} diff --git a/src/Infiltration/ui/Victory.tsx b/src/Infiltration/ui/Victory.tsx index 9702c63c4..6ee057a57 100644 --- a/src/Infiltration/ui/Victory.tsx +++ b/src/Infiltration/ui/Victory.tsx @@ -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 { - {props.Player.factions + {player.factions .filter((f) => Factions[f].getInfo().offersWork()) .map((f) => (