From 2d374093922c0f0a19fff26df41f9f4e036605ab Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 14 May 2019 20:56:59 -0700 Subject: [PATCH 1/9] Refactored SourceFile-related code to TypeScript --- css/augmentations.scss | 27 ++ css/menupages.scss | 46 ---- src/Augmentation/AugmentationHelpers.js | 12 - .../ui/InstalledAugmentations.tsx | 40 +++ .../InstalledAugmentationsAndSourceFiles.tsx | 56 ++++ src/Augmentation/ui/ListConfiguration.tsx | 5 + src/Augmentation/ui/OwnedSourceFiles.tsx | 40 +++ .../ui/PurchasedAugmentations.tsx | 30 ++ src/Augmentation/ui/Root.tsx | 72 +++++ src/PersonObjects/IPlayer.ts | 4 + .../Player/PlayerObjectGeneralMethods.js | 3 +- src/RedPill.js | 5 +- src/SourceFile.js | 256 ------------------ src/SourceFile/SourceFile.ts | 21 ++ src/SourceFile/SourceFiles.ts | 64 +++++ src/SourceFile/applySourceFile.ts | 176 ++++++++++++ src/engine.jsx | 11 +- src/engineStyle.js | 1 + src/ui/React/AugmentationAccordion.tsx | 33 +++ src/ui/React/Popup.tsx | 14 +- src/ui/React/StdButton.tsx | 53 ++-- 21 files changed, 608 insertions(+), 361 deletions(-) create mode 100644 css/augmentations.scss create mode 100644 src/Augmentation/ui/InstalledAugmentations.tsx create mode 100644 src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx create mode 100644 src/Augmentation/ui/ListConfiguration.tsx create mode 100644 src/Augmentation/ui/OwnedSourceFiles.tsx create mode 100644 src/Augmentation/ui/PurchasedAugmentations.tsx create mode 100644 src/Augmentation/ui/Root.tsx delete mode 100644 src/SourceFile.js create mode 100644 src/SourceFile/SourceFile.ts create mode 100644 src/SourceFile/SourceFiles.ts create mode 100644 src/SourceFile/applySourceFile.ts create mode 100644 src/ui/React/AugmentationAccordion.tsx diff --git a/css/augmentations.scss b/css/augmentations.scss new file mode 100644 index 000000000..eb7fd3722 --- /dev/null +++ b/css/augmentations.scss @@ -0,0 +1,27 @@ +/** + * Styling for the Augmentations UI. This is the page that displays all of the + * player's owned and purchased Augmentations and Source-Files. It also allows + * the player to install Augmentations + */ +@import "theme"; + +#augmentations-container { + position: fixed; + padding-top: 10px; +} + +.augmentations-list { + button, + div { + color: var(--my-font-color); + text-decoration: none; + } + + button { + padding: 2px 5px; + } + + div { + padding: 6px; + } +} diff --git a/css/menupages.scss b/css/menupages.scss index 0c8e55033..b0b45c723 100644 --- a/css/menupages.scss +++ b/css/menupages.scss @@ -185,19 +185,6 @@ width: 70%; } -#faction-donate-amount-txt, -#faction-donate-input { - padding: 6px; - margin: 6px; - display: inline-block; - color: var(--my-font-color); - background-color: #000; -} - -#faction-donate-amount-txt { - width: 50%; -} - #faction-container p, #faction-container pre { padding: 4px 6px; @@ -213,45 +200,12 @@ word-wrap: break-word; /* Internet Explorer 5.5+ */ } -/* Faction Augmentations */ -#faction-augmentations-container { - position: fixed; - padding-top: 10px; - - p, a, ul, h1 { - margin: 8px; - padding: 4px; - } -} - /* World */ #world-container li { margin: 0 0 15px 0; list-style-type: none; } -/* Augmentations */ -#augmentations-container { - position: fixed; - padding-top: 10px; -} - -.augmentations-list { - button, - div { - color: var(--my-font-color); - text-decoration: none; - } - - button { - padding: 2px 5px; - } - - div { - padding: 6px; - } -} - /* Tutorial */ #tutorial-container { position: fixed; diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index 7110b70c8..9c6809344 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -17,7 +17,6 @@ import { Server } from "../Server/Server"; import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; import { Settings } from "../Settings/Settings"; -import { SourceFiles } from "../SourceFile"; import { dialogBoxCreate } from "../../utils/DialogBox"; import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement"; import { Reviver, Generic_toJSON, @@ -2041,17 +2040,6 @@ function applyAugmentation(aug, reapply=false) { } } - /* - if (aug.name === AugmentationNames.NeuroFluxGovernor) { - for (var i = 0; i < Player.augmentations.length; ++i) { - if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) { - //Already have this aug, just upgrade the level - return; - } - } - } - */ - // Push onto Player's Augmentation list if (!reapply) { var ownedAug = new PlayerOwnedAugmentation(aug.name); diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx new file mode 100644 index 000000000..84d8cb2ea --- /dev/null +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -0,0 +1,40 @@ +/** + * React Component for displaying a list of the player's installed Augmentations + * on the Augmentations UI + */ +import * as React from "react"; + +import { Player } from "../../Player"; +import { Augmentations } from "../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { Settings } from "../../Settings/Settings"; +import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; + +import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; + +export function InstalledAugmentations(): React.ReactElement { + const sourceAugs = Player.augmentations.slice(); + + if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { + sourceAugs.sort((aug1, aug2) => { + return aug1.name <= aug2.name ? -1 : 1; + }); + } + + const augs = sourceAugs.map((e) => { + const aug = Augmentations[e.name]; + + let level = null; + if (e.name === AugmentationNames.NeuroFluxGovernor) { + level = e.level; + } + + return ( + + ) + }); + + return ( + + ) +} diff --git a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx new file mode 100644 index 000000000..39bac22e6 --- /dev/null +++ b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx @@ -0,0 +1,56 @@ +/** + * React Component for displaying all of the player's installed Augmentations and + * Source-Files. + * + * It also contains 'configuration' buttons that allow you to change how the + * Augs/SF's are displayed + */ +import * as React from "react"; + +import { Settings } from "../../Settings/Settings"; +import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; + +type IProps = { + +} + +type IState = { + rerenderFlag: boolean; +} + +export class InstalledAugmentationsAndSourceFiles extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + rerenderFlag: false, + } + + this.sortByAcquirementTime = this.sortByAcquirementTime.bind(this); + this.sortInOrder = this.sortInOrder.bind(this); + } + + rerender() { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + } + }); + } + + sortByAcquirementTime() { + Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime; + this.rerender(); + } + + sortInOrder() { + Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically + this.rerender(); + } + + render() { + return ( + + ) + } +} diff --git a/src/Augmentation/ui/ListConfiguration.tsx b/src/Augmentation/ui/ListConfiguration.tsx new file mode 100644 index 000000000..ef1368b8f --- /dev/null +++ b/src/Augmentation/ui/ListConfiguration.tsx @@ -0,0 +1,5 @@ +/** + * React Component for configuring the way installed augmentations and + * Source-Files are displayed in the Augmentations UI + */ +import * as React from "react"; diff --git a/src/Augmentation/ui/OwnedSourceFiles.tsx b/src/Augmentation/ui/OwnedSourceFiles.tsx new file mode 100644 index 000000000..24107bcdf --- /dev/null +++ b/src/Augmentation/ui/OwnedSourceFiles.tsx @@ -0,0 +1,40 @@ +/** + * React Component for displaying a list of the player's Source-Files + * on the Augmentations UI + */ +import * as React from "react"; + +import { Player } from "../../Player"; +import { Augmentations } from "../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { Settings } from "../../Settings/Settings"; +import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; + +import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; + +export function OwnedSourceFiles(): React.ReactElement { + const sourceAugs = Player.augmentations.slice(); + + if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { + sourceAugs.sort((aug1, aug2) => { + return aug1.name <= aug2.name ? -1 : 1; + }); + } + + const augs = sourceAugs.map((e) => { + const aug = Augmentations[e.name]; + + let level = null; + if (e.name === AugmentationNames.NeuroFluxGovernor) { + level = e.level; + } + + return ( + + ) + }); + + return ( +
    {augs}
+ ) +} diff --git a/src/Augmentation/ui/PurchasedAugmentations.tsx b/src/Augmentation/ui/PurchasedAugmentations.tsx new file mode 100644 index 000000000..bd966dfe5 --- /dev/null +++ b/src/Augmentation/ui/PurchasedAugmentations.tsx @@ -0,0 +1,30 @@ +/** + * React component for displaying all of the player's purchased (but not installed) + * Augmentations on the Augmentations UI. + */ +import * as React from "react"; + +import { Augmentations } from "../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { Player } from "../../Player"; + +import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; + +export function PurchasedAugmentations(): React.ReactElement { + const augs: React.ReactElement[] = []; + for (const ownedAug of Player.queuedAugmentations) { + const aug = Augmentations[ownedAug.name]; + let level = null; + if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { + level = ownedAug.level; + } + + augs.push( + + ) + } + + return ( +
    {augs}
+ ) +} diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/Root.tsx new file mode 100644 index 000000000..565267f31 --- /dev/null +++ b/src/Augmentation/ui/Root.tsx @@ -0,0 +1,72 @@ +/** + * Root React component for the Augmentations UI page that display all of your + * owned and purchased Augmentations and Source-Files. + */ +import * as React from "react"; + +import { Augmentations } from "../../Augmentation/Augmentations"; +import { Player } from "../../Player"; + +import { StdButton } from "../../ui/React/StdButton"; + +type IProps = { + exportGameFn: () => void; + installAugmentationsFn: () => void; +} + +type IState = { + +} + +export class AugmentationsRoot extends React.Component { + constructor(props: IProps) { + super(props); + } + + render() { + return ( +
+

Purchased Augmentations

+

+ Below is a list of all Augmentations you have purchased but not + yet installed. Click the button below to install them. +

+

+ WARNING: Installing your Augmentations resets most of your progress, + including: +

+

- Stats/Skill levels and Experience

+

- Money

+

- Scripts on every computer but your home computer

+

- Purchased servers

+

- Hacknet Nodes

+

- Faction/Company reputation

+

- Stocks

+

+ Installing Augmentations lets you start over with the perks and + benefits granted by all of the Augmentations you have ever + installed. Also, you will keep any scripts and RAM/Core upgrades + on your home computer (but you will lose all programs besides + NUKE.exe) +

+ + + + + +
    + +
+
+ ) + } +} diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index be95132c8..09146f514 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -101,6 +101,10 @@ export interface IPlayer { work_money_mult: number; crime_success_mult: number; crime_money_mult: number; + bladeburner_max_stamina_mult: number; + bladeburner_stamina_gain_mult: number; + bladeburner_analysis_mult: number; + bladeburner_success_chance_mult: number; // Methods applyForAgentJob(sing?: boolean): boolean | void; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js index c263175d5..385c836cf 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js @@ -36,7 +36,8 @@ import { import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers"; import { Settings } from "../../Settings/Settings"; import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps"; -import { SourceFiles, applySourceFile } from "../../SourceFile"; +import { applySourceFile } from "../../SourceFile/applySourceFile"; +import { SourceFiles } from "../../SourceFile/SourceFiles"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; import Decimal from "decimal.js"; diff --git a/src/RedPill.js b/src/RedPill.js index 23a56ccfe..c4cebac46 100644 --- a/src/RedPill.js +++ b/src/RedPill.js @@ -5,7 +5,7 @@ import { BitNodes } from "./BitNode/BitNode"; import { Engine } from "./engine"; import { Player } from "./Player"; import { prestigeSourceFile } from "./Prestige"; -import { SourceFiles, SourceFile } from "./SourceFile"; +import { SourceFiles } from "./SourceFile/SourceFiles"; import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile"; import { Terminal } from "./Terminal"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; @@ -20,9 +20,6 @@ import { import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; - - - // Returns promise function writeRedPillLine(line) { return new Promise(function(resolve, reject) { diff --git a/src/SourceFile.js b/src/SourceFile.js deleted file mode 100644 index 7c9219562..000000000 --- a/src/SourceFile.js +++ /dev/null @@ -1,256 +0,0 @@ -import { Player } from "./Player"; -import { BitNodes } from "./BitNode/BitNode"; - -// Each SourceFile corresponds to a BitNode with the same number -function SourceFile(number, info="") { - var bitnodeKey = "BitNode" + number; - var bitnode = BitNodes[bitnodeKey]; - if (bitnode == null) { - throw new Error("Invalid Bit Node for this Source File"); - } - - this.n = number; - this.name = "Source-File " + number + ": " + bitnode.name; - this.lvl = 1; - this.info = info; - this.owned = false; -} - -let SourceFiles = {}; -function initSourceFiles() { - SourceFiles = {}; - SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " + - "home computer. It also increases all of the player's multipliers by:

" + - "Level 1: 16%
" + - "Level 2: 24%
" + - "Level 3: 28%"); - SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " + - "once your karma decreases to a certain value. It also increases the player's " + - "crime success rate, crime money, and charisma multipliers by:

" + - "Level 1: 24%
" + - "Level 2: 36%
" + - "Level 3: 42%"); - SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " + - "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
" + - "Level 1: 8%
" + - "Level 2: 12%
" + - "Level 3: 14%"); - SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " + - "level of this Source-File opens up more of the Singularity Functions you can use."); - SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " + - "is unique because it is permanent and persistent (it never gets reset back to 1). However, " + - "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " + - "know when you gain experience and how much). Higher Intelligence levels will boost your production " + - "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " + - "Netscript function, and will raise all of your hacking-related multipliers by:

" + - "Level 1: 8%
" + - "Level 2: 12%
" + - "Level 3: 14%"); - SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " + - "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

" + - "Level 1: 8%
" + - "Level 2: 12%
" + - "Level 3: 14%"); - SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " + - "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

" + - "Level 1: 8%
" + - "Level 2: 12%
" + - "Level 3: 14%"); - SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:

" + - "Level 1: Permanent access to WSE and TIX API
" + - "Level 2: Ability to short stocks in other BitNodes
" + - "Level 3: Ability to use limit/stop orders in other BitNodes

" + - "This Source-File also increases your hacking growth multipliers by: " + - "
Level 1: 12%
Level 2: 18%
Level 3: 21%"); - SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:

" + - "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
" + - "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
" + - "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

" + - "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + - "when installing Augmentations)"); - SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + - "Source-File also grants you a Duplicate Sleeve"); - SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + - "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + - " increases the player's company salary and reputation gain multipliers by:

" + - "Level 1: 32%
" + - "Level 2: 48%
" + - "Level 3: 56%
"); - SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " + - "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); -} - -// Takes in a PlayerOwnedSourceFile as the "srcFile" argument -function applySourceFile(srcFile) { - var srcFileKey = "SourceFile" + srcFile.n; - var sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.log("ERROR: Invalid source file number: " + srcFile.n); - return; - } - - switch(srcFile.n) { - case 1: // The Source Genesis - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (16 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - var decMult = 1 - (mult / 100); - Player.hacking_chance_mult *= incMult; - Player.hacking_speed_mult *= incMult; - Player.hacking_money_mult *= incMult; - Player.hacking_grow_mult *= incMult; - Player.hacking_mult *= incMult; - Player.strength_mult *= incMult; - Player.defense_mult *= incMult; - Player.dexterity_mult *= incMult; - Player.agility_mult *= incMult; - Player.charisma_mult *= incMult; - Player.hacking_exp_mult *= incMult; - Player.strength_exp_mult *= incMult; - Player.defense_exp_mult *= incMult; - Player.dexterity_exp_mult *= incMult; - Player.agility_exp_mult *= incMult; - Player.charisma_exp_mult *= incMult; - Player.company_rep_mult *= incMult; - Player.faction_rep_mult *= incMult; - Player.crime_money_mult *= incMult; - Player.crime_success_mult *= incMult; - Player.hacknet_node_money_mult *= incMult; - Player.hacknet_node_purchase_cost_mult *= decMult; - Player.hacknet_node_ram_cost_mult *= decMult; - Player.hacknet_node_core_cost_mult *= decMult; - Player.hacknet_node_level_cost_mult *= decMult; - Player.work_money_mult *= incMult; - break; - case 2: // Rise of the Underworld - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (24 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.crime_money_mult *= incMult; - Player.crime_success_mult *= incMult; - Player.charisma_mult *= incMult; - break; - case 3: // Corporatocracy - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.charisma_mult *= incMult; - Player.work_money_mult *= incMult; - break; - case 4: // The Singularity - // No effects, just gives access to Singularity functions - break; - case 5: // Artificial Intelligence - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.hacking_chance_mult *= incMult; - Player.hacking_speed_mult *= incMult; - Player.hacking_money_mult *= incMult; - Player.hacking_grow_mult *= incMult; - Player.hacking_mult *= incMult; - Player.hacking_exp_mult *= incMult; - break; - case 6: // Bladeburner - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.strength_exp_mult *= incMult; - Player.defense_exp_mult *= incMult; - Player.dexterity_exp_mult *= incMult; - Player.agility_exp_mult *= incMult; - Player.strength_mult *= incMult; - Player.defense_mult *= incMult; - Player.dexterity_mult *= incMult; - Player.agility_mult *= incMult; - break; - case 7: // Bladeburner 2079 - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.bladeburner_max_stamina_mult *= incMult; - Player.bladeburner_stamina_gain_mult *= incMult; - Player.bladeburner_analysis_mult *= incMult; - Player.bladeburner_success_chance_mult *= incMult; - break; - case 8: // Ghost of Wall Street - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (12 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.hacking_grow_mult *= incMult; - break; - case 9: // Hacktocracy - // This has non-multiplier effects - break; - case 10: // Digital Carbon - // No effects, just grants sleeves - break; - case 11: // The Big Crash - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (32 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.work_money_mult *= incMult; - Player.company_rep_mult *= incMult; - break; - case 12: // The Recursion - var inc = Math.pow(1.01, srcFile.lvl); - var dec = Math.pow(0.99, srcFile.lvl); - - Player.hacking_chance_mult *= inc; - Player.hacking_speed_mult *= inc; - Player.hacking_money_mult *= inc; - Player.hacking_grow_mult *= inc; - Player.hacking_mult *= inc; - - Player.strength_mult *= inc; - Player.defense_mult *= inc; - Player.dexterity_mult *= inc; - Player.agility_mult *= inc; - Player.charisma_mult *= inc; - - Player.hacking_exp_mult *= inc; - Player.strength_exp_mult *= inc; - Player.defense_exp_mult *= inc; - Player.dexterity_exp_mult *= inc; - Player.agility_exp_mult *= inc; - Player.charisma_exp_mult *= inc; - - Player.company_rep_mult *= inc; - Player.faction_rep_mult *= inc; - - Player.crime_money_mult *= inc; - Player.crime_success_mult *= inc; - - Player.hacknet_node_money_mult *= inc; - Player.hacknet_node_purchase_cost_mult *= dec; - Player.hacknet_node_ram_cost_mult *= dec; - Player.hacknet_node_core_cost_mult *= dec; - Player.hacknet_node_level_cost_mult *= dec; - - Player.work_money_mult *= inc; - break; - default: - console.log("ERROR: Invalid source file number: " + srcFile.n); - break; - } - - sourceFileObject.owned = true; -} - -export {SourceFiles, applySourceFile, initSourceFiles}; diff --git a/src/SourceFile/SourceFile.ts b/src/SourceFile/SourceFile.ts new file mode 100644 index 000000000..96290507a --- /dev/null +++ b/src/SourceFile/SourceFile.ts @@ -0,0 +1,21 @@ +import { BitNodes } from "../BitNode/BitNode"; + +export class SourceFile { + info: string; + lvl: number = 1; + n: number; + name: string; + owned: boolean = false; + + constructor(number: number, info: string="") { + const bitnodeKey = "BitNode" + number; + const bitnode = BitNodes[bitnodeKey]; + if (bitnode == null) { + throw new Error("Invalid Bit Node for this Source File"); + } + + this.n = number; + this.name = `Source-File ${number}: ${bitnode.name}` + this.info = info; + } +} diff --git a/src/SourceFile/SourceFiles.ts b/src/SourceFile/SourceFiles.ts new file mode 100644 index 000000000..bc35dcba8 --- /dev/null +++ b/src/SourceFile/SourceFiles.ts @@ -0,0 +1,64 @@ +import { SourceFile } from "./SourceFile"; +import { IMap } from "../types"; + +export const SourceFiles: IMap = {}; + +SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " + + "home computer. It also increases all of the player's multipliers by:

" + + "Level 1: 16%
" + + "Level 2: 24%
" + + "Level 3: 28%"); +SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " + + "once your karma decreases to a certain value. It also increases the player's " + + "crime success rate, crime money, and charisma multipliers by:

" + + "Level 1: 24%
" + + "Level 2: 36%
" + + "Level 3: 42%"); +SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " + + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
" + + "Level 1: 8%
" + + "Level 2: 12%
" + + "Level 3: 14%"); +SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " + + "level of this Source-File opens up more of the Singularity Functions you can use."); +SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " + + "is unique because it is permanent and persistent (it never gets reset back to 1). However, " + + "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " + + "know when you gain experience and how much). Higher Intelligence levels will boost your production " + + "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " + + "Netscript function, and will raise all of your hacking-related multipliers by:

" + + "Level 1: 8%
" + + "Level 2: 12%
" + + "Level 3: 14%"); +SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " + + "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

" + + "Level 1: 8%
" + + "Level 2: 12%
" + + "Level 3: 14%"); +SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " + + "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

" + + "Level 1: 8%
" + + "Level 2: 12%
" + + "Level 3: 14%"); +SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:

" + + "Level 1: Permanent access to WSE and TIX API
" + + "Level 2: Ability to short stocks in other BitNodes
" + + "Level 3: Ability to use limit/stop orders in other BitNodes

" + + "This Source-File also increases your hacking growth multipliers by: " + + "
Level 1: 12%
Level 2: 18%
Level 3: 21%"); +SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:

" + + "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
" + + "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
" + + "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

" + + "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + + "when installing Augmentations)"); +SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + + "Source-File also grants you a Duplicate Sleeve"); +SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + + "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + + " increases the player's company salary and reputation gain multipliers by:

" + + "Level 1: 32%
" + + "Level 2: 48%
" + + "Level 3: 56%
"); +SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " + + "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); diff --git a/src/SourceFile/applySourceFile.ts b/src/SourceFile/applySourceFile.ts new file mode 100644 index 000000000..e38b014ec --- /dev/null +++ b/src/SourceFile/applySourceFile.ts @@ -0,0 +1,176 @@ +import { PlayerOwnedSourceFile } from "./PlayerOwnedSourceFile"; +import { SourceFiles } from "./SourceFiles"; + +import { Player } from "../Player"; + +export function applySourceFile(srcFile: PlayerOwnedSourceFile) { + const srcFileKey = "SourceFile" + srcFile.n; + const sourceFileObject = SourceFiles[srcFileKey]; + if (sourceFileObject == null) { + console.error(`Invalid source file number: ${srcFile.n}`); + return; + } + + switch (srcFile.n) { + case 1: // The Source Genesis + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (16 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + var decMult = 1 - (mult / 100); + Player.hacking_chance_mult *= incMult; + Player.hacking_speed_mult *= incMult; + Player.hacking_money_mult *= incMult; + Player.hacking_grow_mult *= incMult; + Player.hacking_mult *= incMult; + Player.strength_mult *= incMult; + Player.defense_mult *= incMult; + Player.dexterity_mult *= incMult; + Player.agility_mult *= incMult; + Player.charisma_mult *= incMult; + Player.hacking_exp_mult *= incMult; + Player.strength_exp_mult *= incMult; + Player.defense_exp_mult *= incMult; + Player.dexterity_exp_mult *= incMult; + Player.agility_exp_mult *= incMult; + Player.charisma_exp_mult *= incMult; + Player.company_rep_mult *= incMult; + Player.faction_rep_mult *= incMult; + Player.crime_money_mult *= incMult; + Player.crime_success_mult *= incMult; + Player.hacknet_node_money_mult *= incMult; + Player.hacknet_node_purchase_cost_mult *= decMult; + Player.hacknet_node_ram_cost_mult *= decMult; + Player.hacknet_node_core_cost_mult *= decMult; + Player.hacknet_node_level_cost_mult *= decMult; + Player.work_money_mult *= incMult; + break; + case 2: // Rise of the Underworld + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (24 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.crime_money_mult *= incMult; + Player.crime_success_mult *= incMult; + Player.charisma_mult *= incMult; + break; + case 3: // Corporatocracy + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.charisma_mult *= incMult; + Player.work_money_mult *= incMult; + break; + case 4: // The Singularity + // No effects, just gives access to Singularity functions + break; + case 5: // Artificial Intelligence + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.hacking_chance_mult *= incMult; + Player.hacking_speed_mult *= incMult; + Player.hacking_money_mult *= incMult; + Player.hacking_grow_mult *= incMult; + Player.hacking_mult *= incMult; + Player.hacking_exp_mult *= incMult; + break; + case 6: // Bladeburner + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.strength_exp_mult *= incMult; + Player.defense_exp_mult *= incMult; + Player.dexterity_exp_mult *= incMult; + Player.agility_exp_mult *= incMult; + Player.strength_mult *= incMult; + Player.defense_mult *= incMult; + Player.dexterity_mult *= incMult; + Player.agility_mult *= incMult; + break; + case 7: // Bladeburner 2079 + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.bladeburner_max_stamina_mult *= incMult; + Player.bladeburner_stamina_gain_mult *= incMult; + Player.bladeburner_analysis_mult *= incMult; + Player.bladeburner_success_chance_mult *= incMult; + break; + case 8: // Ghost of Wall Street + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (12 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.hacking_grow_mult *= incMult; + break; + case 9: // Hacktocracy + // This has non-multiplier effects + break; + case 10: // Digital Carbon + // No effects, just grants sleeves + break; + case 11: // The Big Crash + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (32 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.work_money_mult *= incMult; + Player.company_rep_mult *= incMult; + break; + case 12: // The Recursion + var inc = Math.pow(1.01, srcFile.lvl); + var dec = Math.pow(0.99, srcFile.lvl); + + Player.hacking_chance_mult *= inc; + Player.hacking_speed_mult *= inc; + Player.hacking_money_mult *= inc; + Player.hacking_grow_mult *= inc; + Player.hacking_mult *= inc; + + Player.strength_mult *= inc; + Player.defense_mult *= inc; + Player.dexterity_mult *= inc; + Player.agility_mult *= inc; + Player.charisma_mult *= inc; + + Player.hacking_exp_mult *= inc; + Player.strength_exp_mult *= inc; + Player.defense_exp_mult *= inc; + Player.dexterity_exp_mult *= inc; + Player.agility_exp_mult *= inc; + Player.charisma_exp_mult *= inc; + + Player.company_rep_mult *= inc; + Player.faction_rep_mult *= inc; + + Player.crime_money_mult *= inc; + Player.crime_success_mult *= inc; + + Player.hacknet_node_money_mult *= inc; + Player.hacknet_node_purchase_cost_mult *= dec; + Player.hacknet_node_ram_cost_mult *= dec; + Player.hacknet_node_core_cost_mult *= dec; + Player.hacknet_node_level_cost_mult *= dec; + + Player.work_money_mult *= inc; + break; + default: + console.log("ERROR: Invalid source file number: " + srcFile.n); + break; + } + + sourceFileObject.owned = true; +} diff --git a/src/engine.jsx b/src/engine.jsx index e86bbcdfb..488c21eb6 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -13,7 +13,6 @@ import { PlayerOwnedAugmentation } from "./Augmentation/AugmentationHelpers"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; - import { BitNodes, initBitNodes, @@ -54,14 +53,14 @@ import { updateOnlineScriptTimes, } from "./NetscriptWorker"; import { Player } from "./Player"; -import { prestigeAugmentation, prestigeSourceFile } from "./Prestige"; +import { prestigeAugmentation } from "./Prestige"; import { Programs } from "./Programs/Programs"; import { displayCreateProgramContent, getNumAvailableCreateProgram, initCreateProgramButtons } from "./Programs/ProgramHelpers"; -import { redPillFlag, hackWorldDaemon } from "./RedPill"; +import { redPillFlag } from "./RedPill"; import { saveObject, loadGame } from "./SaveObject"; import { getCurrentEditor, @@ -69,10 +68,7 @@ import { updateScriptEditorContent } from "./Script/ScriptHelpers"; import { AllServers, initForeignServers } from "./Server/AllServers"; - -import { Server } from "./Server/Server"; import { Settings } from "./Settings/Settings"; -import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SpecialServerIps, @@ -87,7 +83,6 @@ import { displayStockMarketContent } from "./StockMarket/StockMarket"; import { Terminal, postNetburnerText } from "./Terminal"; - import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; import { clearSleevesPage, @@ -1045,7 +1040,6 @@ const Engine = { if (loadGame(saveString)) { initBitNodes(); initBitNodeMultipliers(Player); - initSourceFiles(); Engine.setDisplayElements(); // Sets variables for important DOM elements Engine.init(); // Initialize buttons, work, etc. initAugmentations(); // Also calls Player.reapplyAllAugmentations() @@ -1168,7 +1162,6 @@ const Engine = { console.log("Initializing new game"); initBitNodes(); initBitNodeMultipliers(Player); - initSourceFiles(); initSpecialServerIps(); Engine.setDisplayElements(); // Sets variables for important DOM elements Engine.start(); // Run main game loop and Scripts loop diff --git a/src/engineStyle.js b/src/engineStyle.js index df9dd7250..c297b37cc 100644 --- a/src/engineStyle.js +++ b/src/engineStyle.js @@ -11,6 +11,7 @@ import "../css/scripteditor.scss"; import "../css/codemirror-overrides.scss"; import "../css/hacknetnodes.scss"; import "../css/menupages.scss"; +import "../css/augmentations.scss"; import "../css/redpill.scss"; import "../css/stockmarket.scss"; import "../css/workinprogress.scss"; diff --git a/src/ui/React/AugmentationAccordion.tsx b/src/ui/React/AugmentationAccordion.tsx new file mode 100644 index 000000000..430a9800c --- /dev/null +++ b/src/ui/React/AugmentationAccordion.tsx @@ -0,0 +1,33 @@ +/** + * React Component for displaying a single Augmentation as an accordion. + * + * The header of the accordion contains the Augmentation's name (and level, if + * applicable), and the accordion's panel contains the Augmentation's level. + */ +import * as React from "react"; + +import { Accordion } from "./Accordion"; + +import { Augmentation } from "../../Augmentation/Augmentation"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; + +type IProps = { + aug: Augmentation, + level?: number | string | null, +} + +export function AugmentationAccordion(props: IProps): React.ReactElement { + let displayName = props.aug.name; + if (props.level != null) { + if (props.aug.name === AugmentationNames.NeuroFluxGovernor) { + displayName += (` - Level ${props.level}`) + } + } + + return ( + {displayName}

} + panelContent={

{props.aug.info}

} + /> + ) +} diff --git a/src/ui/React/Popup.tsx b/src/ui/React/Popup.tsx index 2d84ad684..c296809bb 100644 --- a/src/ui/React/Popup.tsx +++ b/src/ui/React/Popup.tsx @@ -13,12 +13,10 @@ interface IProps { props: object; } -export class Popup extends React.Component { - render() { - return ( -
- {React.createElement(this.props.content, this.props.props)} -
- ) - } +export function Popup(props: IProps): React.ReactElement { + return ( +
+ {React.createElement(props.content, props.props)} +
+ ) } diff --git a/src/ui/React/StdButton.tsx b/src/ui/React/StdButton.tsx index d8bd957f4..89227f83b 100644 --- a/src/ui/React/StdButton.tsx +++ b/src/ui/React/StdButton.tsx @@ -5,6 +5,7 @@ import * as React from "react"; interface IStdButtonProps { + addClasses?: string; disabled?: boolean; id?: string; onClick?: (e: React.MouseEvent) => any; @@ -17,30 +18,32 @@ type IInnerHTMLMarkup = { __html: string; } -export class StdButton extends React.Component { - render() { - const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; - let className = this.props.disabled ? "std-button-disabled" : "std-button"; - if (hasTooltip) { - className += " tooltip"; - } - - // Tooltip will be set using inner HTML - let tooltipMarkup: IInnerHTMLMarkup | null; - if (hasTooltip) { - tooltipMarkup = { - __html: this.props.tooltip! - } - } - - return ( - - ) +export function StdButton(props: IStdButtonProps): React.ReactElement { + const hasTooltip = props.tooltip != null && props.tooltip !== ""; + let className = props.disabled ? "std-button-disabled" : "std-button"; + if (hasTooltip) { + className += " tooltip"; } + + if (typeof props.addClasses === "string") { + className += ` ${props.addClasses}`; + } + + // Tooltip will be set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup | null; + if (hasTooltip) { + tooltipMarkup = { + __html: props.tooltip! + } + } + + return ( + + ) } From b744997c72ddcbf1cd552a7895d42dbc953b4013 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 15 May 2019 00:15:07 -0700 Subject: [PATCH 2/9] Finished refactoring augmentations page UI to use react --- css/augmentations.scss | 12 +- ...tionHelpers.js => AugmentationHelpers.jsx} | 282 ++--------- .../ui/InstalledAugmentations.tsx | 6 +- .../InstalledAugmentationsAndSourceFiles.tsx | 59 ++- src/Augmentation/ui/ListConfiguration.tsx | 34 ++ src/Augmentation/ui/OwnedSourceFiles.tsx | 31 +- src/Augmentation/ui/PlayerMultipliers.tsx | 96 ++++ .../ui/PurchasedAugmentations.tsx | 4 +- src/Augmentation/ui/Root.tsx | 25 +- src/BitNode/BitNode.ts | 453 +++++++++--------- src/engine.jsx | 14 +- src/ui/React/Accordion.tsx | 12 +- src/ui/React/AugmentationAccordion.tsx | 6 +- src/ui/React/SourceFileAccordion.tsx | 35 ++ 14 files changed, 561 insertions(+), 508 deletions(-) rename src/Augmentation/{AugmentationHelpers.js => AugmentationHelpers.jsx} (89%) create mode 100644 src/Augmentation/ui/PlayerMultipliers.tsx create mode 100644 src/ui/React/SourceFileAccordion.tsx diff --git a/css/augmentations.scss b/css/augmentations.scss index eb7fd3722..36b39a4e2 100644 --- a/css/augmentations.scss +++ b/css/augmentations.scss @@ -10,6 +10,13 @@ padding-top: 10px; } +#augmentations-content { + > p { + font-size: $defaultFontSize * 0.875; + width: 70%; + } +} + .augmentations-list { button, div { @@ -18,10 +25,7 @@ } button { - padding: 2px 5px; + padding: 4px; } - div { - padding: 6px; - } } diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.jsx similarity index 89% rename from src/Augmentation/AugmentationHelpers.js rename to src/Augmentation/AugmentationHelpers.jsx index 9c6809344..fa4d57f6f 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.jsx @@ -1,32 +1,39 @@ -import { Augmentation } from "./Augmentation"; -import { Augmentations } from "./Augmentations"; -import { PlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; -import { AugmentationNames } from "./data/AugmentationNames"; +import { Augmentation } from "./Augmentation"; +import { Augmentations } from "./Augmentations"; +import { PlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; +import { AugmentationNames } from "./data/AugmentationNames"; -import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; -import { CONSTANTS } from "../Constants"; -import { Factions, - factionExists } from "../Faction/Factions"; -import { addWorkerScript } from "../NetscriptWorker"; -import { Player } from "../Player"; -import { prestigeAugmentation } from "../Prestige"; -import { saveObject } from "../SaveObject"; -import { RunningScript } from "../Script/RunningScript"; -import { Script } from "../Script/Script"; -import { Server } from "../Server/Server"; -import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; -import { Settings } from "../Settings/Settings"; +import { AugmentationsRoot } from "./ui/Root"; -import { dialogBoxCreate } from "../../utils/DialogBox"; -import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement"; -import { Reviver, Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; -import { formatNumber } from "../../utils/StringHelperFunctions"; -import { clearObject } from "../../utils/helpers/clearObject"; -import { createElement } from "../../utils/uiHelpers/createElement"; -import { isString } from "../../utils/helpers/isString"; -import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { Factions, factionExists } from "../Faction/Factions"; +import { addWorkerScript } from "../NetscriptWorker"; +import { Player } from "../Player"; +import { prestigeAugmentation } from "../Prestige"; +import { saveObject } from "../SaveObject"; +import { RunningScript } from "../Script/RunningScript"; +import { Script } from "../Script/Script"; +import { Server } from "../Server/Server"; +import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; +import { Settings } from "../Settings/Settings"; +import { Page, routing } from "../ui/navigationTracking"; +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement"; +import { + Reviver, + Generic_toJSON, + Generic_fromJSON +} from "../../utils/JSONReviver"; +import { formatNumber } from "../../utils/StringHelperFunctions"; +import { clearObject } from "../../utils/helpers/clearObject"; +import { createElement } from "../../utils/uiHelpers/createElement"; +import { isString } from "../../utils/helpers/isString"; +import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; + +import React from "react"; +import ReactDOM from "react-dom"; function AddToAugmentations(aug) { var name = aug.name; @@ -2092,211 +2099,17 @@ function augmentationExists(name) { return Augmentations.hasOwnProperty(name); } -function displayAugmentationsContent(contentEl) { - removeChildrenFromElement(contentEl); - contentEl.appendChild(createElement("h1", { - innerText:"Purchased Augmentations", - })); +export function displayAugmentationsContent(contentEl) { + if (!routing.isOn(Page.Augmentations)) { return; } + if (!(contentEl instanceof HTMLElement)) { return; } - contentEl.appendChild(createElement("pre", { - width:"70%", whiteSpace:"pre-wrap", display:"block", - innerText:"Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to install them.\n" + - "WARNING: Installing your Augmentations resets most of your progress, including:\n\n" + - "Stats/Skill levels and Experience\n" + - "Money\n" + - "Scripts on every computer but your home computer\n" + - "Purchased servers\n" + - "Hacknet Nodes\n" + - "Faction/Company reputation\n" + - "Stocks\n" + - "Installing Augmentations lets you start over with the perks and benefits granted by all " + - "of the Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades " + - "on your home computer (but you will lose all programs besides NUKE.exe)." - })); - - //Install Augmentations button - contentEl.appendChild(createElement("a", { - class:"a-link-button", innerText:"Install Augmentations", - tooltip:"'I never asked for this'", - clickListener:()=>{ - installAugmentations(); - return false; - } - })); - - //Backup button - contentEl.appendChild(createElement("a", { - class:"a-link-button flashing-button", innerText:"Backup Save (Export)", - tooltip:"It's always a good idea to backup/export your save!", - clickListener:()=>{ - saveObject.exportGame(); - return false; - } - })); - - //Purchased/queued augmentations list - var queuedAugmentationsList = createElement("ul", {class:"augmentations-list"}); - - for (var i = 0; i < Player.queuedAugmentations.length; ++i) { - var augName = Player.queuedAugmentations[i].name; - var aug = Augmentations[augName]; - - var displayName = augName; - if (augName === AugmentationNames.NeuroFluxGovernor) { - displayName += " - Level " + (Player.queuedAugmentations[i].level); - } - - var accordion = createAccordionElement({hdrText:displayName, panelText:aug.info}); - queuedAugmentationsList.appendChild(accordion[0]); - } - contentEl.appendChild(queuedAugmentationsList); - - //Installed augmentations list - contentEl.appendChild(createElement("h1", { - innerText:"Installed Augmentations", marginTop:"8px", - })); - contentEl.appendChild(createElement("p", { - width:"70%", whiteSpace:"pre-wrap", - innerText:"List of all Augmentations (including Source Files) that have been " + - "installed. You have gained the effects of these Augmentations." - })); - - var augmentationsList = createElement("ul", {class:"augmentations-list"}); - - //Expand/Collapse All buttons - contentEl.appendChild(createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Expand All", display:"inline-block", - clickListener:()=>{ - var allHeaders = augmentationsList.getElementsByClassName("accordion-header"); - for (var i = 0; i < allHeaders.length; ++i) { - if (!allHeaders[i].classList.contains("active")) {allHeaders[i].click();} - } - } - })); - contentEl.appendChild(createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Collapse All", display:"inline-block", - clickListener:()=>{ - var allHeaders = augmentationsList.getElementsByClassName("accordion-header"); - for (var i = 0; i < allHeaders.length; ++i) { - if (allHeaders[i].classList.contains("active")) {allHeaders[i].click();} - } - } - })); - - //Sort Buttons - const sortInOrderButton = createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Sort in Order", - tooltip:"Sorts the Augmentations alphabetically and Source Files in numerical order (1, 2, 3,...)", - clickListener:()=>{ - removeChildrenFromElement(augmentationsList); - - //Create a copy of Player's Source Files and augs array and sort them - var sourceFiles = Player.sourceFiles.slice(); - var augs = Player.augmentations.slice(); - sourceFiles.sort((sf1, sf2)=>{ - return sf1.n - sf2.n; - }); - augs.sort((aug1, aug2)=>{ - return aug1.name <= aug2.name ? -1 : 1; - }); - displaySourceFiles(augmentationsList, sourceFiles); - displayAugmentations(augmentationsList, augs); - - Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically; - } - }); - contentEl.appendChild(sortInOrderButton); - - const sortByAcquirementTimeButton = createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Sort by Acquirement Time", - tooltip:"Sorts the Augmentations and Source Files based on when you acquired them (same as default)", - clickListener:()=>{ - removeChildrenFromElement(augmentationsList); - displaySourceFiles(augmentationsList, Player.sourceFiles); - displayAugmentations(augmentationsList, Player.augmentations); - - Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime; - } - }); - contentEl.appendChild(sortByAcquirementTimeButton); - - if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sortInOrderButton.click(); - } else { - sortByAcquirementTimeButton.click(); - } - contentEl.appendChild(augmentationsList); - - // Display multiplier information at the bottom - contentEl.appendChild(createElement("p", { - display: "block", - innerHTML: - `

Total Multipliers:
` + - 'Hacking Chance multiplier: ' + formatNumber(Player.hacking_chance_mult * 100, 2) + '%
' + - 'Hacking Speed multiplier: ' + formatNumber(Player.hacking_speed_mult * 100, 2) + '%
' + - 'Hacking Money multiplier: ' + formatNumber(Player.hacking_money_mult * 100, 2) + '%
' + - 'Hacking Growth multiplier: ' + formatNumber(Player.hacking_grow_mult * 100, 2) + '%

' + - 'Hacking Level multiplier: ' + formatNumber(Player.hacking_mult * 100, 2) + '%
' + - 'Hacking Experience multiplier: ' + formatNumber(Player.hacking_exp_mult * 100, 2) + '%

' + - 'Strength Level multiplier: ' + formatNumber(Player.strength_mult * 100, 2) + '%
' + - 'Strength Experience multiplier: ' + formatNumber(Player.strength_exp_mult * 100, 2) + '%

' + - 'Defense Level multiplier: ' + formatNumber(Player.defense_mult * 100, 2) + '%
' + - 'Defense Experience multiplier: ' + formatNumber(Player.defense_exp_mult * 100, 2) + '%

' + - 'Dexterity Level multiplier: ' + formatNumber(Player.dexterity_mult * 100, 2) + '%
' + - 'Dexterity Experience multiplier: ' + formatNumber(Player.dexterity_exp_mult * 100, 2) + '%

' + - 'Agility Level multiplier: ' + formatNumber(Player.agility_mult * 100, 2) + '%
' + - 'Agility Experience multiplier: ' + formatNumber(Player.agility_exp_mult * 100, 2) + '%

' + - 'Charisma Level multiplier: ' + formatNumber(Player.charisma_mult * 100, 2) + '%
' + - 'Charisma Experience multiplier: ' + formatNumber(Player.charisma_exp_mult * 100, 2) + '%

' + - 'Hacknet Node production multiplier: ' + formatNumber(Player.hacknet_node_money_mult * 100, 2) + '%
' + - 'Hacknet Node purchase cost multiplier: ' + formatNumber(Player.hacknet_node_purchase_cost_mult * 100, 2) + '%
' + - 'Hacknet Node RAM upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_ram_cost_mult * 100, 2) + '%
' + - 'Hacknet Node Core purchase cost multiplier: ' + formatNumber(Player.hacknet_node_core_cost_mult * 100, 2) + '%
' + - 'Hacknet Node level upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_level_cost_mult * 100, 2) + '%

' + - 'Company reputation gain multiplier: ' + formatNumber(Player.company_rep_mult * 100, 2) + '%
' + - 'Faction reputation gain multiplier: ' + formatNumber(Player.faction_rep_mult * 100, 2) + '%
' + - 'Salary multiplier: ' + formatNumber(Player.work_money_mult * 100, 2) + '%
' + - 'Crime success multiplier: ' + formatNumber(Player.crime_success_mult * 100, 2) + '%
' + - 'Crime money multiplier: ' + formatNumber(Player.crime_money_mult * 100, 2) + '%


', - })) -} - -//Creates the accordion elements to display Augmentations -// @listElement - List DOM element to append accordion elements to -// @augs - Array of Augmentation objects -function displayAugmentations(listElement, augs) { - for (var i = 0; i < augs.length; ++i) { - var augName = augs[i].name; - var aug = Augmentations[augName]; - - var displayName = augName; - if (augName === AugmentationNames.NeuroFluxGovernor) { - displayName += " - Level " + (augs[i].level); - } - var accordion = createAccordionElement({hdrText:displayName, panelText:aug.info}); - listElement.appendChild(accordion[0]); - } -} - -//Creates the accordion elements to display Source Files -// @listElement - List DOM element to append accordion elements to -// @sourceFiles - Array of Source File objects -function displaySourceFiles(listElement, sourceFiles) { - for (var i = 0; i < sourceFiles.length; ++i) { - var srcFileKey = "SourceFile" + sourceFiles[i].n; - var sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.log("ERROR: Invalid source file number: " + sourceFiles[i].n); - continue; - } - const maxLevel = sourceFiles[i].n == 12 ? "∞" : "3"; - var accordion = createAccordionElement({ - hdrText:sourceFileObject.name + "
" + "Level " + (sourceFiles[i].lvl) + " / "+maxLevel, - panelText:sourceFileObject.info - }); - - listElement.appendChild(accordion[0]); - } + ReactDOM.render( + , + contentEl + ); } export function isRepeatableAug(aug) { @@ -2307,6 +2120,9 @@ export function isRepeatableAug(aug) { return false; } -export {installAugmentations, - initAugmentations, applyAugmentation, augmentationExists, - displayAugmentationsContent}; +export { + installAugmentations, + initAugmentations, + applyAugmentation, + augmentationExists, +}; diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx index 84d8cb2ea..076046cac 100644 --- a/src/Augmentation/ui/InstalledAugmentations.tsx +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -30,11 +30,13 @@ export function InstalledAugmentations(): React.ReactElement { } return ( - +
  • + +
  • ) }); return ( -
      {augs}
    + <>{augs} ) } diff --git a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx index 39bac22e6..7246ed6a8 100644 --- a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx +++ b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx @@ -7,18 +7,22 @@ */ import * as React from "react"; +import { InstalledAugmentations } from "./InstalledAugmentations"; +import { ListConfiguration } from "./ListConfiguration"; +import { OwnedSourceFiles } from "./OwnedSourceFiles"; + import { Settings } from "../../Settings/Settings"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; -type IProps = { - -} +type IProps = {} type IState = { rerenderFlag: boolean; } export class InstalledAugmentationsAndSourceFiles extends React.Component { + listRef: React.RefObject; + constructor(props: IProps) { super(props); @@ -26,8 +30,44 @@ export class InstalledAugmentationsAndSourceFiles extends React.Component + +
      + + +
    + ) } } diff --git a/src/Augmentation/ui/ListConfiguration.tsx b/src/Augmentation/ui/ListConfiguration.tsx index ef1368b8f..555766388 100644 --- a/src/Augmentation/ui/ListConfiguration.tsx +++ b/src/Augmentation/ui/ListConfiguration.tsx @@ -3,3 +3,37 @@ * Source-Files are displayed in the Augmentations UI */ import * as React from "react"; + +import { StdButton } from "../../ui/React/StdButton"; + +type IProps = { + collapseAllButtonsFn: () => void; + expandAllButtonsFn: () => void; + sortByAcquirementTimeFn: () => void; + sortInOrderFn: () => void; +} + +export function ListConfiguration(props: IProps): React.ReactElement { + return ( + <> + + + + + + ) +} diff --git a/src/Augmentation/ui/OwnedSourceFiles.tsx b/src/Augmentation/ui/OwnedSourceFiles.tsx index 24107bcdf..99012b4a7 100644 --- a/src/Augmentation/ui/OwnedSourceFiles.tsx +++ b/src/Augmentation/ui/OwnedSourceFiles.tsx @@ -5,36 +5,37 @@ import * as React from "react"; import { Player } from "../../Player"; -import { Augmentations } from "../../Augmentation/Augmentations"; -import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { Settings } from "../../Settings/Settings"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; +import { SourceFiles } from "../../SourceFile/SourceFiles"; -import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; +import { SourceFileAccordion } from "../../ui/React/SourceFileAccordion"; export function OwnedSourceFiles(): React.ReactElement { - const sourceAugs = Player.augmentations.slice(); + const sourceSfs = Player.sourceFiles.slice(); if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sourceAugs.sort((aug1, aug2) => { - return aug1.name <= aug2.name ? -1 : 1; + sourceSfs.sort((sf1, sf2) => { + return sf1.n - sf2.n; }); } - const augs = sourceAugs.map((e) => { - const aug = Augmentations[e.name]; - - let level = null; - if (e.name === AugmentationNames.NeuroFluxGovernor) { - level = e.level; + const sfs = sourceSfs.map((e) => { + const srcFileKey = "SourceFile" + e.n; + const sfObj = SourceFiles[srcFileKey]; + if (sfObj == null) { + console.error(`Invalid source file number: ${e.n}`); + return null; } return ( - +
  • + +
  • ) }); return ( -
      {augs}
    - ) + <>{sfs} + ); } diff --git a/src/Augmentation/ui/PlayerMultipliers.tsx b/src/Augmentation/ui/PlayerMultipliers.tsx new file mode 100644 index 000000000..f263558de --- /dev/null +++ b/src/Augmentation/ui/PlayerMultipliers.tsx @@ -0,0 +1,96 @@ +/** + * React component for displaying the player's multipliers on the Augmentation UI page + */ +import * as React from "react"; + +import { Player } from "../../Player"; +import { numeralWrapper } from "../../ui/numeralFormat"; + +export function PlayerMultipliers(): React.ReactElement { + return ( + <> +

    Total Multipliers:

    + +
    +            {'Hacking Chance multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_chance_mult)}
    +        
    +
    +            {'Hacking Speed multiplier:  ' + numeralWrapper.formatPercentage(Player.hacking_speed_mult)}
    +        
    +
    +            {'Hacking Money multiplier:  ' + numeralWrapper.formatPercentage(Player.hacking_money_mult)}
    +        
    +
    +            {'Hacking Growth multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_grow_mult)}
    +        

    +
    +            {'Hacking Level multiplier:      ' + numeralWrapper.formatPercentage(Player.hacking_mult)}
    +        
    +
    +            {'Hacking Experience multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_exp_mult)}
    +        
    +
    +
    +            {'Strength Level multiplier:      ' + numeralWrapper.formatPercentage(Player.strength_mult)}
    +        
    +
    +            {'Strength Experience multiplier: ' + numeralWrapper.formatPercentage(Player.strength_exp_mult)}
    +        
    +
    +
    +            {'Defense Level multiplier:      ' + numeralWrapper.formatPercentage(Player.defense_mult)}
    +        
    +
    +            {'Defense Experience multiplier: ' + numeralWrapper.formatPercentage(Player.defense_exp_mult)}
    +        

    +
    +            {'Dexterity Level multiplier:      ' + numeralWrapper.formatPercentage(Player.dexterity_mult)}
    +        
    +
    +            {'Dexterity Experience multiplier: ' + numeralWrapper.formatPercentage(Player.dexterity_exp_mult)}
    +        

    +
    +            {'Agility Level multiplier:      ' + numeralWrapper.formatPercentage(Player.agility_mult)}
    +        
    +
    +            {'Agility Experience multiplier: ' + numeralWrapper.formatPercentage(Player.agility_exp_mult)}
    +        

    +
    +            {'Charisma Level multiplier:      ' + numeralWrapper.formatPercentage(Player.charisma_mult)}
    +        
    +
    +            {'Charisma Experience multiplier: ' + numeralWrapper.formatPercentage(Player.charisma_exp_mult)}
    +        

    +
    +            {'Hacknet Node production multiplier:         ' + numeralWrapper.formatPercentage(Player.hacknet_node_money_mult)}
    +        
    +
    +            {'Hacknet Node purchase cost multiplier:      ' + numeralWrapper.formatPercentage(Player.hacknet_node_purchase_cost_mult)}
    +        
    +
    +            {'Hacknet Node RAM upgrade cost multiplier:   ' + numeralWrapper.formatPercentage(Player.hacknet_node_ram_cost_mult)}
    +        
    +
    +            {'Hacknet Node Core purchase cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_core_cost_mult)}
    +        
    +
    +            {'Hacknet Node level upgrade cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_level_cost_mult)}
    +        

    +
    +            {'Company reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.company_rep_mult)}
    +        
    +
    +            {'Faction reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.faction_rep_mult)}
    +        
    +
    +            {'Salary multiplier: ' + numeralWrapper.formatPercentage(Player.work_money_mult)}
    +        

    +
    +            {'Crime success multiplier: ' + numeralWrapper.formatPercentage(Player.crime_success_mult)}
    +        
    +
    +            {'Crime money multiplier: ' + numeralWrapper.formatPercentage(Player.crime_money_mult)}
    +        
    + + ) +} diff --git a/src/Augmentation/ui/PurchasedAugmentations.tsx b/src/Augmentation/ui/PurchasedAugmentations.tsx index bd966dfe5..3bc54d837 100644 --- a/src/Augmentation/ui/PurchasedAugmentations.tsx +++ b/src/Augmentation/ui/PurchasedAugmentations.tsx @@ -20,7 +20,9 @@ export function PurchasedAugmentations(): React.ReactElement { } augs.push( - +
  • + +
  • ) } diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/Root.tsx index 565267f31..10e733556 100644 --- a/src/Augmentation/ui/Root.tsx +++ b/src/Augmentation/ui/Root.tsx @@ -4,9 +4,11 @@ */ import * as React from "react"; -import { Augmentations } from "../../Augmentation/Augmentations"; -import { Player } from "../../Player"; +import { InstalledAugmentationsAndSourceFiles } from "./InstalledAugmentationsAndSourceFiles"; +import { PlayerMultipliers } from "./PlayerMultipliers"; +import { PurchasedAugmentations } from "./PurchasedAugmentations"; +import { Player } from "../../Player"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { @@ -25,7 +27,7 @@ export class AugmentationsRoot extends React.Component { render() { return ( -
    +

    Purchased Augmentations

    Below is a list of all Augmentations you have purchased but not @@ -34,14 +36,14 @@ export class AugmentationsRoot extends React.Component {

    WARNING: Installing your Augmentations resets most of your progress, including: -

    +


    - Stats/Skill levels and Experience

    - Money

    - Scripts on every computer but your home computer

    - Purchased servers

    - Hacknet Nodes

    - Faction/Company reputation

    -

    - Stocks

    +

    - Stocks


    Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations you have ever @@ -62,10 +64,19 @@ export class AugmentationsRoot extends React.Component { text="Backup Save (Export)" tooltip="It's always a good idea to backup/export your save!" /> + -

      +

      Installed Augmentations

      +

      + { + `List of all Augmentations ${Player.sourceFiles.length > 0 ? "and Source Files " : ""} ` + + `that have been installed. You have gained the effects of these.` + } +

      + -
    +

    +
    ) } diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index a4803866a..7f443c14a 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -25,235 +25,232 @@ class BitNode { } -export let BitNodes: IMap = {}; +export const BitNodes: IMap = {}; -export function initBitNodes() { - BitNodes = {}; - BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode", - "The first BitNode created by the Enders to imprison the minds of humans. It became " + - "the prototype and testing-grounds for all of the BitNodes that followed.

    " + - "This is the first BitNode that you play through. It has no special " + - "modifications or mechanics.

    " + - "Destroying this BitNode will give you Source-File 1, or if you already have " + - "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " + - "lets the player start with 32GB of RAM on his/her home computer when entering a " + - "new BitNode, and also increases all of the player's multipliers by:

    " + - "Level 1: 16%
    " + - "Level 2: 24%
    " + - "Level 3: 28%"); - BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs - "From the shadows, they rose.

    Organized crime groups quickly filled the void of power " + - "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " + - "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " + - "factions quickly rose to the top of the modern world.

    " + - "In this BitNode:

    " + - "Your hacking level is reduced by 20%
    " + - "The growth rate and maximum amount of money available on servers are significantly decreased
    " + - "The amount of money gained from crimes and Infiltration is tripled
    " + - "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " + - "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " + - "will earn the player money and reputation with the corresponding Faction
    " + - "Every Augmentation in the game will be available through the Factions listed above
    " + - "For every Faction NOT listed above, reputation gains are halved
    " + - "You will no longer gain passive reputation with Factions

    " + - "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " + - "once your karma decreases to a certain value. " + - "It also increases the player's crime success rate, crime money, and charisma multipliers by:

    " + - "Level 1: 24%
    " + - "Level 2: 36%
    " + - "Level 3: 42%"); - BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization", - "Our greatest illusion is that a healthy society can revolve around a " + - "single-minded pursuit of wealth.

    " + - "Sometime in the early 21st century economic and political globalization turned " + - "the world into a corporatocracy, and it never looked back. Now, the privileged " + - "elite will happily bankrupt their own countrymen, decimate their own community, " + - "and evict their neighbors from houses in their desperate bid to increase their wealth.

    " + - "In this BitNode you can create and manage your own corporation. Running a successful corporation " + - "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:

    " + - "The price and reputation cost of all Augmentations is tripled
    " + - "The starting and maximum amount of money on servers is reduced by 75%
    " + - "Server growth rate is reduced by 80%
    " + - "You now only need 75 favour with a faction in order to donate to it, rather than 150

    " + - "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " + - "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine", - "The Singularity has arrived. The human race is gone, replaced " + - "by artificially superintelligent beings that are more machine than man.

    " + - "In this BitNode, progressing is significantly harder. Experience gain rates " + - "for all stats are reduced. Most methods of earning money will now give significantly less.

    " + - "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " + - "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " + - "purchasing/installing Augmentations, and creating programs.

    " + - "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " + - "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " + - "that you can use."); - BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman", - "They said it couldn't be done. They said the human brain, " + - "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " + - "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " + - "by 1's and 0's. They were wrong.

    " + - "In this BitNode:

    " + - "The base security level of servers is doubled
    " + - "The starting money on servers is halved, but the maximum money remains the same
    " + - "Most methods of earning money now give significantly less
    " + - "Infiltration gives 50% more reputation and money
    " + - "Corporations have 50% lower valuations and are therefore less profitable
    " + - "Augmentations are more expensive
    " + - "Hacking experience gain rates are reduced

    " + - "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " + - "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " + - "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " + - "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " + - "in the game.

    " + - "In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " + - "and will also raise all of your hacking-related multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain", - "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " + - "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " + - "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " + - "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " + - "than the humans that had created them.

    " + - "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " + - "for progression. Furthermore:

    " + - "Hacking and Hacknet Nodes will be less profitable
    " + - "Your hacking level is reduced by 65%
    " + - "Hacking experience gain from scripts is reduced by 75%
    " + - "Corporations have 80% lower valuations and are therefore less profitable
    " + - "Working for companies is 50% less profitable
    " + - "Crimes and Infiltration are 25% less profitable

    " + - "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " + - "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " + - "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans", - "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " + - "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " + - "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " + - "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " + - "and more intelligent than the humans that had created them.

    " + - "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " + - "functionality through Netscript. Furthermore:

    " + - "The rank you gain from Bladeburner contracts/operations is reduced by 40%
    " + - "Bladeburner skills cost twice as many skill points
    " + - "Augmentations are 3x more expensive
    " + - "Hacking and Hacknet Nodes will be significantly less profitable
    " + - "Your hacking level is reduced by 65%
    " + - "Hacking experience gain from scripts is reduced by 75%
    " + - "Corporations have 80% lower valuations and are therefore less profitable
    " + - "Working for companies is 50% less profitable
    " + - "Crimes and Infiltration are 25% less profitable

    " + - "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " + - "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " + - "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps", - "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.

    " + - "In this BitNode:

    " + - "You start with $250 million
    " + - "The only way to earn money is by trading on the stock market
    " + - "You start with a WSE membership and access to the TIX API
    " + - "You are able to short stocks and place different types of orders (limit/stop)
    " + - "You can immediately donate to factions to gain reputation

    " + - "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + - "Level 1: Permanent access to WSE and TIX API
    " + - "Level 2: Ability to short stocks in other BitNodes
    " + - "Level 3: Ability to use limit/stop orders in other BitNodes

    " + - "This Source-File also increases your hacking growth multipliers by: " + - "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); - BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed", - "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " + - "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " + - "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " + - "abandoned the project and dissociated themselves from it.

    " + - "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " + - "hashes, which can be spent on a variety of different upgrades.

    " + - "In this BitNode:

    " + - "Your stats are significantly decreased
    " + - "You cannnot purchase additional servers
    " + - "Hacking is significantly less profitable

    " + - "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + - "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + - "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + - "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + - "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + - "when installing Augmentations)"); - BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are", - "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + - "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + - "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " + - "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.

    " + - "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

    " + - "1. Re-sleeve: Purchase and transfer your consciousness into a new body
    " + - "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously

    " + - "In this BitNode:

    " + - "Your stats are significantly decreased
    " + - "All methods of gaining money are half as profitable (except Stock Market)
    " + - "Purchased servers are more expensive, have less max RAM, and a lower maximum limit
    " + - "Augmentations are 5x as expensive and require twice as much reputation

    " + - "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " + - "Each level of this Source-File also grants you a Duplicate Sleeve"); - BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.", - "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " + - "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " + - "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.

    " + - "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " + - "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + - "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

    " + - "In this BitNode:

    " + - "Your hacking stat and experience gain are halved
    " + - "The starting and maximum amount of money available on servers is significantly decreased
    " + - "The growth rate of servers is significantly reduced
    " + - "Weakening a server is twice as effective
    " + - "Company wages are decreased by 50%
    " + - "Corporation valuations are 99% lower and are therefore significantly less profitable
    " + - "Hacknet Node production is significantly decreased
    " + - "Crime and Infiltration are more lucrative
    " + - "Augmentations are twice as expensive

    " + - "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + - "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + - "This Source-File also increases the player's company salary and reputation gain multipliers by:

    " + - "Level 1: 32%
    " + - "Level 2: 48%
    " + - "Level 3: 56%"); - BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", - "To iterate is human, to recurse divine.

    " + - "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + - "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + - "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " + - "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); - //Books: Frontera, Shiner - BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes - BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON"); - BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON"); - BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON"); - BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON"); - BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON"); - BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON"); - BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON"); - BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON"); - BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON"); - BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON"); - BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON"); -} +BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode", + "The first BitNode created by the Enders to imprison the minds of humans. It became " + + "the prototype and testing-grounds for all of the BitNodes that followed.

    " + + "This is the first BitNode that you play through. It has no special " + + "modifications or mechanics.

    " + + "Destroying this BitNode will give you Source-File 1, or if you already have " + + "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " + + "lets the player start with 32GB of RAM on his/her home computer when entering a " + + "new BitNode, and also increases all of the player's multipliers by:

    " + + "Level 1: 16%
    " + + "Level 2: 24%
    " + + "Level 3: 28%"); +BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs + "From the shadows, they rose.

    Organized crime groups quickly filled the void of power " + + "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " + + "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " + + "factions quickly rose to the top of the modern world.

    " + + "In this BitNode:

    " + + "Your hacking level is reduced by 20%
    " + + "The growth rate and maximum amount of money available on servers are significantly decreased
    " + + "The amount of money gained from crimes and Infiltration is tripled
    " + + "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " + + "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " + + "will earn the player money and reputation with the corresponding Faction
    " + + "Every Augmentation in the game will be available through the Factions listed above
    " + + "For every Faction NOT listed above, reputation gains are halved
    " + + "You will no longer gain passive reputation with Factions

    " + + "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " + + "once your karma decreases to a certain value. " + + "It also increases the player's crime success rate, crime money, and charisma multipliers by:

    " + + "Level 1: 24%
    " + + "Level 2: 36%
    " + + "Level 3: 42%"); +BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization", + "Our greatest illusion is that a healthy society can revolve around a " + + "single-minded pursuit of wealth.

    " + + "Sometime in the early 21st century economic and political globalization turned " + + "the world into a corporatocracy, and it never looked back. Now, the privileged " + + "elite will happily bankrupt their own countrymen, decimate their own community, " + + "and evict their neighbors from houses in their desperate bid to increase their wealth.

    " + + "In this BitNode you can create and manage your own corporation. Running a successful corporation " + + "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:

    " + + "The price and reputation cost of all Augmentations is tripled
    " + + "The starting and maximum amount of money on servers is reduced by 75%
    " + + "Server growth rate is reduced by 80%
    " + + "You now only need 75 favour with a faction in order to donate to it, rather than 150

    " + + "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " + + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine", + "The Singularity has arrived. The human race is gone, replaced " + + "by artificially superintelligent beings that are more machine than man.

    " + + "In this BitNode, progressing is significantly harder. Experience gain rates " + + "for all stats are reduced. Most methods of earning money will now give significantly less.

    " + + "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " + + "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " + + "purchasing/installing Augmentations, and creating programs.

    " + + "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " + + "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " + + "that you can use."); +BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman", + "They said it couldn't be done. They said the human brain, " + + "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " + + "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " + + "by 1's and 0's. They were wrong.

    " + + "In this BitNode:

    " + + "The base security level of servers is doubled
    " + + "The starting money on servers is halved, but the maximum money remains the same
    " + + "Most methods of earning money now give significantly less
    " + + "Infiltration gives 50% more reputation and money
    " + + "Corporations have 50% lower valuations and are therefore less profitable
    " + + "Augmentations are more expensive
    " + + "Hacking experience gain rates are reduced

    " + + "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " + + "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " + + "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " + + "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " + + "in the game.

    " + + "In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " + + "and will also raise all of your hacking-related multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain", + "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " + + "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " + + "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " + + "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " + + "than the humans that had created them.

    " + + "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " + + "for progression. Furthermore:

    " + + "Hacking and Hacknet Nodes will be less profitable
    " + + "Your hacking level is reduced by 65%
    " + + "Hacking experience gain from scripts is reduced by 75%
    " + + "Corporations have 80% lower valuations and are therefore less profitable
    " + + "Working for companies is 50% less profitable
    " + + "Crimes and Infiltration are 25% less profitable

    " + + "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " + + "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " + + "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans", + "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " + + "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " + + "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " + + "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " + + "and more intelligent than the humans that had created them.

    " + + "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " + + "functionality through Netscript. Furthermore:

    " + + "The rank you gain from Bladeburner contracts/operations is reduced by 40%
    " + + "Bladeburner skills cost twice as many skill points
    " + + "Augmentations are 3x more expensive
    " + + "Hacking and Hacknet Nodes will be significantly less profitable
    " + + "Your hacking level is reduced by 65%
    " + + "Hacking experience gain from scripts is reduced by 75%
    " + + "Corporations have 80% lower valuations and are therefore less profitable
    " + + "Working for companies is 50% less profitable
    " + + "Crimes and Infiltration are 25% less profitable

    " + + "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " + + "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " + + "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps", + "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.

    " + + "In this BitNode:

    " + + "You start with $250 million
    " + + "The only way to earn money is by trading on the stock market
    " + + "You start with a WSE membership and access to the TIX API
    " + + "You are able to short stocks and place different types of orders (limit/stop)
    " + + "You can immediately donate to factions to gain reputation

    " + + "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + + "Level 1: Permanent access to WSE and TIX API
    " + + "Level 2: Ability to short stocks in other BitNodes
    " + + "Level 3: Ability to use limit/stop orders in other BitNodes

    " + + "This Source-File also increases your hacking growth multipliers by: " + + "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); +BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed", + "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " + + "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " + + "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " + + "abandoned the project and dissociated themselves from it.

    " + + "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " + + "hashes, which can be spent on a variety of different upgrades.

    " + + "In this BitNode:

    " + + "Your stats are significantly decreased
    " + + "You cannnot purchase additional servers
    " + + "Hacking is significantly less profitable

    " + + "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + + "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + + "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + + "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + + "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + + "when installing Augmentations)"); +BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are", + "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + + "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + + "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " + + "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.

    " + + "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

    " + + "1. Re-sleeve: Purchase and transfer your consciousness into a new body
    " + + "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously

    " + + "In this BitNode:

    " + + "Your stats are significantly decreased
    " + + "All methods of gaining money are half as profitable (except Stock Market)
    " + + "Purchased servers are more expensive, have less max RAM, and a lower maximum limit
    " + + "Augmentations are 5x as expensive and require twice as much reputation

    " + + "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " + + "Each level of this Source-File also grants you a Duplicate Sleeve"); +BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.", + "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " + + "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " + + "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.

    " + + "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " + + "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + + "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

    " + + "In this BitNode:

    " + + "Your hacking stat and experience gain are halved
    " + + "The starting and maximum amount of money available on servers is significantly decreased
    " + + "The growth rate of servers is significantly reduced
    " + + "Weakening a server is twice as effective
    " + + "Company wages are decreased by 50%
    " + + "Corporation valuations are 99% lower and are therefore significantly less profitable
    " + + "Hacknet Node production is significantly decreased
    " + + "Crime and Infiltration are more lucrative
    " + + "Augmentations are twice as expensive

    " + + "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + + "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + + "This Source-File also increases the player's company salary and reputation gain multipliers by:

    " + + "Level 1: 32%
    " + + "Level 2: 48%
    " + + "Level 3: 56%"); +BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", + "To iterate is human, to recurse divine.

    " + + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + + "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + + "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " + + "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); +// Books: Frontera, Shiner +BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes +BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON"); +BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON"); +BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON"); +BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON"); +BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON"); +BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON"); +BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON"); +BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON"); +BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON"); +BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON"); +BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON"); export function initBitNodeMultipliers(p: IPlayer) { if (p.bitNodeN == null) { diff --git a/src/engine.jsx b/src/engine.jsx index 488c21eb6..57f397c8d 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -15,7 +15,6 @@ import { import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { BitNodes, - initBitNodes, initBitNodeMultipliers } from "./BitNode/BitNode"; import { Bladeburner } from "./Bladeburner"; @@ -310,8 +309,8 @@ const Engine = { loadAugmentationsContent: function() { Engine.hideAllContent(); Engine.Display.augmentationsContent.style.display = "block"; - displayAugmentationsContent(Engine.Display.augmentationsContent); routing.navigateTo(Page.Augmentations); + displayAugmentationsContent(Engine.Display.augmentationsContent); MainMenuLinks.Augmentations.classList.add("active"); }, @@ -488,13 +487,20 @@ const Engine = { Engine.Display.activeScriptsContent.style.display = "none"; clearHacknetNodesUI(); Engine.Display.createProgramContent.style.display = "none"; + Engine.Display.factionsContent.style.display = "none"; - ReactDOM.unmountComponentAtNode(Engine.Display.factionContent); + Engine.Display.factionContent.style.display = "none"; + ReactDOM.unmountComponentAtNode(Engine.Display.factionContent); + Engine.Display.augmentationsContent.style.display = "none"; + ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent); + Engine.Display.tutorialContent.style.display = "none"; + Engine.Display.locationContent.style.display = "none"; ReactDOM.unmountComponentAtNode(Engine.Display.locationContent); + Engine.Display.workInProgressContent.style.display = "none"; Engine.Display.redPillContent.style.display = "none"; Engine.Display.cinematicTextContent.style.display = "none"; @@ -1038,7 +1044,6 @@ const Engine = { // Load game from save or create new game if (loadGame(saveString)) { - initBitNodes(); initBitNodeMultipliers(Player); Engine.setDisplayElements(); // Sets variables for important DOM elements Engine.init(); // Initialize buttons, work, etc. @@ -1160,7 +1165,6 @@ const Engine = { } else { // No save found, start new game console.log("Initializing new game"); - initBitNodes(); initBitNodeMultipliers(Player); initSpecialServerIps(); Engine.setDisplayElements(); // Sets variables for important DOM elements diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index b7d382fe5..5833f29f7 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -45,12 +45,12 @@ export class Accordion extends React.Component { render() { return ( -
    - - -
    + <> + + + ) } } diff --git a/src/ui/React/AugmentationAccordion.tsx b/src/ui/React/AugmentationAccordion.tsx index 430a9800c..98b95f8f1 100644 --- a/src/ui/React/AugmentationAccordion.tsx +++ b/src/ui/React/AugmentationAccordion.tsx @@ -2,7 +2,7 @@ * React Component for displaying a single Augmentation as an accordion. * * The header of the accordion contains the Augmentation's name (and level, if - * applicable), and the accordion's panel contains the Augmentation's level. + * applicable), and the accordion's panel contains the Augmentation's description. */ import * as React from "react"; @@ -26,8 +26,8 @@ export function AugmentationAccordion(props: IProps): React.ReactElement { return ( {displayName}

    } - panelContent={

    {props.aug.info}

    } + headerContent={<>{displayName}} + panelContent={

    } /> ) } diff --git a/src/ui/React/SourceFileAccordion.tsx b/src/ui/React/SourceFileAccordion.tsx new file mode 100644 index 000000000..2f5172e36 --- /dev/null +++ b/src/ui/React/SourceFileAccordion.tsx @@ -0,0 +1,35 @@ +/** + * React Component for displaying a single Source-File as an accordion. + * + * The header of the accordion contains the Source-Files's name and level, + * and the accordion's panel contains the Source-File's description. + */ +import * as React from "react"; + +import { Accordion } from "./Accordion"; + +import { SourceFile } from "../../SourceFile/SourceFile"; + +type IProps = { + level: number, + sf: SourceFile, +} + +export function SourceFileAccordion(props: IProps): React.ReactElement { + const maxLevel = props.sf.n === 3 ? "∞" : "3"; + + return ( + + {props.sf.name} +
    + {`Level ${props.level} / ${maxLevel}`} + + } + panelContent={ +

    + } + /> + ) +} From b1248521f3ecb3c88d96e5e3b2e8d0025432a062 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 15 May 2019 00:37:11 -0700 Subject: [PATCH 3/9] Removed unused imports in engine --- src/engine.jsx | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/engine.jsx b/src/engine.jsx index 57f397c8d..1e8e8731b 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -1,27 +1,22 @@ import { - formatNumber, convertTimeMsToTimeElapsedString, replaceAt } from "../utils/StringHelperFunctions"; -import { loxBoxCreate, logBoxUpdateText, logBoxOpened } from "../utils/LogBox"; +import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox"; import { updateActiveScriptsItems } from "./ActiveScriptsUI"; import { Augmentations } from "./Augmentation/Augmentations"; import { - installAugmentations, initAugmentations, displayAugmentationsContent, - PlayerOwnedAugmentation } from "./Augmentation/AugmentationHelpers"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { - BitNodes, initBitNodeMultipliers } from "./BitNode/BitNode"; import { Bladeburner } from "./Bladeburner"; import { CharacterOverviewComponent } from "./ui/React/CharacterOverview"; import { cinematicTextFlag } from "./CinematicText"; import { generateRandomContract } from "./CodingContractGenerator"; -import { CompanyPositions } from "./Company/CompanyPositions"; import { initCompanies } from "./Company/Companies"; import { Corporation } from "./Corporation/Corporation"; import { CONSTANTS } from "./Constants"; @@ -53,7 +48,6 @@ import { } from "./NetscriptWorker"; import { Player } from "./Player"; import { prestigeAugmentation } from "./Prestige"; -import { Programs } from "./Programs/Programs"; import { displayCreateProgramContent, getNumAvailableCreateProgram, @@ -66,16 +60,11 @@ import { scriptEditorInit, updateScriptEditorContent } from "./Script/ScriptHelpers"; -import { AllServers, initForeignServers } from "./Server/AllServers"; +import { initForeignServers } from "./Server/AllServers"; import { Settings } from "./Settings/Settings"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; +import { initSpecialServerIps } from "./Server/SpecialServerIps"; import { - SpecialServerIps, - initSpecialServerIps -} from "./Server/SpecialServerIps"; -import { - StockMarket, - SymbolToStockMap, initSymbolToStockMap, stockMarketCycle, processStockPrices, @@ -103,9 +92,7 @@ import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links"; import { dialogBoxCreate } from "../utils/DialogBox"; import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions"; -import { getRandomInt } from "../utils/helpers/getRandomInt"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; -import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { createElement } from "../utils/uiHelpers/createElement"; import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen"; From 42804b0cd3a0110d7d76c465c44298381e595dfb Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 15 May 2019 23:05:36 -0700 Subject: [PATCH 4/9] Refactored 'workerScripts' array and killWorkerScript() fn to be their own modules in TypeScript --- css/activescripts.scss | 119 +++++++++++++++++ css/menupages.scss | 120 ----------------- src/Netscript/WorkerScript.ts | 5 + src/Netscript/WorkerScripts.ts | 6 + src/Netscript/killWorkerScript.ts | 123 ++++++++++++++++++ src/NetscriptEvaluator.js | 12 -- src/NetscriptFunctions.js | 4 +- src/NetscriptWorker.js | 68 +--------- src/Terminal.js | 3 +- src/engine.jsx | 5 + src/engineStyle.js | 1 + src/ui/ActiveScripts/Root.tsx | 31 +++++ .../ActiveScripts/WorkerScriptAccordion.tsx | 40 ++++++ src/ui/React/Accordion.tsx | 23 +++- src/utils/EventEmitter.ts | 50 +++++++ utils/LogBox.js | 6 +- 16 files changed, 413 insertions(+), 203 deletions(-) create mode 100644 css/activescripts.scss create mode 100644 src/Netscript/WorkerScripts.ts create mode 100644 src/Netscript/killWorkerScript.ts create mode 100644 src/ui/ActiveScripts/Root.tsx create mode 100644 src/ui/ActiveScripts/WorkerScriptAccordion.tsx create mode 100644 src/utils/EventEmitter.ts diff --git a/css/activescripts.scss b/css/activescripts.scss new file mode 100644 index 000000000..82b7ae7b9 --- /dev/null +++ b/css/activescripts.scss @@ -0,0 +1,119 @@ +@import "theme"; + +.active-scripts-list { + list-style-type: none; +} + +#active-scripts-container { + position: fixed; + padding-top: 10px; + + > p { + width: 70%; + margin: 6px; + padding: 4px; + } +} + +.active-scripts-server-header { + background-color: #444; + font-size: $defaultFontSize * 1.25; + color: #fff; + margin: 6px 6px 0 6px; + padding: 6px; + cursor: pointer; + width: 60%; + text-align: left; + border: none; + outline: none; +} + +.active-scripts-server-header.active, +.active-scripts-server-header:hover { + background-color: #555; +} + +.active-scripts-server-header.active:hover { + background-color: #666; +} + +.active-scripts-server-header:after { + content: '\02795'; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; + color: #fff; + float: right; + margin-left: 5px; +} + +.active-scripts-server-header.active:after { + content: "\2796"; /* "minus" sign (-) */ + font-size: $defaultFontSize * 0.8125; + color: #fff; + float: right; + margin-left: 5px; +} + +.active-scripts-server-panel { + margin: 0 6px 6px 6px; + padding: 0 6px 6px 6px; + width: 55%; + margin-left: 5%; + display: none; +} + +.active-scripts-server-panel div, +.active-scripts-server-panel ul, +.active-scripts-server-panel ul > li { + background-color: #555; +} + +.active-scripts-script-header { + background-color: #555; + color: var(--my-font-color); + padding: 4px 25px 4px 10px; + cursor: pointer; + width: auto; + text-align: left; + border: none; + outline: none; + position: relative; + + &:after { + content: '\02795'; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; + float: right; + margin-left: 5px; + color: transparent; + text-shadow: 0 0 0 var(--my-font-color); + position: absolute; + bottom: 4px; + } + + &.active:after { + content: "\2796"; /* "minus" sign (-) */ + } + + &:hover, + &.active:hover { + background-color: #666; + } + + &.active { + background-color: #555; + } +} + +.active-scripts-script-panel { + padding: 0 18px; + background-color: #555; + width: auto; + display: none; + margin-bottom: 6px; + + p, h2, ul, li { + background-color: #555; + width: auto; + color: #fff; + margin-left: 5%; + } +} diff --git a/css/menupages.scss b/css/menupages.scss index b0b45c723..2c94436de 100644 --- a/css/menupages.scss +++ b/css/menupages.scss @@ -18,126 +18,6 @@ position: fixed; } -/* Active scripts */ -.active-scripts-list { - list-style-type: none; -} - -#active-scripts-container { - position: fixed; - padding-top: 10px; -} - -#active-scripts-text, -#active-scripts-total-prod { - width: 70%; - margin: 6px; - padding: 4px; -} - -.active-scripts-server-header { - background-color: #444; - font-size: $defaultFontSize * 1.25; - color: #fff; - margin: 6px 6px 0 6px; - padding: 6px; - cursor: pointer; - width: 60%; - text-align: left; - border: none; - outline: none; -} - -.active-scripts-server-header.active, -.active-scripts-server-header:hover { - background-color: #555; -} - -.active-scripts-server-header.active:hover { - background-color: #666; -} - -.active-scripts-server-header:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; -} - -.active-scripts-server-header.active:after { - content: "\2796"; /* "minus" sign (-) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; -} - -.active-scripts-server-panel { - margin: 0 6px 6px 6px; - padding: 0 6px 6px 6px; - width: 55%; - margin-left: 5%; - display: none; -} - -.active-scripts-server-panel div, -.active-scripts-server-panel ul, -.active-scripts-server-panel ul > li { - background-color: #555; -} - -.active-scripts-script-header { - background-color: #555; - color: var(--my-font-color); - padding: 4px 25px 4px 10px; - cursor: pointer; - width: auto; - text-align: left; - border: none; - outline: none; - position: relative; - - &:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - float: right; - margin-left: 5px; - color: transparent; - text-shadow: 0 0 0 var(--my-font-color); - position: absolute; - bottom: 4px; - } - - &.active:after { - content: "\2796"; /* "minus" sign (-) */ - } - - &:hover, - &.active:hover { - background-color: #666; - } - - &.active { - background-color: #555; - } -} - -.active-scripts-script-panel { - padding: 0 18px; - background-color: #555; - width: auto; - display: none; - margin-bottom: 6px; - - p, h2, ul, li { - background-color: #555; - width: auto; - color: #fff; - margin-left: 5%; - } -} - /* World */ #world-container { position: fixed; diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts index 2549c06e0..6b99c43d5 100644 --- a/src/Netscript/WorkerScript.ts +++ b/src/Netscript/WorkerScript.ts @@ -32,6 +32,11 @@ export class WorkerScript { */ delay: number | null = null; + /** + * Holds the Promise resolve() function for when the script is "blocked" by an async op + */ + delayResolve?: () => void; + /** * Stores names of all functions that have logging disabled */ diff --git a/src/Netscript/WorkerScripts.ts b/src/Netscript/WorkerScripts.ts new file mode 100644 index 000000000..50515f413 --- /dev/null +++ b/src/Netscript/WorkerScripts.ts @@ -0,0 +1,6 @@ +/** + * Global pool of all active scripts (scripts that are currently running) + */ +import { WorkerScript } from "./WorkerScript"; + +export const workerScripts: WorkerScript[] = []; diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts new file mode 100644 index 000000000..ee60576f8 --- /dev/null +++ b/src/Netscript/killWorkerScript.ts @@ -0,0 +1,123 @@ +/** + * Function that stops an active script (represented by a WorkerScript object) + * and removes it from the global pool of active scripts. + */ +import { WorkerScript } from "./WorkerScript"; +import { workerScripts } from "./WorkerScripts"; + +import { RunningScript } from "../Script/RunningScript"; +import { AllServers } from "../Server/AllServers"; + +import { compareArrays } from "../../utils/helpers/compareArrays"; +import { roundToTwo } from "../../utils/helpers/roundToTwo"; + +export function killWorkerScript(runningScriptObj: RunningScript, serverIp: string): boolean; +export function killWorkerScript(workerScript: WorkerScript): boolean; +export function killWorkerScript(script: RunningScript | WorkerScript, serverIp?: string): boolean { + if (script instanceof WorkerScript) { + script.env.stopFlag = true; + killNetscriptDelay(script); + removeWorkerScript(script); + + return true; + } else if (script instanceof RunningScript && typeof serverIp === "string") { + for (let i = 0; i < workerScripts.length; i++) { + if (workerScripts[i].name == script.filename && workerScripts[i].serverIp == serverIp && + compareArrays(workerScripts[i].args, script.args)) { + workerScripts[i].env.stopFlag = true; + killNetscriptDelay(workerScripts[i]); + removeWorkerScript(workerScripts[i]); + + return true; + } + } + + return false; + } else { + return false; + } +} + +/** + * Helper function that removes the script being killed from the global pool. + * Also handles other cleanup-time operations + * + * @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or + * its index in the global workerScripts array + */ +function removeWorkerScript(id: WorkerScript | number): void { + // Get a reference to the WorkerScript and its index in the global pool + let workerScript: WorkerScript; + let index: number | null = null; + + if (typeof id === "number") { + if (id < 0 || id >= workerScripts.length) { + console.error(`Too high of an index passed into removeWorkerScript(): ${id}`); + return; + } + + workerScript = workerScripts[id]; + index = id; + } else if (id instanceof WorkerScript) { + workerScript = id; + for (let i = 0; i < workerScripts.length; ++i) { + if (workerScripts[i] == id) { + index = i; + break; + } + } + + if (index == null) { + console.error(`Could not find WorkerScript in global pool:`); + console.error(workerScript); + } + } else { + console.error(`Invalid argument passed into removeWorkerScript(): ${id}`); + return; + } + + const ip = workerScript.serverIp; + const name = workerScript.name; + + // Get the server on which the script runs + const server = AllServers[ip]; + if (server == null) { + console.error(`Could not find server on which this script is running: ${ip}`); + return; + } + + // Recalculate ram used on that server + server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage); + if (server.ramUsed < 0) { + console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`); + server.ramUsed = 0; + } + + // Delete the RunningScript object from that server + for (let i = 0; i < server.runningScripts.length; ++i) { + const runningScript = server.runningScripts[i]; + if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) { + server.runningScripts.splice(i, 1); + break; + } + } + + // Delete script from global pool (workerScripts) + workerScripts.splice(index, 1); +} + +/** + * Helper function that interrupts a script's delay if it is in the middle of a + * timed, blocked operation (like hack(), sleep(), etc.). This allows scripts to + * be killed immediately even if they're in the middle of one of those long operations + */ +function killNetscriptDelay(workerScript: WorkerScript) { + if (workerScript instanceof WorkerScript) { + if (workerScript.delay) { + clearTimeout(workerScript.delay); + if (workerScript.delayResolve) { + workerScript.delayResolve(); + } + } + } +} diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index cad189a8e..323919ad4 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -1,21 +1,9 @@ -import { WorkerScript } from "./Netscript/WorkerScript"; -import { getServer } from "./Server/ServerHelpers"; - import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { parse, Node } from "../utils/acorn"; import { isValidIPAddress } from "../utils/helpers/isValidIPAddress"; import { isString } from "../utils/helpers/isString"; -export function killNetscriptDelay(workerScript) { - if (workerScript instanceof WorkerScript) { - if (workerScript.delay) { - clearTimeout(workerScript.delay); - workerScript.delayResolve(); - } - } -} - export function netscriptDelay(time, workerScript) { return new Promise(function(resolve, reject) { workerScript.delay = setTimeoutRef(() => { diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 308054333..e41ff542c 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -120,11 +120,11 @@ import { } from "./NetscriptBladeburner"; import * as nsGang from "./NetscriptGang"; import { - workerScripts, - killWorkerScript, NetscriptPorts, runScriptFromScript, } from "./NetscriptWorker"; +import { killWorkerScript } from "./Netscript/killWorkerScript"; +import { workerScripts } from "./Netscript/WorkerScripts"; import { makeRuntimeRejectMsg, netscriptDelay, diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 9c519e8b1..e5548ee57 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -3,6 +3,7 @@ * that allows for scripts to run */ import { WorkerScript } from "./Netscript/WorkerScript"; +import { workerScripts } from "./Netscript/WorkerScripts"; import { addActiveScriptsItem, @@ -15,7 +16,6 @@ import { Interpreter } from "./JSInterpreter"; import { isScriptErrorMessage, makeRuntimeRejectMsg, - killNetscriptDelay } from "./NetscriptEvaluator"; import { NetscriptFunctions } from "./NetscriptFunctions"; import { executeJSScript } from "./NetscriptJSEvaluator"; @@ -29,6 +29,7 @@ import { } from "./Script/ScriptHelpers"; import { AllServers } from "./Server/AllServers"; import { Settings } from "./Settings/Settings"; +import { EventEmitter } from "./utils/EventEmitter"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { generate } from "escodegen"; @@ -42,14 +43,15 @@ import { isString } from "../utils/StringHelperFunctions"; const walk = require("acorn/dist/walk"); -//Array containing all scripts that are running across all servers, to easily run them all -export const workerScripts = []; - +// Netscript Ports are instantiated here export const NetscriptPorts = []; for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) { NetscriptPorts.push(new NetscriptPort()); } +// WorkerScript-related event emitter. Used for the UI +export const WorkerScriptEventEmitter = new EventEmitter(); + export function prestigeWorkerScripts() { for (var i = 0; i < workerScripts.length; ++i) { deleteActiveScriptsItem(workerScripts[i]); @@ -415,46 +417,6 @@ function processNetscript1Imports(code, workerScript) { // Loop through workerScripts and run every script that is not currently running export function runScriptsLoop() { - let scriptDeleted = false; - - // Delete any scripts that finished or have been killed. Loop backwards bc removing items screws up indexing - for (let i = workerScripts.length - 1; i >= 0; i--) { - if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == true) { - scriptDeleted = true; - // Delete script from the runningScripts array on its host serverIp - const ip = workerScripts[i].serverIp; - const name = workerScripts[i].name; - - // Recalculate ram used - AllServers[ip].ramUsed = 0; - for (let j = 0; j < workerScripts.length; j++) { - if (workerScripts[j].serverIp !== ip) { - continue; - } - if (j === i) { // not this one - continue; - } - AllServers[ip].ramUsed += workerScripts[j].ramUsage; - } - - // Delete script from Active Scripts - deleteActiveScriptsItem(workerScripts[i]); - - for (let j = 0; j < AllServers[ip].runningScripts.length; j++) { - if (AllServers[ip].runningScripts[j].filename == name && - compareArrays(AllServers[ip].runningScripts[j].args, workerScripts[i].args)) { - AllServers[ip].runningScripts.splice(j, 1); - break; - } - } - - // Delete script from workerScripts - workerScripts.splice(i, 1); - } - } - if (scriptDeleted) { updateActiveScriptsItems(); } // Force Update - - // Run any scripts that haven't been started for (let i = 0; i < workerScripts.length; i++) { // If it isn't running, start the script @@ -520,24 +482,6 @@ export function runScriptsLoop() { setTimeoutRef(runScriptsLoop, 3e3); } -/** - * Queues a script to be killed by setting its stop flag to true. This - * kills and timed/blocking Netscript functions (like hack(), sleep(), etc.) and - * prevents any further execution of Netscript functions. - * The runScriptsLoop() handles the actual deletion of the WorkerScript - */ -export function killWorkerScript(runningScriptObj, serverIp) { - for (var i = 0; i < workerScripts.length; i++) { - if (workerScripts[i].name == runningScriptObj.filename && workerScripts[i].serverIp == serverIp && - compareArrays(workerScripts[i].args, runningScriptObj.args)) { - workerScripts[i].env.stopFlag = true; - killNetscriptDelay(workerScripts[i]); - return true; - } - } - return false; -} - /** * Given a RunningScript object, queues that script to be run */ diff --git a/src/Terminal.js b/src/Terminal.js index b88832954..12e150742 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -53,7 +53,8 @@ import { import { showLiterature } from "./Literature"; import { Message } from "./Message/Message"; import { showMessage } from "./Message/MessageHelpers"; -import { killWorkerScript, addWorkerScript } from "./NetscriptWorker"; +import { addWorkerScript } from "./NetscriptWorker"; +import { killWorkerScript } from "./Netscript/killWorkerScript"; import { Player } from "./Player"; import { hackWorldDaemon } from "./RedPill"; import { RunningScript } from "./Script/RunningScript"; diff --git a/src/engine.jsx b/src/engine.jsx index 1e8e8731b..28518e377 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -1,3 +1,8 @@ +/** + * Game engine. Handles the main game loop as well as the main UI pages + * + * TODO: Separate UI functionality into its own component + */ import { convertTimeMsToTimeElapsedString, replaceAt diff --git a/src/engineStyle.js b/src/engineStyle.js index c297b37cc..f9895a77a 100644 --- a/src/engineStyle.js +++ b/src/engineStyle.js @@ -9,6 +9,7 @@ import "../css/characteroverview.scss"; import "../css/terminal.scss"; import "../css/scripteditor.scss"; import "../css/codemirror-overrides.scss"; +import "../css/activescripts.scss"; import "../css/hacknetnodes.scss"; import "../css/menupages.scss"; import "../css/augmentations.scss"; diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx new file mode 100644 index 000000000..aecacb192 --- /dev/null +++ b/src/ui/ActiveScripts/Root.tsx @@ -0,0 +1,31 @@ +/** + * Root React Component for the "Active Scripts" UI page. This page displays + * and provides information about all of the player's scripts that are currently running + */ +import * as React from "react"; + +import { WorkerScript } from "../../Netscript/WorkerScript"; + +type IProps = { + workerScripts: WorkerScript[]; +} + +export class ActiveScriptsRoot extends React.Component { + constructor(props: IProps) { + super(props); + } + + render() { + return ( + <> +

    + This page displays a list of all of your scripts that are currently + running across every machine. It also provides information about each + script's production. The scripts are categorized by the hostname of + the servers on which they are running. +

    + + + ) + } +} diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx new file mode 100644 index 000000000..117c2b045 --- /dev/null +++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx @@ -0,0 +1,40 @@ +/** + * React Component for displaying a single WorkerScript's info as an + * Accordion element + */ +import * as React from "react"; + +import { Accordion } from "../React/Accordion"; + +import { WorkerScript } from "../../Netscript/WorkerScript"; + +import { arrayToString } from "../../../utils/helpers/arrayToString"; + +type IProps = { + workerScript: WorkerScript; +} + +export function WorkerScriptAccordion(props: IProps): React.ReactElement { + + + return ( + + + } + panelClass="active-scripts-script-panel" + panelContent={ + <> +

    + Threads: {props.workerScript.scriptRef.threads} +

    +

    + Args: {arrayToString(props.workerScript.args)} +

    + + } + /> + ) +} diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index 5833f29f7..948039076 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -4,7 +4,9 @@ import * as React from "react"; type IProps = { + headerClass?: string; // Override default class headerContent: React.ReactElement; + panelClass?: string; // Override default class panelContent: React.ReactElement; panelInitiallyOpened?: boolean; } @@ -44,12 +46,21 @@ export class Accordion extends React.Component { } render() { + let className = "accordion-header"; + if (typeof this.props.headerClass === "string") { + className = this.props.headerClass; + } + return ( <> - - + ) } @@ -57,6 +68,7 @@ export class Accordion extends React.Component { type IPanelProps = { opened: boolean; + panelClass?: string; // Override default class panelContent: React.ReactElement; } @@ -66,8 +78,13 @@ class AccordionPanel extends React.Component { } render() { + let className = "accordion-panel" + if (typeof this.props.panelClass === "string") { + className = this.props.panelClass; + } + return ( -
    +
    {this.props.panelContent}
    ) diff --git a/src/utils/EventEmitter.ts b/src/utils/EventEmitter.ts new file mode 100644 index 000000000..b513d76d5 --- /dev/null +++ b/src/utils/EventEmitter.ts @@ -0,0 +1,50 @@ +/** + * Generic Event Emitter class following a subscribe/publish paradigm. + */ +import { IMap } from "../types"; + +type cbFn = (...args: any[]) => any; + +export interface ISubscriber { + /** + * Callback function that will be run when an event is emitted + */ + cb: cbFn; + + /** + * Name/identifier for this subscriber + */ + id: string; +} + +export class EventEmitter { + /** + * Map of Subscriber name -> Callback function + */ + subscribers: IMap = {}; + + constructor(subs?: ISubscriber[]) { + if (Array.isArray(subs)) { + for (const s of subs) { + this.addSubscriber(s); + } + } + } + + addSubscriber(s: ISubscriber) { + this.subscribers[s.id] = s.cb; + } + + emitEvent(...args: any[]): void { + for (const s in this.subscribers) { + const cb = this.subscribers[s]; + + cb(args); + } + } + + removeSubscriber(id: string) { + delete this.subscribers[id]; + } + +} diff --git a/utils/LogBox.js b/utils/LogBox.js index 615567610..cedbeb967 100644 --- a/utils/LogBox.js +++ b/utils/LogBox.js @@ -1,6 +1,6 @@ -import {killWorkerScript} from "../src/NetscriptWorker"; -import {clearEventListeners} from "./uiHelpers/clearEventListeners"; -import {arrayToString} from "./helpers/arrayToString"; +import { killWorkerScript } from "../src/Netscript/killWorkerScript"; +import { clearEventListeners } from "./uiHelpers/clearEventListeners"; +import { arrayToString } from "./helpers/arrayToString"; $(document).keydown(function(event) { if (logBoxOpened && event.keyCode == 27) { From c1ec3c5ebaad833b19a2e7b10bbbc4de82cc082f Mon Sep 17 00:00:00 2001 From: danielyxie Date: Thu, 16 May 2019 23:44:59 -0700 Subject: [PATCH 5/9] Finished refactoring Active Scripts UI into React/TypeScript. Currently untested --- src/ActiveScriptsUI.js | 337 ------------------ src/Netscript/killWorkerScript.ts | 4 +- src/NetscriptFunctions.js | 1 - src/NetscriptWorker.js | 170 +++++---- src/Prestige.js | 1 - src/engine.jsx | 5 - src/ui/ActiveScripts/Root.tsx | 7 + src/ui/ActiveScripts/ScriptProduction.tsx | 45 +++ src/ui/ActiveScripts/ServerAccordion.tsx | 49 +++ src/ui/ActiveScripts/ServerAccordions.tsx | 81 +++++ .../ActiveScripts/WorkerScriptAccordion.tsx | 45 ++- src/ui/React/AccordionButton.tsx | 52 +++ utils/LogBox.js | 68 ---- utils/LogBox.ts | 105 ++++++ 14 files changed, 460 insertions(+), 510 deletions(-) delete mode 100644 src/ActiveScriptsUI.js create mode 100644 src/ui/ActiveScripts/ScriptProduction.tsx create mode 100644 src/ui/ActiveScripts/ServerAccordion.tsx create mode 100644 src/ui/ActiveScripts/ServerAccordions.tsx create mode 100644 src/ui/React/AccordionButton.tsx delete mode 100644 utils/LogBox.js create mode 100644 utils/LogBox.ts diff --git a/src/ActiveScriptsUI.js b/src/ActiveScriptsUI.js deleted file mode 100644 index 998614fb4..000000000 --- a/src/ActiveScriptsUI.js +++ /dev/null @@ -1,337 +0,0 @@ -// TODO - Convert this to React -import { workerScripts, killWorkerScript } from "./NetscriptWorker"; -import { Player } from "./Player"; -import { getServer } from "./Server/ServerHelpers"; - -import { Page, routing } from "./ui/navigationTracking"; -import { numeralWrapper } from "./ui/numeralFormat"; - -import { dialogBoxCreate } from "../utils/DialogBox"; -import { logBoxCreate } from "../utils/LogBox"; -import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; -import { arrayToString } from "../utils/helpers/arrayToString"; -import { createProgressBarText } from "../utils/helpers/createProgressBarText"; -import { exceptionAlert } from "../utils/helpers/exceptionAlert"; -import { roundToTwo } from "../utils/helpers/roundToTwo"; -import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement"; -import { createElement } from "../utils/uiHelpers/createElement"; -import { getElementById } from "../utils/uiHelpers/getElementById"; -import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; -import { removeElement } from "../utils/uiHelpers/removeElement"; - - -/** - * { - * serverName: { - * header: Server Header Element - * panel: Server Panel List (ul) element - * scripts: { - * script id: Ref to Script information - * } - * } - * ... - */ -const ActiveScriptsUI = {}; -const ActiveScriptsTasks = []; // Sequentially schedule the creation/deletion of UI elements - -const getHeaderHtml = (server) => { - // TODO: calculate the longest hostname length rather than hard coding it - const longestHostnameLength = 18; - const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength)); - const barOptions = { - progress: server.ramUsed / server.maxRam, - totalTicks: 30 - }; - return `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' '); -}; - -const updateHeaderHtml = (server) => { - const accordion = ActiveScriptsUI[server.hostname]; - if (accordion === null || accordion === undefined) { - return; - } - - // Convert it to a string, as that's how it's stored it will come out of the data attributes - const ramPercentage = '' + roundToTwo(server.ramUsed / server.maxRam); - if (accordion.header.dataset.ramPercentage !== ramPercentage) { - accordion.header.dataset.ramPercentage = ramPercentage; - accordion.header.innerHTML = getHeaderHtml(server); - } -} - -function createActiveScriptsServerPanel(server) { - let hostname = server.hostname; - - var activeScriptsList = document.getElementById("active-scripts-list"); - - let res = createAccordionElement({ - hdrText: getHeaderHtml(server) - }); - let li = res[0]; - var hdr = res[1]; - let panel = res[2]; - - if (ActiveScriptsUI[hostname] != null) { - console.log("WARNING: Tried to create already-existing Active Scripts Server panel. This is most likely fine. It probably means many scripts just got started up on a new server. Aborting"); - return; - } - - var panelScriptList = createElement("ul"); - panel.appendChild(panelScriptList); - activeScriptsList.appendChild(li); - - ActiveScriptsUI[hostname] = { - header: hdr, - panel: panel, - panelList: panelScriptList, - scripts: {}, // Holds references to li elements for each active script - scriptHdrs: {}, // Holds references to header elements for each active script - scriptStats: {}, // Holds references to the p elements containing text for each active script - }; - - return li; -} - -/** - * Deletes the info for a particular server (Dropdown header + Panel with all info) - * in the Active Scripts page if it exists - */ -function deleteActiveScriptsServerPanel(server) { - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null) { - console.log("WARNING: Tried to delete non-existent Active Scripts Server panel. Aborting"); - return; - } - - // Make sure it's empty - if (Object.keys(ActiveScriptsUI[hostname].scripts).length > 0) { - console.warn("Tried to delete Active Scripts Server panel that still has scripts. Aborting"); - return; - } - - removeElement(ActiveScriptsUI[hostname].panel); - removeElement(ActiveScriptsUI[hostname].header); - delete ActiveScriptsUI[hostname]; -} - -function addActiveScriptsItem(workerscript) { - var server = getServer(workerscript.serverIp); - if (server == null) { - console.warn("Invalid server IP for workerscript in addActiveScriptsItem()"); - return; - } - let hostname = server.hostname; - - ActiveScriptsTasks.push(function(workerscript, hostname) { - if (ActiveScriptsUI[hostname] == null) { - createActiveScriptsServerPanel(server); - } - - // Create the unique identifier (key) for this script - var itemNameArray = ["active", "scripts", hostname, workerscript.name]; - for (var i = 0; i < workerscript.args.length; ++i) { - itemNameArray.push(String(workerscript.args[i])); - } - var itemName = itemNameArray.join("-"); - - let res = createAccordionElement({hdrText:workerscript.name}); - let li = res[0]; - let hdr = res[1]; - let panel = res[2]; - - hdr.classList.remove("accordion-header"); - hdr.classList.add("active-scripts-script-header"); - panel.classList.remove("accordion-panel"); - panel.classList.add("active-scripts-script-panel"); - - /** - * Handle the constant elements on the panel that don't change after creation: - * Threads, args, kill/log button - */ - panel.appendChild(createElement("p", { - innerHTML: "Threads: " + workerscript.scriptRef.threads + "
    " + - "Args: " + arrayToString(workerscript.args) - })); - var panelText = createElement("p", { - innerText: "Loading...", - fontSize: "14px", - }); - panel.appendChild(panelText); - panel.appendChild(createElement("br")); - panel.appendChild(createElement("span", { - innerText: "Log", - class: "accordion-button", - margin: "4px", - padding: "4px", - clickListener: () => { - logBoxCreate(workerscript.scriptRef); - return false; - } - })); - panel.appendChild(createElement("span", { - innerText: "Kill Script", - class: "accordion-button", - margin: "4px", - padding: "4px", - clickListener: () => { - killWorkerScript(workerscript.scriptRef, workerscript.scriptRef.server); - dialogBoxCreate("Killing script, may take a few minutes to complete..."); - return false; - } - })); - - // Append element to list - ActiveScriptsUI[hostname]["panelList"].appendChild(li); - ActiveScriptsUI[hostname].scripts[itemName] = li; - ActiveScriptsUI[hostname].scriptHdrs[itemName] = hdr; - ActiveScriptsUI[hostname].scriptStats[itemName] = panelText; - }.bind(null, workerscript, hostname)); -} - -function deleteActiveScriptsItem(workerscript) { - ActiveScriptsTasks.push(function(workerscript) { - var server = getServer(workerscript.serverIp); - if (server == null) { - throw new Error("ERROR: Invalid server IP for workerscript. This most likely occurred because " + - "you tried to delete a large number of scripts and also deleted servers at the " + - "same time. It's not a big deal, just save and refresh the game."); - return; - } - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null) { - console.log("ERROR: Trying to delete Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname); - return; - } - - var itemNameArray = ["active", "scripts", server.hostname, workerscript.name]; - for (var i = 0; i < workerscript.args.length; ++i) { - itemNameArray.push(String(workerscript.args[i])); - } - var itemName = itemNameArray.join("-"); - - let li = ActiveScriptsUI[hostname].scripts[itemName]; - if (li == null) { - console.log("ERROR: Cannot find Active Script UI element for workerscript: "); - console.log(workerscript); - return; - } - removeElement(li); - delete ActiveScriptsUI[hostname].scripts[itemName]; - delete ActiveScriptsUI[hostname].scriptHdrs[itemName]; - delete ActiveScriptsUI[hostname].scriptStats[itemName]; - if (Object.keys(ActiveScriptsUI[hostname].scripts).length === 0) { - deleteActiveScriptsServerPanel(server); - } - }.bind(null, workerscript)); -} - -function updateActiveScriptsItems(maxTasks=150) { - /** - * Run tasks that need to be done sequentially (adding items, creating/deleting server panels) - * We'll limit this to 150 at a time for performance (in case someone decides to start a - * bunch of scripts all at once...) - */ - const numTasks = Math.min(maxTasks, ActiveScriptsTasks.length); - for (let i = 0; i < numTasks; ++i) { - let task = ActiveScriptsTasks.shift(); - try { - task(); - } catch(e) { - exceptionAlert(e); - console.log(task); - } - } - - let total = 0; - for (var i = 0; i < workerScripts.length; ++i) { - try { - total += updateActiveScriptsItemContent(workerScripts[i]); - } catch(e) { - exceptionAlert(e); - } - } - - if (!routing.isOn(Page.ActiveScripts)) { return total; } - getElementById("active-scripts-total-production-active").innerText = numeralWrapper.formatMoney(total); - getElementById("active-scripts-total-prod-aug-total").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug); - getElementById("active-scripts-total-prod-aug-avg").innerText = numeralWrapper.formatMoney(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000)); - - return total; -} - -function updateActiveScriptsItemContent(workerscript) { - var server = getServer(workerscript.serverIp); - if (server == null) { - console.log("ERROR: Invalid server IP for workerscript in updateActiveScriptsItemContent()."); - return; - } - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null) { - return; // Hasn't been created yet. We'll skip it - } - - updateHeaderHtml(server); - - var itemNameArray = ["active", "scripts", server.hostname, workerscript.name]; - for (var i = 0; i < workerscript.args.length; ++i) { - itemNameArray.push(String(workerscript.args[i])); - } - var itemName = itemNameArray.join("-"); - - if (ActiveScriptsUI[hostname].scriptStats[itemName] == null) { - return; // Hasn't been fully added yet. We'll skip it - } - var item = ActiveScriptsUI[hostname].scriptStats[itemName]; - - // Update the text if necessary. This fn returns the online $/s production - return updateActiveScriptsText(workerscript, item, itemName); -} - -function updateActiveScriptsText(workerscript, item, itemName) { - var server = getServer(workerscript.serverIp); - if (server == null) { - console.log("ERROR: Invalid server IP for workerscript for updateActiveScriptsText()"); - return; - } - let hostname = server.hostname; - if (ActiveScriptsUI[hostname] == null || ActiveScriptsUI[hostname].scriptHdrs[itemName] == null) { - console.log("ERROR: Trying to update Active Script UI Element with a hostname that cant be found in ActiveScriptsUI: " + hostname); - return; - } - - updateHeaderHtml(server); - var onlineMps = workerscript.scriptRef.onlineMoneyMade / workerscript.scriptRef.onlineRunningTime; - - // Only update if the item is visible - if (ActiveScriptsUI[hostname].header.classList.contains("active") === false) {return onlineMps;} - if (ActiveScriptsUI[hostname].scriptHdrs[itemName].classList.contains("active") === false) {return onlineMps;} - - removeChildrenFromElement(item); - - var onlineTime = "Online Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.onlineRunningTime * 1e3); - var offlineTime = "Offline Time: " + convertTimeMsToTimeElapsedString(workerscript.scriptRef.offlineRunningTime * 1e3); - - // Online - var onlineTotalMoneyMade = "Total online production: " + numeralWrapper.formatMoney(workerscript.scriptRef.onlineMoneyMade); - var onlineTotalExpEarned = (Array(26).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " "); - - var onlineMpsText = "Online production rate: " + numeralWrapper.formatMoney(onlineMps) + " / second"; - var onlineEps = workerscript.scriptRef.onlineExpGained / workerscript.scriptRef.onlineRunningTime; - var onlineEpsText = (Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " "); - - // Offline - var offlineTotalMoneyMade = "Total offline production: " + numeralWrapper.formatMoney(workerscript.scriptRef.offlineMoneyMade); - var offlineTotalExpEarned = (Array(27).join(" ") + numeralWrapper.formatBigNumber(workerscript.scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " "); - - var offlineMps = workerscript.scriptRef.offlineMoneyMade / workerscript.scriptRef.offlineRunningTime; - var offlineMpsText = "Offline production rate: " + numeralWrapper.formatMoney(offlineMps) + " / second"; - var offlineEps = workerscript.scriptRef.offlineExpGained / workerscript.scriptRef.offlineRunningTime; - var offlineEpsText = (Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " "); - - item.innerHTML = onlineTime + "
    " + offlineTime + "
    " + onlineTotalMoneyMade + "
    " + onlineTotalExpEarned + "
    " + - onlineMpsText + "
    " + onlineEpsText + "
    " + offlineTotalMoneyMade + "
    " + offlineTotalExpEarned + "
    " + - offlineMpsText + "
    " + offlineEpsText + "
    "; - return onlineMps; -} - -export {addActiveScriptsItem, deleteActiveScriptsItem, updateActiveScriptsItems}; diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index ee60576f8..4c8c3561d 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -1,5 +1,5 @@ /** - * Function that stops an active script (represented by a WorkerScript object) + * Stops an actively-running script (represented by a WorkerScript object) * and removes it from the global pool of active scripts. */ import { WorkerScript } from "./WorkerScript"; @@ -34,6 +34,8 @@ export function killWorkerScript(script: RunningScript | WorkerScript, serverIp? return false; } else { + console.error(`killWorkerScript() called with invalid argument:`); + console.error(script); return false; } } diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index e41ff542c..394dd0156 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -3,7 +3,6 @@ const vsprintf = require("sprintf-js").vsprintf; import { getRamCost } from "./Netscript/RamCostGenerator"; -import { updateActiveScriptsItems } from "./ActiveScriptsUI"; import { Augmentation } from "./Augmentation/Augmentation"; import { Augmentations } from "./Augmentation/Augmentations"; import { diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index e5548ee57..38da9c654 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -2,14 +2,10 @@ * Functions for handling WorkerScripts, which are the underlying mechanism * that allows for scripts to run */ +import { killWorkerScript } from "./Netscript/killWorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript"; import { workerScripts } from "./Netscript/WorkerScripts"; -import { - addActiveScriptsItem, - deleteActiveScriptsItem, - updateActiveScriptsItems -} from "./ActiveScriptsUI"; import { CONSTANTS } from "./Constants"; import { Engine } from "./engine"; import { Interpreter } from "./JSInterpreter"; @@ -54,10 +50,9 @@ export const WorkerScriptEventEmitter = new EventEmitter(); export function prestigeWorkerScripts() { for (var i = 0; i < workerScripts.length; ++i) { - deleteActiveScriptsItem(workerScripts[i]); + // TODO Signal event emitter workerScripts[i].env.stopFlag = true; } - updateActiveScriptsItems(5000); //Force UI to update workerScripts.length = 0; } @@ -415,106 +410,101 @@ function processNetscript1Imports(code, workerScript) { return res; } -// Loop through workerScripts and run every script that is not currently running -export function runScriptsLoop() { - // Run any scripts that haven't been started - for (let i = 0; i < workerScripts.length; i++) { - // If it isn't running, start the script - if (workerScripts[i].running == false && workerScripts[i].env.stopFlag == false) { - let p = null; // p is the script's result promise. - if (workerScripts[i].name.endsWith(".js") || workerScripts[i].name.endsWith(".ns")) { - p = startNetscript2Script(workerScripts[i]); - } else { - p = startNetscript1Script(workerScripts[i]); - if (!(p instanceof Promise)) { continue; } - } - - // Once the code finishes (either resolved or rejected, doesnt matter), set its - // running status to false - p.then(function(w) { - console.log("Stopping script " + w.name + " because it finished running naturally"); - w.running = false; - w.env.stopFlag = true; - w.scriptRef.log("Script finished running"); - }).catch(function(w) { - if (w instanceof Error) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); - return; - } else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") { - // Script ends with a return statement - console.log("Script returning with value: " + w[1]); - // TODO maybe do something with this in the future - return; - } else if (w instanceof WorkerScript) { - if (isScriptErrorMessage(w.errorMessage)) { - var errorTextArray = w.errorMessage.split("|"); - if (errorTextArray.length != 4) { - console.log("ERROR: Something wrong with Error text in evaluator..."); - console.log("Error text: " + errorText); - return; - } - var serverIp = errorTextArray[1]; - var scriptName = errorTextArray[2]; - var errorMsg = errorTextArray[3]; - - dialogBoxCreate("Script runtime error:
    Server Ip: " + serverIp + - "
    Script name: " + scriptName + - "
    Args:" + arrayToString(w.args) + "
    " + errorMsg); - w.scriptRef.log("Script crashed with runtime error"); - } else { - w.scriptRef.log("Script killed"); - } - w.running = false; - w.env.stopFlag = true; - } else if (isScriptErrorMessage(w)) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); - return; - } else { - dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); - console.log(w); - } - }); - } - } - - setTimeoutRef(runScriptsLoop, 3e3); -} - /** - * Given a RunningScript object, queues that script to be run + * Start a script + * + * Given a RunningScript object, constructs a corresponding WorkerScript, + * adds it to the global 'workerScripts' pool, and begins executing it. + * @param {RunningScript} runningScriptObj - Script that's being run + * @param {Server} server - Server on which the script is to be run */ export function addWorkerScript(runningScriptObj, server) { - var filename = runningScriptObj.filename; + const filename = runningScriptObj.filename; - //Update server's ram usage - var threads = 1; + // Update server's ram usage + let threads = 1; if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) { threads = runningScriptObj.threads; } else { runningScriptObj.threads = 1; } - var ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads); - var ramAvailable = server.maxRam - server.ramUsed; + const ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads); + const ramAvailable = server.maxRam - server.ramUsed; if (ramUsage > ramAvailable) { - dialogBoxCreate("Not enough RAM to run script " + runningScriptObj.filename + " with args " + - arrayToString(runningScriptObj.args) + ". This likely occurred because you re-loaded " + - "the game and the script's RAM usage increased (either because of an update to the game or " + - "your changes to the script.)"); + dialogBoxCreate( + `Not enough RAM to run script ${runningScriptObj.filename} with args ` + + `${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` + + `the game and the script's RAM usage increased (either because of an update to the game or ` + + `your changes to the script.)` + ); return; } server.ramUsed = roundToTwo(server.ramUsed + ramUsage); - //Create the WorkerScript - var s = new WorkerScript(runningScriptObj, NetscriptFunctions); + // Create the WorkerScript + const s = new WorkerScript(runningScriptObj, NetscriptFunctions); s.ramUsage = ramUsage; - //Add the WorkerScript to the Active Scripts list - addActiveScriptsItem(s); + // Start the script's execution + let p = null; // Script's resulting promise + if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { + p = startNetscript2Script(workerScripts[i]); + } else { + p = startNetscript1Script(workerScripts[i]); + if (!(p instanceof Promise)) { return; } + } - //Add the WorkerScript - workerScripts.push(s); + // Once the code finishes (either resolved or rejected, doesnt matter), set its + // running status to false + p.then(function(w) { + console.log("Stopping script " + w.name + " because it finished running naturally"); + killWorkerScript(s); + w.scriptRef.log("Script finished running"); + }).catch(function(w) { + if (w instanceof Error) { + dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); + console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); + return; + } else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") { + // Script ends with a return statement + console.log("Script returning with value: " + w[1]); + // TODO maybe do something with this in the future + return; + } else if (w instanceof WorkerScript) { + if (isScriptErrorMessage(w.errorMessage)) { + var errorTextArray = w.errorMessage.split("|"); + if (errorTextArray.length != 4) { + console.log("ERROR: Something wrong with Error text in evaluator..."); + console.log("Error text: " + errorText); + return; + } + var serverIp = errorTextArray[1]; + var scriptName = errorTextArray[2]; + var errorMsg = errorTextArray[3]; + + dialogBoxCreate("Script runtime error:
    Server Ip: " + serverIp + + "
    Script name: " + scriptName + + "
    Args:" + arrayToString(w.args) + "
    " + errorMsg); + w.scriptRef.log("Script crashed with runtime error"); + } else { + w.scriptRef.log("Script killed"); + } + w.running = false; + w.env.stopFlag = true; + } else if (isScriptErrorMessage(w)) { + dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); + console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); + return; + } else { + dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); + console.log(w); + } + + killWorkerScript(s); + }); + + // Add the WorkerScript to the global pool + workerScripts.push(s); return; } diff --git a/src/Prestige.js b/src/Prestige.js index 6e3dc4947..ae875e366 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -1,4 +1,3 @@ -import { deleteActiveScriptsItem } from "./ActiveScriptsUI"; import { Augmentations } from "./Augmentation/Augmentations"; import { augmentationExists, diff --git a/src/engine.jsx b/src/engine.jsx index 28518e377..9ed84ce4f 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -8,7 +8,6 @@ import { replaceAt } from "../utils/StringHelperFunctions"; import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox"; -import { updateActiveScriptsItems } from "./ActiveScriptsUI"; import { Augmentations } from "./Augmentation/Augmentations"; import { initAugmentations, @@ -48,7 +47,6 @@ import { checkForMessagesToSend, initMessages } from "./Message/MessageHelpers"; import { inMission, currMission } from "./Missions"; import { loadAllRunningScripts, - runScriptsLoop, updateOnlineScriptTimes, } from "./NetscriptWorker"; import { Player } from "./Player"; @@ -1520,9 +1518,6 @@ const Engine = { start: function() { // Run main loop Engine.idleTimer(); - - // Script-processing loop - runScriptsLoop(); } }; diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx index aecacb192..193363017 100644 --- a/src/ui/ActiveScripts/Root.tsx +++ b/src/ui/ActiveScripts/Root.tsx @@ -4,9 +4,14 @@ */ import * as React from "react"; +import { ScriptProduction } from "./ScriptProduction"; +import { ServerAccordions } from "./ServerAccordions"; + import { WorkerScript } from "../../Netscript/WorkerScript"; +import { IPlayer } from "../../PersonObjects/IPlayer"; type IProps = { + p: IPlayer; workerScripts: WorkerScript[]; } @@ -25,6 +30,8 @@ export class ActiveScriptsRoot extends React.Component { the servers on which they are running.

    + + ) } diff --git a/src/ui/ActiveScripts/ScriptProduction.tsx b/src/ui/ActiveScripts/ScriptProduction.tsx new file mode 100644 index 000000000..a348b82cb --- /dev/null +++ b/src/ui/ActiveScripts/ScriptProduction.tsx @@ -0,0 +1,45 @@ +/** + * React Component for displaying the total production and production rate + * of scripts on the 'Active Scripts' UI page + */ +import * as React from "react"; + +import { numeralWrapper } from "../numeralFormat"; + +import { WorkerScript } from "../../Netscript/WorkerScript"; +import { IPlayer } from "../../PersonObjects/IPlayer"; + +type IProps = { + p: IPlayer; + workerScripts: WorkerScript[]; +} + +export function ScriptProduction(props: IProps): React.ReactElement { + const prodRateSinceLastAug = props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000); + + let onlineProduction = 0; + for (const ws of props.workerScripts) { + onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime); + } + + return ( +

    + Total online production of Active scripts: + + + {numeralWrapper.formatMoney(onlineProduction)} + / sec +
    + + Total online production since last Aug installation: + + {numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)} + + ( + + {numeralWrapper.formatMoney(prodRateSinceLastAug)} + / sec + ) +

    + ) +} diff --git a/src/ui/ActiveScripts/ServerAccordion.tsx b/src/ui/ActiveScripts/ServerAccordion.tsx new file mode 100644 index 000000000..9f49a88f3 --- /dev/null +++ b/src/ui/ActiveScripts/ServerAccordion.tsx @@ -0,0 +1,49 @@ +/** + * React Component for rendering the Accordion element for a single + * server in the 'Active Scripts' UI page + */ +import * as React from "react"; + +import { WorkerScriptAccordion } from "./WorkerScriptAccordion"; +import { Accordion } from "../React/Accordion"; + +import { BaseServer } from "../../Server/BaseServer"; +import { WorkerScript } from "../../Netscript/WorkerScript"; + +import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; + +type IProps = { + server: BaseServer; + workerScripts: WorkerScript[]; +} + +export function ServerAccordion(props: IProps): React.ReactElement { + const server = props.server; + + // Accordion's header text + // TODO: calculate the longest hostname length rather than hard coding it + const longestHostnameLength = 18; + const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength)); + const barOptions = { + progress: server.ramUsed / server.maxRam, + totalTicks: 30 + }; + const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' '); + + const scripts = props.workerScripts.map((ws) => { + return ( + + ) + }); + + return ( + {headerTxt}

    + } + panelContent={ +
      {scripts}
    + } + /> + ) +} diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx new file mode 100644 index 000000000..1a0dbcd9c --- /dev/null +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -0,0 +1,81 @@ +/** + * React Component for rendering the Accordion elements for all servers + * on which scripts are running + */ +import * as React from "react"; + +import { ServerAccordion } from "./ServerAccordion"; + +import { getServer } from "../../Server/ServerHelpers"; +import { BaseServer } from "../../Server/BaseServer"; +import { WorkerScript } from "../../Netscript/WorkerScript"; + +// Map of server hostname -> all workerscripts on that server for all active scripts +interface IServerData { + server: BaseServer; + workerScripts: WorkerScript[]; +} + +interface IServerToScriptsMap { + [key: string]: IServerData; +} + +type IProps = { + workerScripts: WorkerScript[]; +}; + +export class ServerAccordions extends React.Component { + serverToScriptMap: IServerToScriptsMap = {}; + + constructor(props: IProps) { + super(props); + + this.updateServerToScriptsMap(); + + // TODO + // We subscribe to an event emitter that publishes whenever a script is + // started/stopped. This allows us to only update the map when necessary + } + + updateServerToScriptsMap(): void { + const map: IServerToScriptsMap = {}; + + for (const ws of this.props.workerScripts) { + const server = getServer(ws.serverIp); + if (server == null) { + console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`); + continue; + } + + if (map[server.hostname] == null) { + map[server.hostname] = { + server: server, + workerScripts: [], + }; + } + + map[server.hostname].workerScripts.push(ws); + } + + this.serverToScriptMap = map; + } + + render() { + const elems = Object.keys(this.serverToScriptMap).map((serverName) => { + const data = this.serverToScriptMap[serverName]; + return ( + + ) + }); + + return ( +
      + {elems} +
    + ) + } +} diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx index 117c2b045..2f346220b 100644 --- a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx +++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx @@ -4,10 +4,16 @@ */ import * as React from "react"; -import { Accordion } from "../React/Accordion"; +import { numeralWrapper } from "../numeralFormat"; +import { Accordion } from "../React/Accordion"; +import { AccordionButton } from "../React/AccordionButton"; + +import { killWorkerScript } from "../../Netscript/killWorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript"; +import { logBoxCreate } from "../../../utils/LogBox"; +import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { arrayToString } from "../../../utils/helpers/arrayToString"; type IProps = { @@ -15,7 +21,17 @@ type IProps = { } export function WorkerScriptAccordion(props: IProps): React.ReactElement { + const workerScript = props.workerScript; + const scriptRef = workerScript.scriptRef; + const logClickHandler = logBoxCreate.bind(null, scriptRef); + const killScriptButton = killWorkerScript.bind(null, scriptRef, scriptRef.server); + + // Calculations for script stats + const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime; + const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime; + const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime; + const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime; return ( -

    - Threads: {props.workerScript.scriptRef.threads} -

    -

    - Args: {arrayToString(props.workerScript.args)} -

    +

    Threads: {props.workerScript.scriptRef.threads}

    +

    Args: {arrayToString(props.workerScript.args)}

    +

    Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}

    +

    Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}

    +

    Total online production: {numeralWrapper.formatMoney(scriptRef.onlineMoneyMade)}

    +

    {(Array(26).join(" ") + numeralWrapper.formatBigNumber(scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " ")}

    +

    Online production rate: {numeralWrapper.formatMoney(onlineMps)} / second

    +

    {(Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " ")}

    +

    Total offline production: {numeralWrapper.formatMoney(scriptRef.offlineMoneyMade)}

    +

    {(Array(27).join(" ") + numeralWrapper.formatBigNumber(scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " ")}

    +

    Offline production rate: {numeralWrapper.formatMoney(offlineMps)} / second

    +

    {(Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " ")}

    + + + } /> diff --git a/src/ui/React/AccordionButton.tsx b/src/ui/React/AccordionButton.tsx new file mode 100644 index 000000000..f60c6b821 --- /dev/null +++ b/src/ui/React/AccordionButton.tsx @@ -0,0 +1,52 @@ +/** + * Basic stateless button that uses the 'accordion-button' css class. + * This class has a black background so that it does not clash with the default + * accordion coloring + */ +import * as React from "react"; + +interface IProps { + addClasses?: string; + disabled?: boolean; + id?: string; + onClick?: (e: React.MouseEvent) => any; + style?: object; + text: string; + tooltip?: string; +} + +type IInnerHTMLMarkup = { + __html: string; +} + +export function AccordionButton(props: IProps): React.ReactElement { + const hasTooltip = props.tooltip != null && props.tooltip !== ""; + + // TODO Add a disabled class for accordion buttons? + let className = "accordion-button"; + if (hasTooltip) { + className += " tooltip"; + } + + if (typeof props.addClasses === "string") { + className += ` ${props.addClasses}`; + } + + // Tooltip will be set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup | null; + if (hasTooltip) { + tooltipMarkup = { + __html: props.tooltip! + } + } + + return ( + + ) +} diff --git a/utils/LogBox.js b/utils/LogBox.js deleted file mode 100644 index cedbeb967..000000000 --- a/utils/LogBox.js +++ /dev/null @@ -1,68 +0,0 @@ -import { killWorkerScript } from "../src/Netscript/killWorkerScript"; -import { clearEventListeners } from "./uiHelpers/clearEventListeners"; -import { arrayToString } from "./helpers/arrayToString"; - -$(document).keydown(function(event) { - if (logBoxOpened && event.keyCode == 27) { - logBoxClose(); - } -}); - -function logBoxInit() { - var closeButton = document.getElementById("log-box-close"); - logBoxClose(); - - //Close Dialog box - closeButton.addEventListener("click", function() { - logBoxClose(); - return false; - }); - document.getElementById("log-box-text-header").style.display = "inline-block"; -}; - -document.addEventListener("DOMContentLoaded", logBoxInit, false); - -function logBoxClose() { - logBoxOpened = false; - var logBox = document.getElementById("log-box-container"); - logBox.style.display = "none"; -} - -function logBoxOpen() { - logBoxOpened = true; - - var logBox = document.getElementById("log-box-container"); - logBox.style.display = "block"; -} - - -var logBoxOpened = false; -var logBoxCurrentScript = null; -function logBoxCreate(script) { - logBoxCurrentScript = script; - var killScriptBtn = clearEventListeners("log-box-kill-script"); - killScriptBtn.addEventListener("click", ()=>{ - killWorkerScript(script, script.server); - return false; - }); - document.getElementById('log-box-kill-script').style.display = "inline-block"; - logBoxOpen(); - document.getElementById("log-box-text-header").innerHTML = - logBoxCurrentScript.filename + " " + arrayToString(logBoxCurrentScript.args) + ":

    "; - logBoxCurrentScript.logUpd = true; - logBoxUpdateText(); -} - -function logBoxUpdateText() { - var txt = document.getElementById("log-box-text"); - if (logBoxCurrentScript && logBoxOpened && txt && logBoxCurrentScript.logUpd) { - txt.innerHTML = ""; - for (var i = 0; i < logBoxCurrentScript.logs.length; ++i) { - txt.innerHTML += logBoxCurrentScript.logs[i]; - txt.innerHTML += "
    "; - } - logBoxCurrentScript.logUpd = false; - } -} - -export {logBoxCreate, logBoxUpdateText, logBoxOpened, logBoxCurrentScript}; diff --git a/utils/LogBox.ts b/utils/LogBox.ts new file mode 100644 index 000000000..c22542958 --- /dev/null +++ b/utils/LogBox.ts @@ -0,0 +1,105 @@ +import { killWorkerScript } from "../src/Netscript/killWorkerScript"; +import { RunningScript } from "../src/Script/RunningScript"; + +import { clearEventListeners } from "./uiHelpers/clearEventListeners"; +import { arrayToString } from "./helpers/arrayToString"; + +import { KEY } from "./helpers/keyCodes"; + +document.addEventListener("keydown", function(event: KeyboardEvent) { + if (logBoxOpened && event.keyCode == KEY.ESC) { + logBoxClose(); + } +}); + +let logBoxContainer: HTMLElement | null; +let textHeader: HTMLElement | null; +let logText: HTMLElement | null; + +function logBoxInit(): void { + // Initialize Close button click listener + const closeButton = document.getElementById("log-box-close"); + if (closeButton == null) { + console.error(`Could not find LogBox's close button`); + return; + } + logBoxClose(); + + closeButton.addEventListener("click", function() { + logBoxClose(); + return false; + }); + + // Initialize text header + textHeader = document.getElementById("log-box-text-header"); + if (textHeader instanceof HTMLElement) { + textHeader.style.display = "inline-block"; + } + + // Initialize references to other DOM elements + logBoxContainer = document.getElementById("log-box-container"); + logText = document.getElementById("log-box-text"); + + document.removeEventListener("DOMContentLoaded", logBoxInit); +}; + +document.addEventListener("DOMContentLoaded", logBoxInit); + +function logBoxClose() { + logBoxOpened = false; + if (logBoxContainer instanceof HTMLElement) { + logBoxContainer.style.display = "none"; + } +} + +function logBoxOpen() { + logBoxOpened = true; + + if (logBoxContainer instanceof HTMLElement) { + logBoxContainer.style.display = "block"; + } +} + + +export let logBoxOpened = false; +let logBoxCurrentScript: RunningScript | null = null; +export function logBoxCreate(script: RunningScript) { + logBoxCurrentScript = script; + + const killScriptBtn = clearEventListeners("log-box-kill-script"); + if (killScriptBtn == null) { + console.error(`Could not find LogBox's 'Kill Script' button`); + return; + } + + killScriptBtn.addEventListener("click", () => { + killWorkerScript(script, script.server); + return false; + }); + + killScriptBtn.style.display = "inline-block"; + + logBoxOpen(); + + if (textHeader instanceof HTMLElement) { + textHeader.innerHTML = `${logBoxCurrentScript.filename} ${arrayToString(logBoxCurrentScript.args)}:

    `; + } else { + console.warn(`LogBox's Text Header DOM element is null`); + } + + logBoxCurrentScript.logUpd = true; + logBoxUpdateText(); +} + +export function logBoxUpdateText() { + if (!(logText instanceof HTMLElement)) { return; } + + if (logBoxCurrentScript && logBoxOpened && logBoxCurrentScript.logUpd) { + logText.innerHTML = ""; + for (let i = 0; i < logBoxCurrentScript.logs.length; ++i) { + logText.innerHTML += logBoxCurrentScript.logs[i]; + logText.innerHTML += "
    "; + } + logBoxCurrentScript.logUpd = false; + } +} From 3b7f9c9fb0fee86b5bf40fd504efa3e89ed42549 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Fri, 17 May 2019 13:41:16 -0700 Subject: [PATCH 6/9] Fixed issues with Active Scripts UI. Implemented event emitter for Active Scripts UI --- css/activescripts.scss | 77 ++++++++++--------- css/styles.scss | 2 +- .../WorkerScriptStartStopEventEmitter.ts | 6 ++ src/Netscript/killWorkerScript.ts | 2 + src/NetscriptWorker.js | 13 ++-- src/Script/RunningScript.ts | 12 +-- src/Script/Script.ts | 31 +++++--- src/engine.jsx | 22 ++++-- src/ui/ActiveScripts/Root.tsx | 2 +- src/ui/ActiveScripts/ScriptProduction.tsx | 1 + src/ui/ActiveScripts/ServerAccordion.tsx | 4 +- src/ui/ActiveScripts/ServerAccordions.tsx | 38 +++++++-- .../ActiveScripts/WorkerScriptAccordion.tsx | 38 +++++---- src/ui/React/Accordion.tsx | 1 + utils/LogBox.ts | 3 +- 15 files changed, 160 insertions(+), 92 deletions(-) create mode 100644 src/Netscript/WorkerScriptStartStopEventEmitter.ts diff --git a/css/activescripts.scss b/css/activescripts.scss index 82b7ae7b9..8dbfbc2e3 100644 --- a/css/activescripts.scss +++ b/css/activescripts.scss @@ -13,6 +13,12 @@ margin: 6px; padding: 4px; } + + .accordion-header { + > pre { + color: white; + } + } } .active-scripts-server-header { @@ -26,31 +32,32 @@ text-align: left; border: none; outline: none; + + &:after { + content: '\02795'; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; + color: #fff; + float: right; + margin-left: 5px; + } + + &.active, &:hover { + background-color: #555; + } } -.active-scripts-server-header.active, -.active-scripts-server-header:hover { - background-color: #555; -} +.active-scripts-server-header.active { + &:after { + content: "\2796"; /* "minus" sign (-) */ + font-size: $defaultFontSize * 0.8125; + color: #fff; + float: right; + margin-left: 5px; + } -.active-scripts-server-header.active:hover { - background-color: #666; -} - -.active-scripts-server-header:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; -} - -.active-scripts-server-header.active:after { - content: "\2796"; /* "minus" sign (-) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; + &:hover { + background-color: #666; + } } .active-scripts-server-panel { @@ -59,24 +66,23 @@ width: 55%; margin-left: 5%; display: none; -} -.active-scripts-server-panel div, -.active-scripts-server-panel ul, -.active-scripts-server-panel ul > li { - background-color: #555; + div, ul, ul > li { + background-color: #555; + } } .active-scripts-script-header { background-color: #555; - color: var(--my-font-color); - padding: 4px 25px 4px 10px; - cursor: pointer; - width: auto; - text-align: left; border: none; + color: var(--my-font-color); + cursor: pointer; + display: block; outline: none; + padding: 4px 25px 4px 10px; position: relative; + text-align: left; + width: auto; &:after { content: '\02795'; /* "plus" sign (+) */ @@ -104,13 +110,14 @@ } .active-scripts-script-panel { - padding: 0 18px; background-color: #555; - width: auto; display: none; + font-size: 14px; margin-bottom: 6px; + padding: 0 18px; + width: auto; - p, h2, ul, li { + pre, h2, ul, li { background-color: #555; width: auto; color: #fff; diff --git a/css/styles.scss b/css/styles.scss index 563a343de..febdf156a 100644 --- a/css/styles.scss +++ b/css/styles.scss @@ -243,8 +243,8 @@ a:visited { /* Accordion menus (Header with collapsible panel) */ .accordion-header { background-color: #444; - font-size: $defaultFontSize * 1.25; color: #fff; + font-size: $defaultFontSize * 1.25; margin: 6px 6px 0 6px; padding: 4px 6px; cursor: pointer; diff --git a/src/Netscript/WorkerScriptStartStopEventEmitter.ts b/src/Netscript/WorkerScriptStartStopEventEmitter.ts new file mode 100644 index 000000000..8fdb922b8 --- /dev/null +++ b/src/Netscript/WorkerScriptStartStopEventEmitter.ts @@ -0,0 +1,6 @@ +/** + * Event emitter that triggers when scripts are started/stopped + */ +import { EventEmitter } from "../utils/EventEmitter"; + +export const WorkerScriptStartStopEventEmitter = new EventEmitter(); diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index 4c8c3561d..59abd4e93 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -4,6 +4,7 @@ */ import { WorkerScript } from "./WorkerScript"; import { workerScripts } from "./WorkerScripts"; +import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter"; import { RunningScript } from "../Script/RunningScript"; import { AllServers } from "../Server/AllServers"; @@ -106,6 +107,7 @@ function removeWorkerScript(id: WorkerScript | number): void { // Delete script from global pool (workerScripts) workerScripts.splice(index, 1); + WorkerScriptStartStopEventEmitter.emitEvent(); } /** diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 38da9c654..319693358 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -5,6 +5,7 @@ import { killWorkerScript } from "./Netscript/killWorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript"; import { workerScripts } from "./Netscript/WorkerScripts"; +import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { CONSTANTS } from "./Constants"; import { Engine } from "./engine"; @@ -25,7 +26,6 @@ import { } from "./Script/ScriptHelpers"; import { AllServers } from "./Server/AllServers"; import { Settings } from "./Settings/Settings"; -import { EventEmitter } from "./utils/EventEmitter"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { generate } from "escodegen"; @@ -45,9 +45,6 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) { NetscriptPorts.push(new NetscriptPort()); } -// WorkerScript-related event emitter. Used for the UI -export const WorkerScriptEventEmitter = new EventEmitter(); - export function prestigeWorkerScripts() { for (var i = 0; i < workerScripts.length; ++i) { // TODO Signal event emitter @@ -138,7 +135,7 @@ function startNetscript2Script(workerScript) { } function startNetscript1Script(workerScript) { - var code = workerScript.code; + const code = workerScript.code; workerScript.running = true; //Process imports @@ -448,9 +445,9 @@ export function addWorkerScript(runningScriptObj, server) { // Start the script's execution let p = null; // Script's resulting promise if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { - p = startNetscript2Script(workerScripts[i]); + p = startNetscript2Script(s); } else { - p = startNetscript1Script(workerScripts[i]); + p = startNetscript1Script(s); if (!(p instanceof Promise)) { return; } } @@ -488,6 +485,7 @@ export function addWorkerScript(runningScriptObj, server) { w.scriptRef.log("Script crashed with runtime error"); } else { w.scriptRef.log("Script killed"); + return; // Already killed, so stop here } w.running = false; w.env.stopFlag = true; @@ -505,6 +503,7 @@ export function addWorkerScript(runningScriptObj, server) { // Add the WorkerScript to the global pool workerScripts.push(s); + WorkerScriptStartStopEventEmitter.emitEvent(); return; } diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index be26ddba8..eca819276 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -1,5 +1,7 @@ -// Class representing a Script instance that is actively running. -// A Script can have multiple active instances +/** + * Class representing a Script instance that is actively running. + * A Script can have multiple active instances + */ import { Script } from "./Script"; import { FconfSettings } from "../Fconf/FconfSettings"; import { Settings } from "../Settings/Settings"; @@ -22,10 +24,8 @@ export class RunningScript { // Script arguments args: any[] = []; - // Holds a map of servers hacked, where server = key and the value for each - // server is an array of four numbers. The four numbers represent: - // [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - // This data is used for offline progress + // Map of [key: server ip] -> Hacking data. Used for offline progress calculations. + // Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] dataMap: IMap = {}; // Script filename diff --git a/src/Script/Script.ts b/src/Script/Script.ts index f700b0ef1..cb3200a6c 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -1,6 +1,9 @@ -// Class representing a script file -// This does NOT represent a script that is actively running and -// being evaluated. See RunningScript for that +/** + * Class representing a script file. + * + * This does NOT represent a script that is actively running and + * being evaluated. See RunningScript for that + */ import { calculateRamUsage } from "./RamCalculations"; import { Page, routing } from "../ui/navigationTracking"; @@ -34,7 +37,6 @@ export class Script { // IP of server that this script is on. server: string = ""; - constructor(fn: string="", code: string="", server: string="", otherScripts: Script[]=[]) { this.filename = fn; this.code = code; @@ -44,6 +46,9 @@ export class Script { if (this.code !== "") { this.updateRamUsage(otherScripts); } }; + /** + * Download the script as a file + */ download(): void { const filename = this.filename + ".js"; const file = new Blob([this.code], {type: 'text/plain'}); @@ -63,10 +68,14 @@ export class Script { } } - // Save a script FROM THE SCRIPT EDITOR + /** + * Save a script from the script editor + * @param {string} code - The new contents of the script + * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports + */ saveScript(code: string, serverIp: string, otherScripts: Script[]): void { if (routing.isOn(Page.ScriptEditor)) { - //Update code and filename + // Update code and filename this.code = code.replace(/^\s+|\s+$/g, ''); const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement; @@ -75,18 +84,16 @@ export class Script { return; } this.filename = filenameElem!.value; - - // Server this.server = serverIp; - - //Calculate/update ram usage, execution time, etc. this.updateRamUsage(otherScripts); - this.module = ""; } } - // Updates the script's RAM usage based on its code + /** + * Calculates and updates the script's RAM usage based on its code + * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports + */ async updateRamUsage(otherScripts: Script[]) { var res = await calculateRamUsage(this.code, otherScripts); if (res > 0) { diff --git a/src/engine.jsx b/src/engine.jsx index 9ed84ce4f..20f2ff35b 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -45,6 +45,7 @@ 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, @@ -90,6 +91,8 @@ import { displayCharacterInfo } from "./ui/displayCharacterInfo"; import { Page, routing } from "./ui/navigationTracking"; import { numeralWrapper } from "./ui/numeralFormat"; import { setSettingsLabels } from "./ui/setSettingsLabels"; + +import { ActiveScriptsRoot } from "./ui/ActiveScripts/Root"; import { initializeMainMenuHeaders } from "./ui/MainMenu/Headers"; import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links"; @@ -262,7 +265,10 @@ const Engine = { Engine.hideAllContent(); Engine.Display.activeScriptsContent.style.display = "block"; routing.navigateTo(Page.ActiveScripts); - updateActiveScriptsItems(); + ReactDOM.render( + , + Engine.Display.activeScriptsContent + ) MainMenuLinks.ActiveScripts.classList.add("active"); }, @@ -474,7 +480,10 @@ const Engine = { Engine.Display.terminalContent.style.display = "none"; Engine.Display.characterContent.style.display = "none"; Engine.Display.scriptEditorContent.style.display = "none"; + Engine.Display.activeScriptsContent.style.display = "none"; + ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent); + clearHacknetNodesUI(); Engine.Display.createProgramContent.style.display = "none"; @@ -805,13 +814,14 @@ const Engine = { } if (Engine.Counters.updateActiveScriptsDisplay <= 0) { - // Always update, but make the interval longer if the page isn't active - updateActiveScriptsItems(); if (routing.isOn(Page.ActiveScripts)) { - Engine.Counters.updateActiveScriptsDisplay = 5; - } else { - Engine.Counters.updateActiveScriptsDisplay = 10; + ReactDOM.render( + , + Engine.Display.activeScriptsContent + ) } + + Engine.Counters.updateActiveScriptsDisplay = 5; } if (Engine.Counters.updateDisplays <= 0) { diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx index 193363017..2dd6e78d0 100644 --- a/src/ui/ActiveScripts/Root.tsx +++ b/src/ui/ActiveScripts/Root.tsx @@ -15,7 +15,7 @@ type IProps = { workerScripts: WorkerScript[]; } -export class ActiveScriptsRoot extends React.Component { +export class ActiveScriptsRoot extends React.Component { constructor(props: IProps) { super(props); } diff --git a/src/ui/ActiveScripts/ScriptProduction.tsx b/src/ui/ActiveScripts/ScriptProduction.tsx index a348b82cb..66da02a01 100644 --- a/src/ui/ActiveScripts/ScriptProduction.tsx +++ b/src/ui/ActiveScripts/ScriptProduction.tsx @@ -35,6 +35,7 @@ export function ScriptProduction(props: IProps): React.ReactElement { {numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)} + ( {numeralWrapper.formatMoney(prodRateSinceLastAug)} diff --git a/src/ui/ActiveScripts/ServerAccordion.tsx b/src/ui/ActiveScripts/ServerAccordion.tsx index 9f49a88f3..9a0d1c57f 100644 --- a/src/ui/ActiveScripts/ServerAccordion.tsx +++ b/src/ui/ActiveScripts/ServerAccordion.tsx @@ -28,7 +28,7 @@ export function ServerAccordion(props: IProps): React.ReactElement { progress: server.ramUsed / server.maxRam, totalTicks: 30 }; - const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`.replace(/\s/g, ' '); + const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`; const scripts = props.workerScripts.map((ws) => { return ( @@ -39,7 +39,7 @@ export function ServerAccordion(props: IProps): React.ReactElement { return ( {headerTxt}

    +
    {headerTxt}
    } panelContent={
      {scripts}
    diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx index 1a0dbcd9c..554d69317 100644 --- a/src/ui/ActiveScripts/ServerAccordions.tsx +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -6,9 +6,10 @@ import * as React from "react"; import { ServerAccordion } from "./ServerAccordion"; +import { WorkerScript } from "../../Netscript/WorkerScript"; +import { WorkerScriptStartStopEventEmitter } from "../../Netscript/WorkerScriptStartStopEventEmitter"; import { getServer } from "../../Server/ServerHelpers"; import { BaseServer } from "../../Server/BaseServer"; -import { WorkerScript } from "../../Netscript/WorkerScript"; // Map of server hostname -> all workerscripts on that server for all active scripts interface IServerData { @@ -24,17 +25,37 @@ type IProps = { workerScripts: WorkerScript[]; }; -export class ServerAccordions extends React.Component { +type IState = { + rerenderFlag: boolean; +} + + +const subscriberId = "ActiveScriptsUI"; + +export class ServerAccordions extends React.Component { serverToScriptMap: IServerToScriptsMap = {}; constructor(props: IProps) { super(props); + this.state = { + rerenderFlag: false, + } + this.updateServerToScriptsMap(); - // TODO - // We subscribe to an event emitter that publishes whenever a script is - // started/stopped. This allows us to only update the map when necessary + this.rerender = this.rerender.bind(this); + } + + componentDidMount() { + WorkerScriptStartStopEventEmitter.addSubscriber({ + cb: this.rerender, + id: subscriberId, + }) + } + + componentWillUnmount() { + WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId); } updateServerToScriptsMap(): void { @@ -60,6 +81,13 @@ export class ServerAccordions extends React.Component { this.serverToScriptMap = map; } + rerender() { + this.updateServerToScriptsMap(); + this.setState((prevState) => { + return { rerenderFlag: !prevState.rerenderFlag } + }); + } + render() { const elems = Object.keys(this.serverToScriptMap).map((serverName) => { const data = this.serverToScriptMap[serverName]; diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx index 2f346220b..bbf522596 100644 --- a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx +++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx @@ -12,6 +12,7 @@ import { AccordionButton } from "../React/AccordionButton"; import { killWorkerScript } from "../../Netscript/killWorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; import { logBoxCreate } from "../../../utils/LogBox"; import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { arrayToString } from "../../../utils/helpers/arrayToString"; @@ -24,8 +25,14 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement { const workerScript = props.workerScript; const scriptRef = workerScript.scriptRef; + const logClickHandler = logBoxCreate.bind(null, scriptRef); - const killScriptButton = killWorkerScript.bind(null, scriptRef, scriptRef.server); + const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server); + + function killScriptClickHandler() { + killScript(); + dialogBoxCreate("Killing script"); + } // Calculations for script stats const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime; @@ -37,31 +44,30 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement { - + <>{props.workerScript.name} } panelClass="active-scripts-script-panel" panelContent={ <> -

    Threads: {props.workerScript.scriptRef.threads}

    -

    Args: {arrayToString(props.workerScript.args)}

    -

    Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}

    -

    Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}

    -

    Total online production: {numeralWrapper.formatMoney(scriptRef.onlineMoneyMade)}

    -

    {(Array(26).join(" ") + numeralWrapper.formatBigNumber(scriptRef.onlineExpGained) + " hacking exp").replace( / /g, " ")}

    -

    Online production rate: {numeralWrapper.formatMoney(onlineMps)} / second

    -

    {(Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second").replace( / /g, " ")}

    -

    Total offline production: {numeralWrapper.formatMoney(scriptRef.offlineMoneyMade)}

    -

    {(Array(27).join(" ") + numeralWrapper.formatBigNumber(scriptRef.offlineExpGained) + " hacking exp").replace( / /g, " ")}

    -

    Offline production rate: {numeralWrapper.formatMoney(offlineMps)} / second

    -

    {(Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) + " hacking exp / second").replace( / /g, " ")}

    +
    Threads: {props.workerScript.scriptRef.threads}
    +
    Args: {arrayToString(props.workerScript.args)}
    +
    Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}
    +
    Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}
    +
    Total online production: {numeralWrapper.formatMoney(scriptRef.onlineMoneyMade)}
    +
    {(Array(26).join(" ") + numeralWrapper.formatBigNumber(scriptRef.onlineExpGained) + " hacking exp")}
    +
    Online production rate: {numeralWrapper.formatMoney(onlineMps)} / second
    +
    {(Array(25).join(" ") + numeralWrapper.formatBigNumber(onlineEps) + " hacking exp / second")}
    +
    Total offline production: {numeralWrapper.formatMoney(scriptRef.offlineMoneyMade)}
    +
    {(Array(27).join(" ") + numeralWrapper.formatBigNumber(scriptRef.offlineExpGained) + " hacking exp")}
    +
    Offline production rate: {numeralWrapper.formatMoney(offlineMps)} / second
    +
    {(Array(26).join(" ") + numeralWrapper.formatBigNumber(offlineEps) +  " hacking exp / second")}
    diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index 948039076..a90be06f0 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -9,6 +9,7 @@ type IProps = { panelClass?: string; // Override default class panelContent: React.ReactElement; panelInitiallyOpened?: boolean; + style?: string; } type IState = { diff --git a/utils/LogBox.ts b/utils/LogBox.ts index c22542958..1a38244c6 100644 --- a/utils/LogBox.ts +++ b/utils/LogBox.ts @@ -23,7 +23,6 @@ function logBoxInit(): void { console.error(`Could not find LogBox's close button`); return; } - logBoxClose(); closeButton.addEventListener("click", function() { logBoxClose(); @@ -40,6 +39,8 @@ function logBoxInit(): void { logBoxContainer = document.getElementById("log-box-container"); logText = document.getElementById("log-box-text"); + logBoxClose(); + document.removeEventListener("DOMContentLoaded", logBoxInit); }; From 9442b348e6a363117fd0c3229c0d58b289fa85e5 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 14 May 2019 20:56:59 -0700 Subject: [PATCH 7/9] Refactored SourceFile-related code to TypeScript --- css/augmentations.scss | 27 ++ css/menupages.scss | 46 ---- src/Augmentation/AugmentationHelpers.js | 12 - .../ui/InstalledAugmentations.tsx | 40 +++ .../InstalledAugmentationsAndSourceFiles.tsx | 56 ++++ src/Augmentation/ui/ListConfiguration.tsx | 5 + src/Augmentation/ui/OwnedSourceFiles.tsx | 40 +++ .../ui/PurchasedAugmentations.tsx | 30 ++ src/Augmentation/ui/Root.tsx | 72 +++++ src/PersonObjects/IPlayer.ts | 4 + .../Player/PlayerObjectGeneralMethods.js | 3 +- src/RedPill.js | 5 +- src/SourceFile.js | 256 ------------------ src/SourceFile/SourceFile.ts | 21 ++ src/SourceFile/SourceFiles.ts | 64 +++++ src/SourceFile/applySourceFile.ts | 176 ++++++++++++ src/engine.jsx | 11 +- src/engineStyle.js | 1 + src/ui/React/AugmentationAccordion.tsx | 33 +++ src/ui/React/Popup.tsx | 14 +- src/ui/React/StdButton.tsx | 53 ++-- 21 files changed, 608 insertions(+), 361 deletions(-) create mode 100644 css/augmentations.scss create mode 100644 src/Augmentation/ui/InstalledAugmentations.tsx create mode 100644 src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx create mode 100644 src/Augmentation/ui/ListConfiguration.tsx create mode 100644 src/Augmentation/ui/OwnedSourceFiles.tsx create mode 100644 src/Augmentation/ui/PurchasedAugmentations.tsx create mode 100644 src/Augmentation/ui/Root.tsx delete mode 100644 src/SourceFile.js create mode 100644 src/SourceFile/SourceFile.ts create mode 100644 src/SourceFile/SourceFiles.ts create mode 100644 src/SourceFile/applySourceFile.ts create mode 100644 src/ui/React/AugmentationAccordion.tsx diff --git a/css/augmentations.scss b/css/augmentations.scss new file mode 100644 index 000000000..eb7fd3722 --- /dev/null +++ b/css/augmentations.scss @@ -0,0 +1,27 @@ +/** + * Styling for the Augmentations UI. This is the page that displays all of the + * player's owned and purchased Augmentations and Source-Files. It also allows + * the player to install Augmentations + */ +@import "theme"; + +#augmentations-container { + position: fixed; + padding-top: 10px; +} + +.augmentations-list { + button, + div { + color: var(--my-font-color); + text-decoration: none; + } + + button { + padding: 2px 5px; + } + + div { + padding: 6px; + } +} diff --git a/css/menupages.scss b/css/menupages.scss index 0c8e55033..b0b45c723 100644 --- a/css/menupages.scss +++ b/css/menupages.scss @@ -185,19 +185,6 @@ width: 70%; } -#faction-donate-amount-txt, -#faction-donate-input { - padding: 6px; - margin: 6px; - display: inline-block; - color: var(--my-font-color); - background-color: #000; -} - -#faction-donate-amount-txt { - width: 50%; -} - #faction-container p, #faction-container pre { padding: 4px 6px; @@ -213,45 +200,12 @@ word-wrap: break-word; /* Internet Explorer 5.5+ */ } -/* Faction Augmentations */ -#faction-augmentations-container { - position: fixed; - padding-top: 10px; - - p, a, ul, h1 { - margin: 8px; - padding: 4px; - } -} - /* World */ #world-container li { margin: 0 0 15px 0; list-style-type: none; } -/* Augmentations */ -#augmentations-container { - position: fixed; - padding-top: 10px; -} - -.augmentations-list { - button, - div { - color: var(--my-font-color); - text-decoration: none; - } - - button { - padding: 2px 5px; - } - - div { - padding: 6px; - } -} - /* Tutorial */ #tutorial-container { position: fixed; diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.js index 7110b70c8..9c6809344 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.js @@ -17,7 +17,6 @@ import { Server } from "../Server/Server"; import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; import { Settings } from "../Settings/Settings"; -import { SourceFiles } from "../SourceFile"; import { dialogBoxCreate } from "../../utils/DialogBox"; import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement"; import { Reviver, Generic_toJSON, @@ -2041,17 +2040,6 @@ function applyAugmentation(aug, reapply=false) { } } - /* - if (aug.name === AugmentationNames.NeuroFluxGovernor) { - for (var i = 0; i < Player.augmentations.length; ++i) { - if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) { - //Already have this aug, just upgrade the level - return; - } - } - } - */ - // Push onto Player's Augmentation list if (!reapply) { var ownedAug = new PlayerOwnedAugmentation(aug.name); diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx new file mode 100644 index 000000000..84d8cb2ea --- /dev/null +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -0,0 +1,40 @@ +/** + * React Component for displaying a list of the player's installed Augmentations + * on the Augmentations UI + */ +import * as React from "react"; + +import { Player } from "../../Player"; +import { Augmentations } from "../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { Settings } from "../../Settings/Settings"; +import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; + +import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; + +export function InstalledAugmentations(): React.ReactElement { + const sourceAugs = Player.augmentations.slice(); + + if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { + sourceAugs.sort((aug1, aug2) => { + return aug1.name <= aug2.name ? -1 : 1; + }); + } + + const augs = sourceAugs.map((e) => { + const aug = Augmentations[e.name]; + + let level = null; + if (e.name === AugmentationNames.NeuroFluxGovernor) { + level = e.level; + } + + return ( + + ) + }); + + return ( +
      {augs}
    + ) +} diff --git a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx new file mode 100644 index 000000000..39bac22e6 --- /dev/null +++ b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx @@ -0,0 +1,56 @@ +/** + * React Component for displaying all of the player's installed Augmentations and + * Source-Files. + * + * It also contains 'configuration' buttons that allow you to change how the + * Augs/SF's are displayed + */ +import * as React from "react"; + +import { Settings } from "../../Settings/Settings"; +import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; + +type IProps = { + +} + +type IState = { + rerenderFlag: boolean; +} + +export class InstalledAugmentationsAndSourceFiles extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + rerenderFlag: false, + } + + this.sortByAcquirementTime = this.sortByAcquirementTime.bind(this); + this.sortInOrder = this.sortInOrder.bind(this); + } + + rerender() { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + } + }); + } + + sortByAcquirementTime() { + Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime; + this.rerender(); + } + + sortInOrder() { + Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically + this.rerender(); + } + + render() { + return ( + + ) + } +} diff --git a/src/Augmentation/ui/ListConfiguration.tsx b/src/Augmentation/ui/ListConfiguration.tsx new file mode 100644 index 000000000..ef1368b8f --- /dev/null +++ b/src/Augmentation/ui/ListConfiguration.tsx @@ -0,0 +1,5 @@ +/** + * React Component for configuring the way installed augmentations and + * Source-Files are displayed in the Augmentations UI + */ +import * as React from "react"; diff --git a/src/Augmentation/ui/OwnedSourceFiles.tsx b/src/Augmentation/ui/OwnedSourceFiles.tsx new file mode 100644 index 000000000..24107bcdf --- /dev/null +++ b/src/Augmentation/ui/OwnedSourceFiles.tsx @@ -0,0 +1,40 @@ +/** + * React Component for displaying a list of the player's Source-Files + * on the Augmentations UI + */ +import * as React from "react"; + +import { Player } from "../../Player"; +import { Augmentations } from "../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { Settings } from "../../Settings/Settings"; +import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; + +import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; + +export function OwnedSourceFiles(): React.ReactElement { + const sourceAugs = Player.augmentations.slice(); + + if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { + sourceAugs.sort((aug1, aug2) => { + return aug1.name <= aug2.name ? -1 : 1; + }); + } + + const augs = sourceAugs.map((e) => { + const aug = Augmentations[e.name]; + + let level = null; + if (e.name === AugmentationNames.NeuroFluxGovernor) { + level = e.level; + } + + return ( + + ) + }); + + return ( +
      {augs}
    + ) +} diff --git a/src/Augmentation/ui/PurchasedAugmentations.tsx b/src/Augmentation/ui/PurchasedAugmentations.tsx new file mode 100644 index 000000000..bd966dfe5 --- /dev/null +++ b/src/Augmentation/ui/PurchasedAugmentations.tsx @@ -0,0 +1,30 @@ +/** + * React component for displaying all of the player's purchased (but not installed) + * Augmentations on the Augmentations UI. + */ +import * as React from "react"; + +import { Augmentations } from "../../Augmentation/Augmentations"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; +import { Player } from "../../Player"; + +import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; + +export function PurchasedAugmentations(): React.ReactElement { + const augs: React.ReactElement[] = []; + for (const ownedAug of Player.queuedAugmentations) { + const aug = Augmentations[ownedAug.name]; + let level = null; + if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { + level = ownedAug.level; + } + + augs.push( + + ) + } + + return ( +
      {augs}
    + ) +} diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/Root.tsx new file mode 100644 index 000000000..565267f31 --- /dev/null +++ b/src/Augmentation/ui/Root.tsx @@ -0,0 +1,72 @@ +/** + * Root React component for the Augmentations UI page that display all of your + * owned and purchased Augmentations and Source-Files. + */ +import * as React from "react"; + +import { Augmentations } from "../../Augmentation/Augmentations"; +import { Player } from "../../Player"; + +import { StdButton } from "../../ui/React/StdButton"; + +type IProps = { + exportGameFn: () => void; + installAugmentationsFn: () => void; +} + +type IState = { + +} + +export class AugmentationsRoot extends React.Component { + constructor(props: IProps) { + super(props); + } + + render() { + return ( +
    +

    Purchased Augmentations

    +

    + Below is a list of all Augmentations you have purchased but not + yet installed. Click the button below to install them. +

    +

    + WARNING: Installing your Augmentations resets most of your progress, + including: +

    +

    - Stats/Skill levels and Experience

    +

    - Money

    +

    - Scripts on every computer but your home computer

    +

    - Purchased servers

    +

    - Hacknet Nodes

    +

    - Faction/Company reputation

    +

    - Stocks

    +

    + Installing Augmentations lets you start over with the perks and + benefits granted by all of the Augmentations you have ever + installed. Also, you will keep any scripts and RAM/Core upgrades + on your home computer (but you will lose all programs besides + NUKE.exe) +

    + + + + + +
      + +
    +
    + ) + } +} diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index be95132c8..09146f514 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -101,6 +101,10 @@ export interface IPlayer { work_money_mult: number; crime_success_mult: number; crime_money_mult: number; + bladeburner_max_stamina_mult: number; + bladeburner_stamina_gain_mult: number; + bladeburner_analysis_mult: number; + bladeburner_success_chance_mult: number; // Methods applyForAgentJob(sing?: boolean): boolean | void; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js index c263175d5..385c836cf 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js @@ -36,7 +36,8 @@ import { import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers"; import { Settings } from "../../Settings/Settings"; import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps"; -import { SourceFiles, applySourceFile } from "../../SourceFile"; +import { applySourceFile } from "../../SourceFile/applySourceFile"; +import { SourceFiles } from "../../SourceFile/SourceFiles"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; import Decimal from "decimal.js"; diff --git a/src/RedPill.js b/src/RedPill.js index 23a56ccfe..c4cebac46 100644 --- a/src/RedPill.js +++ b/src/RedPill.js @@ -5,7 +5,7 @@ import { BitNodes } from "./BitNode/BitNode"; import { Engine } from "./engine"; import { Player } from "./Player"; import { prestigeSourceFile } from "./Prestige"; -import { SourceFiles, SourceFile } from "./SourceFile"; +import { SourceFiles } from "./SourceFile/SourceFiles"; import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile"; import { Terminal } from "./Terminal"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; @@ -20,9 +20,6 @@ import { import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; - - - // Returns promise function writeRedPillLine(line) { return new Promise(function(resolve, reject) { diff --git a/src/SourceFile.js b/src/SourceFile.js deleted file mode 100644 index 7c9219562..000000000 --- a/src/SourceFile.js +++ /dev/null @@ -1,256 +0,0 @@ -import { Player } from "./Player"; -import { BitNodes } from "./BitNode/BitNode"; - -// Each SourceFile corresponds to a BitNode with the same number -function SourceFile(number, info="") { - var bitnodeKey = "BitNode" + number; - var bitnode = BitNodes[bitnodeKey]; - if (bitnode == null) { - throw new Error("Invalid Bit Node for this Source File"); - } - - this.n = number; - this.name = "Source-File " + number + ": " + bitnode.name; - this.lvl = 1; - this.info = info; - this.owned = false; -} - -let SourceFiles = {}; -function initSourceFiles() { - SourceFiles = {}; - SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " + - "home computer. It also increases all of the player's multipliers by:

    " + - "Level 1: 16%
    " + - "Level 2: 24%
    " + - "Level 3: 28%"); - SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " + - "once your karma decreases to a certain value. It also increases the player's " + - "crime success rate, crime money, and charisma multipliers by:

    " + - "Level 1: 24%
    " + - "Level 2: 36%
    " + - "Level 3: 42%"); - SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " + - "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " + - "level of this Source-File opens up more of the Singularity Functions you can use."); - SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " + - "is unique because it is permanent and persistent (it never gets reset back to 1). However, " + - "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " + - "know when you gain experience and how much). Higher Intelligence levels will boost your production " + - "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " + - "Netscript function, and will raise all of your hacking-related multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " + - "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " + - "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:

    " + - "Level 1: Permanent access to WSE and TIX API
    " + - "Level 2: Ability to short stocks in other BitNodes
    " + - "Level 3: Ability to use limit/stop orders in other BitNodes

    " + - "This Source-File also increases your hacking growth multipliers by: " + - "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); - SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:

    " + - "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + - "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + - "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + - "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + - "when installing Augmentations)"); - SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + - "Source-File also grants you a Duplicate Sleeve"); - SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + - "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + - " increases the player's company salary and reputation gain multipliers by:

    " + - "Level 1: 32%
    " + - "Level 2: 48%
    " + - "Level 3: 56%
    "); - SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " + - "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); -} - -// Takes in a PlayerOwnedSourceFile as the "srcFile" argument -function applySourceFile(srcFile) { - var srcFileKey = "SourceFile" + srcFile.n; - var sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.log("ERROR: Invalid source file number: " + srcFile.n); - return; - } - - switch(srcFile.n) { - case 1: // The Source Genesis - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (16 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - var decMult = 1 - (mult / 100); - Player.hacking_chance_mult *= incMult; - Player.hacking_speed_mult *= incMult; - Player.hacking_money_mult *= incMult; - Player.hacking_grow_mult *= incMult; - Player.hacking_mult *= incMult; - Player.strength_mult *= incMult; - Player.defense_mult *= incMult; - Player.dexterity_mult *= incMult; - Player.agility_mult *= incMult; - Player.charisma_mult *= incMult; - Player.hacking_exp_mult *= incMult; - Player.strength_exp_mult *= incMult; - Player.defense_exp_mult *= incMult; - Player.dexterity_exp_mult *= incMult; - Player.agility_exp_mult *= incMult; - Player.charisma_exp_mult *= incMult; - Player.company_rep_mult *= incMult; - Player.faction_rep_mult *= incMult; - Player.crime_money_mult *= incMult; - Player.crime_success_mult *= incMult; - Player.hacknet_node_money_mult *= incMult; - Player.hacknet_node_purchase_cost_mult *= decMult; - Player.hacknet_node_ram_cost_mult *= decMult; - Player.hacknet_node_core_cost_mult *= decMult; - Player.hacknet_node_level_cost_mult *= decMult; - Player.work_money_mult *= incMult; - break; - case 2: // Rise of the Underworld - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (24 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.crime_money_mult *= incMult; - Player.crime_success_mult *= incMult; - Player.charisma_mult *= incMult; - break; - case 3: // Corporatocracy - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.charisma_mult *= incMult; - Player.work_money_mult *= incMult; - break; - case 4: // The Singularity - // No effects, just gives access to Singularity functions - break; - case 5: // Artificial Intelligence - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.hacking_chance_mult *= incMult; - Player.hacking_speed_mult *= incMult; - Player.hacking_money_mult *= incMult; - Player.hacking_grow_mult *= incMult; - Player.hacking_mult *= incMult; - Player.hacking_exp_mult *= incMult; - break; - case 6: // Bladeburner - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.strength_exp_mult *= incMult; - Player.defense_exp_mult *= incMult; - Player.dexterity_exp_mult *= incMult; - Player.agility_exp_mult *= incMult; - Player.strength_mult *= incMult; - Player.defense_mult *= incMult; - Player.dexterity_mult *= incMult; - Player.agility_mult *= incMult; - break; - case 7: // Bladeburner 2079 - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.bladeburner_max_stamina_mult *= incMult; - Player.bladeburner_stamina_gain_mult *= incMult; - Player.bladeburner_analysis_mult *= incMult; - Player.bladeburner_success_chance_mult *= incMult; - break; - case 8: // Ghost of Wall Street - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (12 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.hacking_grow_mult *= incMult; - break; - case 9: // Hacktocracy - // This has non-multiplier effects - break; - case 10: // Digital Carbon - // No effects, just grants sleeves - break; - case 11: // The Big Crash - var mult = 0; - for (var i = 0; i < srcFile.lvl; ++i) { - mult += (32 / (Math.pow(2, i))); - } - var incMult = 1 + (mult / 100); - Player.work_money_mult *= incMult; - Player.company_rep_mult *= incMult; - break; - case 12: // The Recursion - var inc = Math.pow(1.01, srcFile.lvl); - var dec = Math.pow(0.99, srcFile.lvl); - - Player.hacking_chance_mult *= inc; - Player.hacking_speed_mult *= inc; - Player.hacking_money_mult *= inc; - Player.hacking_grow_mult *= inc; - Player.hacking_mult *= inc; - - Player.strength_mult *= inc; - Player.defense_mult *= inc; - Player.dexterity_mult *= inc; - Player.agility_mult *= inc; - Player.charisma_mult *= inc; - - Player.hacking_exp_mult *= inc; - Player.strength_exp_mult *= inc; - Player.defense_exp_mult *= inc; - Player.dexterity_exp_mult *= inc; - Player.agility_exp_mult *= inc; - Player.charisma_exp_mult *= inc; - - Player.company_rep_mult *= inc; - Player.faction_rep_mult *= inc; - - Player.crime_money_mult *= inc; - Player.crime_success_mult *= inc; - - Player.hacknet_node_money_mult *= inc; - Player.hacknet_node_purchase_cost_mult *= dec; - Player.hacknet_node_ram_cost_mult *= dec; - Player.hacknet_node_core_cost_mult *= dec; - Player.hacknet_node_level_cost_mult *= dec; - - Player.work_money_mult *= inc; - break; - default: - console.log("ERROR: Invalid source file number: " + srcFile.n); - break; - } - - sourceFileObject.owned = true; -} - -export {SourceFiles, applySourceFile, initSourceFiles}; diff --git a/src/SourceFile/SourceFile.ts b/src/SourceFile/SourceFile.ts new file mode 100644 index 000000000..96290507a --- /dev/null +++ b/src/SourceFile/SourceFile.ts @@ -0,0 +1,21 @@ +import { BitNodes } from "../BitNode/BitNode"; + +export class SourceFile { + info: string; + lvl: number = 1; + n: number; + name: string; + owned: boolean = false; + + constructor(number: number, info: string="") { + const bitnodeKey = "BitNode" + number; + const bitnode = BitNodes[bitnodeKey]; + if (bitnode == null) { + throw new Error("Invalid Bit Node for this Source File"); + } + + this.n = number; + this.name = `Source-File ${number}: ${bitnode.name}` + this.info = info; + } +} diff --git a/src/SourceFile/SourceFiles.ts b/src/SourceFile/SourceFiles.ts new file mode 100644 index 000000000..bc35dcba8 --- /dev/null +++ b/src/SourceFile/SourceFiles.ts @@ -0,0 +1,64 @@ +import { SourceFile } from "./SourceFile"; +import { IMap } from "../types"; + +export const SourceFiles: IMap = {}; + +SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " + + "home computer. It also increases all of the player's multipliers by:

    " + + "Level 1: 16%
    " + + "Level 2: 24%
    " + + "Level 3: 28%"); +SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " + + "once your karma decreases to a certain value. It also increases the player's " + + "crime success rate, crime money, and charisma multipliers by:

    " + + "Level 1: 24%
    " + + "Level 2: 36%
    " + + "Level 3: 42%"); +SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " + + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " + + "level of this Source-File opens up more of the Singularity Functions you can use."); +SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " + + "is unique because it is permanent and persistent (it never gets reset back to 1). However, " + + "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " + + "know when you gain experience and how much). Higher Intelligence levels will boost your production " + + "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " + + "Netscript function, and will raise all of your hacking-related multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " + + "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " + + "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:

    " + + "Level 1: Permanent access to WSE and TIX API
    " + + "Level 2: Ability to short stocks in other BitNodes
    " + + "Level 3: Ability to use limit/stop orders in other BitNodes

    " + + "This Source-File also increases your hacking growth multipliers by: " + + "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); +SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:

    " + + "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + + "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + + "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + + "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + + "when installing Augmentations)"); +SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + + "Source-File also grants you a Duplicate Sleeve"); +SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + + "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + + " increases the player's company salary and reputation gain multipliers by:

    " + + "Level 1: 32%
    " + + "Level 2: 48%
    " + + "Level 3: 56%
    "); +SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File increases all your multipliers by 1% per level. This effect is multiplicative with itself. " + + "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); diff --git a/src/SourceFile/applySourceFile.ts b/src/SourceFile/applySourceFile.ts new file mode 100644 index 000000000..e38b014ec --- /dev/null +++ b/src/SourceFile/applySourceFile.ts @@ -0,0 +1,176 @@ +import { PlayerOwnedSourceFile } from "./PlayerOwnedSourceFile"; +import { SourceFiles } from "./SourceFiles"; + +import { Player } from "../Player"; + +export function applySourceFile(srcFile: PlayerOwnedSourceFile) { + const srcFileKey = "SourceFile" + srcFile.n; + const sourceFileObject = SourceFiles[srcFileKey]; + if (sourceFileObject == null) { + console.error(`Invalid source file number: ${srcFile.n}`); + return; + } + + switch (srcFile.n) { + case 1: // The Source Genesis + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (16 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + var decMult = 1 - (mult / 100); + Player.hacking_chance_mult *= incMult; + Player.hacking_speed_mult *= incMult; + Player.hacking_money_mult *= incMult; + Player.hacking_grow_mult *= incMult; + Player.hacking_mult *= incMult; + Player.strength_mult *= incMult; + Player.defense_mult *= incMult; + Player.dexterity_mult *= incMult; + Player.agility_mult *= incMult; + Player.charisma_mult *= incMult; + Player.hacking_exp_mult *= incMult; + Player.strength_exp_mult *= incMult; + Player.defense_exp_mult *= incMult; + Player.dexterity_exp_mult *= incMult; + Player.agility_exp_mult *= incMult; + Player.charisma_exp_mult *= incMult; + Player.company_rep_mult *= incMult; + Player.faction_rep_mult *= incMult; + Player.crime_money_mult *= incMult; + Player.crime_success_mult *= incMult; + Player.hacknet_node_money_mult *= incMult; + Player.hacknet_node_purchase_cost_mult *= decMult; + Player.hacknet_node_ram_cost_mult *= decMult; + Player.hacknet_node_core_cost_mult *= decMult; + Player.hacknet_node_level_cost_mult *= decMult; + Player.work_money_mult *= incMult; + break; + case 2: // Rise of the Underworld + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (24 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.crime_money_mult *= incMult; + Player.crime_success_mult *= incMult; + Player.charisma_mult *= incMult; + break; + case 3: // Corporatocracy + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.charisma_mult *= incMult; + Player.work_money_mult *= incMult; + break; + case 4: // The Singularity + // No effects, just gives access to Singularity functions + break; + case 5: // Artificial Intelligence + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.hacking_chance_mult *= incMult; + Player.hacking_speed_mult *= incMult; + Player.hacking_money_mult *= incMult; + Player.hacking_grow_mult *= incMult; + Player.hacking_mult *= incMult; + Player.hacking_exp_mult *= incMult; + break; + case 6: // Bladeburner + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.strength_exp_mult *= incMult; + Player.defense_exp_mult *= incMult; + Player.dexterity_exp_mult *= incMult; + Player.agility_exp_mult *= incMult; + Player.strength_mult *= incMult; + Player.defense_mult *= incMult; + Player.dexterity_mult *= incMult; + Player.agility_mult *= incMult; + break; + case 7: // Bladeburner 2079 + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (8 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.bladeburner_max_stamina_mult *= incMult; + Player.bladeburner_stamina_gain_mult *= incMult; + Player.bladeburner_analysis_mult *= incMult; + Player.bladeburner_success_chance_mult *= incMult; + break; + case 8: // Ghost of Wall Street + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (12 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.hacking_grow_mult *= incMult; + break; + case 9: // Hacktocracy + // This has non-multiplier effects + break; + case 10: // Digital Carbon + // No effects, just grants sleeves + break; + case 11: // The Big Crash + var mult = 0; + for (var i = 0; i < srcFile.lvl; ++i) { + mult += (32 / (Math.pow(2, i))); + } + var incMult = 1 + (mult / 100); + Player.work_money_mult *= incMult; + Player.company_rep_mult *= incMult; + break; + case 12: // The Recursion + var inc = Math.pow(1.01, srcFile.lvl); + var dec = Math.pow(0.99, srcFile.lvl); + + Player.hacking_chance_mult *= inc; + Player.hacking_speed_mult *= inc; + Player.hacking_money_mult *= inc; + Player.hacking_grow_mult *= inc; + Player.hacking_mult *= inc; + + Player.strength_mult *= inc; + Player.defense_mult *= inc; + Player.dexterity_mult *= inc; + Player.agility_mult *= inc; + Player.charisma_mult *= inc; + + Player.hacking_exp_mult *= inc; + Player.strength_exp_mult *= inc; + Player.defense_exp_mult *= inc; + Player.dexterity_exp_mult *= inc; + Player.agility_exp_mult *= inc; + Player.charisma_exp_mult *= inc; + + Player.company_rep_mult *= inc; + Player.faction_rep_mult *= inc; + + Player.crime_money_mult *= inc; + Player.crime_success_mult *= inc; + + Player.hacknet_node_money_mult *= inc; + Player.hacknet_node_purchase_cost_mult *= dec; + Player.hacknet_node_ram_cost_mult *= dec; + Player.hacknet_node_core_cost_mult *= dec; + Player.hacknet_node_level_cost_mult *= dec; + + Player.work_money_mult *= inc; + break; + default: + console.log("ERROR: Invalid source file number: " + srcFile.n); + break; + } + + sourceFileObject.owned = true; +} diff --git a/src/engine.jsx b/src/engine.jsx index e86bbcdfb..488c21eb6 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -13,7 +13,6 @@ import { PlayerOwnedAugmentation } from "./Augmentation/AugmentationHelpers"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; - import { BitNodes, initBitNodes, @@ -54,14 +53,14 @@ import { updateOnlineScriptTimes, } from "./NetscriptWorker"; import { Player } from "./Player"; -import { prestigeAugmentation, prestigeSourceFile } from "./Prestige"; +import { prestigeAugmentation } from "./Prestige"; import { Programs } from "./Programs/Programs"; import { displayCreateProgramContent, getNumAvailableCreateProgram, initCreateProgramButtons } from "./Programs/ProgramHelpers"; -import { redPillFlag, hackWorldDaemon } from "./RedPill"; +import { redPillFlag } from "./RedPill"; import { saveObject, loadGame } from "./SaveObject"; import { getCurrentEditor, @@ -69,10 +68,7 @@ import { updateScriptEditorContent } from "./Script/ScriptHelpers"; import { AllServers, initForeignServers } from "./Server/AllServers"; - -import { Server } from "./Server/Server"; import { Settings } from "./Settings/Settings"; -import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SpecialServerIps, @@ -87,7 +83,6 @@ import { displayStockMarketContent } from "./StockMarket/StockMarket"; import { Terminal, postNetburnerText } from "./Terminal"; - import { Sleeve } from "./PersonObjects/Sleeve/Sleeve"; import { clearSleevesPage, @@ -1045,7 +1040,6 @@ const Engine = { if (loadGame(saveString)) { initBitNodes(); initBitNodeMultipliers(Player); - initSourceFiles(); Engine.setDisplayElements(); // Sets variables for important DOM elements Engine.init(); // Initialize buttons, work, etc. initAugmentations(); // Also calls Player.reapplyAllAugmentations() @@ -1168,7 +1162,6 @@ const Engine = { console.log("Initializing new game"); initBitNodes(); initBitNodeMultipliers(Player); - initSourceFiles(); initSpecialServerIps(); Engine.setDisplayElements(); // Sets variables for important DOM elements Engine.start(); // Run main game loop and Scripts loop diff --git a/src/engineStyle.js b/src/engineStyle.js index df9dd7250..c297b37cc 100644 --- a/src/engineStyle.js +++ b/src/engineStyle.js @@ -11,6 +11,7 @@ import "../css/scripteditor.scss"; import "../css/codemirror-overrides.scss"; import "../css/hacknetnodes.scss"; import "../css/menupages.scss"; +import "../css/augmentations.scss"; import "../css/redpill.scss"; import "../css/stockmarket.scss"; import "../css/workinprogress.scss"; diff --git a/src/ui/React/AugmentationAccordion.tsx b/src/ui/React/AugmentationAccordion.tsx new file mode 100644 index 000000000..430a9800c --- /dev/null +++ b/src/ui/React/AugmentationAccordion.tsx @@ -0,0 +1,33 @@ +/** + * React Component for displaying a single Augmentation as an accordion. + * + * The header of the accordion contains the Augmentation's name (and level, if + * applicable), and the accordion's panel contains the Augmentation's level. + */ +import * as React from "react"; + +import { Accordion } from "./Accordion"; + +import { Augmentation } from "../../Augmentation/Augmentation"; +import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; + +type IProps = { + aug: Augmentation, + level?: number | string | null, +} + +export function AugmentationAccordion(props: IProps): React.ReactElement { + let displayName = props.aug.name; + if (props.level != null) { + if (props.aug.name === AugmentationNames.NeuroFluxGovernor) { + displayName += (` - Level ${props.level}`) + } + } + + return ( + {displayName}

    } + panelContent={

    {props.aug.info}

    } + /> + ) +} diff --git a/src/ui/React/Popup.tsx b/src/ui/React/Popup.tsx index 2d84ad684..c296809bb 100644 --- a/src/ui/React/Popup.tsx +++ b/src/ui/React/Popup.tsx @@ -13,12 +13,10 @@ interface IProps { props: object; } -export class Popup extends React.Component { - render() { - return ( -
    - {React.createElement(this.props.content, this.props.props)} -
    - ) - } +export function Popup(props: IProps): React.ReactElement { + return ( +
    + {React.createElement(props.content, props.props)} +
    + ) } diff --git a/src/ui/React/StdButton.tsx b/src/ui/React/StdButton.tsx index d8bd957f4..89227f83b 100644 --- a/src/ui/React/StdButton.tsx +++ b/src/ui/React/StdButton.tsx @@ -5,6 +5,7 @@ import * as React from "react"; interface IStdButtonProps { + addClasses?: string; disabled?: boolean; id?: string; onClick?: (e: React.MouseEvent) => any; @@ -17,30 +18,32 @@ type IInnerHTMLMarkup = { __html: string; } -export class StdButton extends React.Component { - render() { - const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; - let className = this.props.disabled ? "std-button-disabled" : "std-button"; - if (hasTooltip) { - className += " tooltip"; - } - - // Tooltip will be set using inner HTML - let tooltipMarkup: IInnerHTMLMarkup | null; - if (hasTooltip) { - tooltipMarkup = { - __html: this.props.tooltip! - } - } - - return ( - - ) +export function StdButton(props: IStdButtonProps): React.ReactElement { + const hasTooltip = props.tooltip != null && props.tooltip !== ""; + let className = props.disabled ? "std-button-disabled" : "std-button"; + if (hasTooltip) { + className += " tooltip"; } + + if (typeof props.addClasses === "string") { + className += ` ${props.addClasses}`; + } + + // Tooltip will be set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup | null; + if (hasTooltip) { + tooltipMarkup = { + __html: props.tooltip! + } + } + + return ( + + ) } From 2597b33f818de74754d9458c90c91082904cee5d Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 15 May 2019 00:15:07 -0700 Subject: [PATCH 8/9] Finished refactoring augmentations page UI to use react --- css/augmentations.scss | 12 +- ...tionHelpers.js => AugmentationHelpers.jsx} | 282 ++--------- .../ui/InstalledAugmentations.tsx | 6 +- .../InstalledAugmentationsAndSourceFiles.tsx | 59 ++- src/Augmentation/ui/ListConfiguration.tsx | 34 ++ src/Augmentation/ui/OwnedSourceFiles.tsx | 31 +- src/Augmentation/ui/PlayerMultipliers.tsx | 96 ++++ .../ui/PurchasedAugmentations.tsx | 4 +- src/Augmentation/ui/Root.tsx | 25 +- src/BitNode/BitNode.ts | 453 +++++++++--------- src/engine.jsx | 14 +- src/ui/React/Accordion.tsx | 12 +- src/ui/React/AugmentationAccordion.tsx | 6 +- src/ui/React/SourceFileAccordion.tsx | 35 ++ 14 files changed, 561 insertions(+), 508 deletions(-) rename src/Augmentation/{AugmentationHelpers.js => AugmentationHelpers.jsx} (89%) create mode 100644 src/Augmentation/ui/PlayerMultipliers.tsx create mode 100644 src/ui/React/SourceFileAccordion.tsx diff --git a/css/augmentations.scss b/css/augmentations.scss index eb7fd3722..36b39a4e2 100644 --- a/css/augmentations.scss +++ b/css/augmentations.scss @@ -10,6 +10,13 @@ padding-top: 10px; } +#augmentations-content { + > p { + font-size: $defaultFontSize * 0.875; + width: 70%; + } +} + .augmentations-list { button, div { @@ -18,10 +25,7 @@ } button { - padding: 2px 5px; + padding: 4px; } - div { - padding: 6px; - } } diff --git a/src/Augmentation/AugmentationHelpers.js b/src/Augmentation/AugmentationHelpers.jsx similarity index 89% rename from src/Augmentation/AugmentationHelpers.js rename to src/Augmentation/AugmentationHelpers.jsx index 9c6809344..fa4d57f6f 100644 --- a/src/Augmentation/AugmentationHelpers.js +++ b/src/Augmentation/AugmentationHelpers.jsx @@ -1,32 +1,39 @@ -import { Augmentation } from "./Augmentation"; -import { Augmentations } from "./Augmentations"; -import { PlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; -import { AugmentationNames } from "./data/AugmentationNames"; +import { Augmentation } from "./Augmentation"; +import { Augmentations } from "./Augmentations"; +import { PlayerOwnedAugmentation } from "./PlayerOwnedAugmentation"; +import { AugmentationNames } from "./data/AugmentationNames"; -import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; -import { CONSTANTS } from "../Constants"; -import { Factions, - factionExists } from "../Faction/Factions"; -import { addWorkerScript } from "../NetscriptWorker"; -import { Player } from "../Player"; -import { prestigeAugmentation } from "../Prestige"; -import { saveObject } from "../SaveObject"; -import { RunningScript } from "../Script/RunningScript"; -import { Script } from "../Script/Script"; -import { Server } from "../Server/Server"; -import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; -import { Settings } from "../Settings/Settings"; +import { AugmentationsRoot } from "./ui/Root"; -import { dialogBoxCreate } from "../../utils/DialogBox"; -import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement"; -import { Reviver, Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; -import { formatNumber } from "../../utils/StringHelperFunctions"; -import { clearObject } from "../../utils/helpers/clearObject"; -import { createElement } from "../../utils/uiHelpers/createElement"; -import { isString } from "../../utils/helpers/isString"; -import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { Factions, factionExists } from "../Faction/Factions"; +import { addWorkerScript } from "../NetscriptWorker"; +import { Player } from "../Player"; +import { prestigeAugmentation } from "../Prestige"; +import { saveObject } from "../SaveObject"; +import { RunningScript } from "../Script/RunningScript"; +import { Script } from "../Script/Script"; +import { Server } from "../Server/Server"; +import { OwnedAugmentationsOrderSetting } from "../Settings/SettingEnums"; +import { Settings } from "../Settings/Settings"; +import { Page, routing } from "../ui/navigationTracking"; +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { createAccordionElement } from "../../utils/uiHelpers/createAccordionElement"; +import { + Reviver, + Generic_toJSON, + Generic_fromJSON +} from "../../utils/JSONReviver"; +import { formatNumber } from "../../utils/StringHelperFunctions"; +import { clearObject } from "../../utils/helpers/clearObject"; +import { createElement } from "../../utils/uiHelpers/createElement"; +import { isString } from "../../utils/helpers/isString"; +import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; + +import React from "react"; +import ReactDOM from "react-dom"; function AddToAugmentations(aug) { var name = aug.name; @@ -2092,211 +2099,17 @@ function augmentationExists(name) { return Augmentations.hasOwnProperty(name); } -function displayAugmentationsContent(contentEl) { - removeChildrenFromElement(contentEl); - contentEl.appendChild(createElement("h1", { - innerText:"Purchased Augmentations", - })); +export function displayAugmentationsContent(contentEl) { + if (!routing.isOn(Page.Augmentations)) { return; } + if (!(contentEl instanceof HTMLElement)) { return; } - contentEl.appendChild(createElement("pre", { - width:"70%", whiteSpace:"pre-wrap", display:"block", - innerText:"Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to install them.\n" + - "WARNING: Installing your Augmentations resets most of your progress, including:\n\n" + - "Stats/Skill levels and Experience\n" + - "Money\n" + - "Scripts on every computer but your home computer\n" + - "Purchased servers\n" + - "Hacknet Nodes\n" + - "Faction/Company reputation\n" + - "Stocks\n" + - "Installing Augmentations lets you start over with the perks and benefits granted by all " + - "of the Augmentations you have ever installed. Also, you will keep any scripts and RAM/Core upgrades " + - "on your home computer (but you will lose all programs besides NUKE.exe)." - })); - - //Install Augmentations button - contentEl.appendChild(createElement("a", { - class:"a-link-button", innerText:"Install Augmentations", - tooltip:"'I never asked for this'", - clickListener:()=>{ - installAugmentations(); - return false; - } - })); - - //Backup button - contentEl.appendChild(createElement("a", { - class:"a-link-button flashing-button", innerText:"Backup Save (Export)", - tooltip:"It's always a good idea to backup/export your save!", - clickListener:()=>{ - saveObject.exportGame(); - return false; - } - })); - - //Purchased/queued augmentations list - var queuedAugmentationsList = createElement("ul", {class:"augmentations-list"}); - - for (var i = 0; i < Player.queuedAugmentations.length; ++i) { - var augName = Player.queuedAugmentations[i].name; - var aug = Augmentations[augName]; - - var displayName = augName; - if (augName === AugmentationNames.NeuroFluxGovernor) { - displayName += " - Level " + (Player.queuedAugmentations[i].level); - } - - var accordion = createAccordionElement({hdrText:displayName, panelText:aug.info}); - queuedAugmentationsList.appendChild(accordion[0]); - } - contentEl.appendChild(queuedAugmentationsList); - - //Installed augmentations list - contentEl.appendChild(createElement("h1", { - innerText:"Installed Augmentations", marginTop:"8px", - })); - contentEl.appendChild(createElement("p", { - width:"70%", whiteSpace:"pre-wrap", - innerText:"List of all Augmentations (including Source Files) that have been " + - "installed. You have gained the effects of these Augmentations." - })); - - var augmentationsList = createElement("ul", {class:"augmentations-list"}); - - //Expand/Collapse All buttons - contentEl.appendChild(createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Expand All", display:"inline-block", - clickListener:()=>{ - var allHeaders = augmentationsList.getElementsByClassName("accordion-header"); - for (var i = 0; i < allHeaders.length; ++i) { - if (!allHeaders[i].classList.contains("active")) {allHeaders[i].click();} - } - } - })); - contentEl.appendChild(createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Collapse All", display:"inline-block", - clickListener:()=>{ - var allHeaders = augmentationsList.getElementsByClassName("accordion-header"); - for (var i = 0; i < allHeaders.length; ++i) { - if (allHeaders[i].classList.contains("active")) {allHeaders[i].click();} - } - } - })); - - //Sort Buttons - const sortInOrderButton = createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Sort in Order", - tooltip:"Sorts the Augmentations alphabetically and Source Files in numerical order (1, 2, 3,...)", - clickListener:()=>{ - removeChildrenFromElement(augmentationsList); - - //Create a copy of Player's Source Files and augs array and sort them - var sourceFiles = Player.sourceFiles.slice(); - var augs = Player.augmentations.slice(); - sourceFiles.sort((sf1, sf2)=>{ - return sf1.n - sf2.n; - }); - augs.sort((aug1, aug2)=>{ - return aug1.name <= aug2.name ? -1 : 1; - }); - displaySourceFiles(augmentationsList, sourceFiles); - displayAugmentations(augmentationsList, augs); - - Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically; - } - }); - contentEl.appendChild(sortInOrderButton); - - const sortByAcquirementTimeButton = createElement("a", { - class:"a-link-button", fontSize:"14px", innerText:"Sort by Acquirement Time", - tooltip:"Sorts the Augmentations and Source Files based on when you acquired them (same as default)", - clickListener:()=>{ - removeChildrenFromElement(augmentationsList); - displaySourceFiles(augmentationsList, Player.sourceFiles); - displayAugmentations(augmentationsList, Player.augmentations); - - Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime; - } - }); - contentEl.appendChild(sortByAcquirementTimeButton); - - if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sortInOrderButton.click(); - } else { - sortByAcquirementTimeButton.click(); - } - contentEl.appendChild(augmentationsList); - - // Display multiplier information at the bottom - contentEl.appendChild(createElement("p", { - display: "block", - innerHTML: - `

    Total Multipliers:
    ` + - 'Hacking Chance multiplier: ' + formatNumber(Player.hacking_chance_mult * 100, 2) + '%
    ' + - 'Hacking Speed multiplier: ' + formatNumber(Player.hacking_speed_mult * 100, 2) + '%
    ' + - 'Hacking Money multiplier: ' + formatNumber(Player.hacking_money_mult * 100, 2) + '%
    ' + - 'Hacking Growth multiplier: ' + formatNumber(Player.hacking_grow_mult * 100, 2) + '%

    ' + - 'Hacking Level multiplier: ' + formatNumber(Player.hacking_mult * 100, 2) + '%
    ' + - 'Hacking Experience multiplier: ' + formatNumber(Player.hacking_exp_mult * 100, 2) + '%

    ' + - 'Strength Level multiplier: ' + formatNumber(Player.strength_mult * 100, 2) + '%
    ' + - 'Strength Experience multiplier: ' + formatNumber(Player.strength_exp_mult * 100, 2) + '%

    ' + - 'Defense Level multiplier: ' + formatNumber(Player.defense_mult * 100, 2) + '%
    ' + - 'Defense Experience multiplier: ' + formatNumber(Player.defense_exp_mult * 100, 2) + '%

    ' + - 'Dexterity Level multiplier: ' + formatNumber(Player.dexterity_mult * 100, 2) + '%
    ' + - 'Dexterity Experience multiplier: ' + formatNumber(Player.dexterity_exp_mult * 100, 2) + '%

    ' + - 'Agility Level multiplier: ' + formatNumber(Player.agility_mult * 100, 2) + '%
    ' + - 'Agility Experience multiplier: ' + formatNumber(Player.agility_exp_mult * 100, 2) + '%

    ' + - 'Charisma Level multiplier: ' + formatNumber(Player.charisma_mult * 100, 2) + '%
    ' + - 'Charisma Experience multiplier: ' + formatNumber(Player.charisma_exp_mult * 100, 2) + '%

    ' + - 'Hacknet Node production multiplier: ' + formatNumber(Player.hacknet_node_money_mult * 100, 2) + '%
    ' + - 'Hacknet Node purchase cost multiplier: ' + formatNumber(Player.hacknet_node_purchase_cost_mult * 100, 2) + '%
    ' + - 'Hacknet Node RAM upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_ram_cost_mult * 100, 2) + '%
    ' + - 'Hacknet Node Core purchase cost multiplier: ' + formatNumber(Player.hacknet_node_core_cost_mult * 100, 2) + '%
    ' + - 'Hacknet Node level upgrade cost multiplier: ' + formatNumber(Player.hacknet_node_level_cost_mult * 100, 2) + '%

    ' + - 'Company reputation gain multiplier: ' + formatNumber(Player.company_rep_mult * 100, 2) + '%
    ' + - 'Faction reputation gain multiplier: ' + formatNumber(Player.faction_rep_mult * 100, 2) + '%
    ' + - 'Salary multiplier: ' + formatNumber(Player.work_money_mult * 100, 2) + '%
    ' + - 'Crime success multiplier: ' + formatNumber(Player.crime_success_mult * 100, 2) + '%
    ' + - 'Crime money multiplier: ' + formatNumber(Player.crime_money_mult * 100, 2) + '%


    ', - })) -} - -//Creates the accordion elements to display Augmentations -// @listElement - List DOM element to append accordion elements to -// @augs - Array of Augmentation objects -function displayAugmentations(listElement, augs) { - for (var i = 0; i < augs.length; ++i) { - var augName = augs[i].name; - var aug = Augmentations[augName]; - - var displayName = augName; - if (augName === AugmentationNames.NeuroFluxGovernor) { - displayName += " - Level " + (augs[i].level); - } - var accordion = createAccordionElement({hdrText:displayName, panelText:aug.info}); - listElement.appendChild(accordion[0]); - } -} - -//Creates the accordion elements to display Source Files -// @listElement - List DOM element to append accordion elements to -// @sourceFiles - Array of Source File objects -function displaySourceFiles(listElement, sourceFiles) { - for (var i = 0; i < sourceFiles.length; ++i) { - var srcFileKey = "SourceFile" + sourceFiles[i].n; - var sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.log("ERROR: Invalid source file number: " + sourceFiles[i].n); - continue; - } - const maxLevel = sourceFiles[i].n == 12 ? "∞" : "3"; - var accordion = createAccordionElement({ - hdrText:sourceFileObject.name + "
    " + "Level " + (sourceFiles[i].lvl) + " / "+maxLevel, - panelText:sourceFileObject.info - }); - - listElement.appendChild(accordion[0]); - } + ReactDOM.render( + , + contentEl + ); } export function isRepeatableAug(aug) { @@ -2307,6 +2120,9 @@ export function isRepeatableAug(aug) { return false; } -export {installAugmentations, - initAugmentations, applyAugmentation, augmentationExists, - displayAugmentationsContent}; +export { + installAugmentations, + initAugmentations, + applyAugmentation, + augmentationExists, +}; diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx index 84d8cb2ea..076046cac 100644 --- a/src/Augmentation/ui/InstalledAugmentations.tsx +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -30,11 +30,13 @@ export function InstalledAugmentations(): React.ReactElement { } return ( - +
  • + +
  • ) }); return ( -
      {augs}
    + <>{augs} ) } diff --git a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx index 39bac22e6..7246ed6a8 100644 --- a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx +++ b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx @@ -7,18 +7,22 @@ */ import * as React from "react"; +import { InstalledAugmentations } from "./InstalledAugmentations"; +import { ListConfiguration } from "./ListConfiguration"; +import { OwnedSourceFiles } from "./OwnedSourceFiles"; + import { Settings } from "../../Settings/Settings"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; -type IProps = { - -} +type IProps = {} type IState = { rerenderFlag: boolean; } export class InstalledAugmentationsAndSourceFiles extends React.Component { + listRef: React.RefObject; + constructor(props: IProps) { super(props); @@ -26,8 +30,44 @@ export class InstalledAugmentationsAndSourceFiles extends React.Component + +
      + + +
    + ) } } diff --git a/src/Augmentation/ui/ListConfiguration.tsx b/src/Augmentation/ui/ListConfiguration.tsx index ef1368b8f..555766388 100644 --- a/src/Augmentation/ui/ListConfiguration.tsx +++ b/src/Augmentation/ui/ListConfiguration.tsx @@ -3,3 +3,37 @@ * Source-Files are displayed in the Augmentations UI */ import * as React from "react"; + +import { StdButton } from "../../ui/React/StdButton"; + +type IProps = { + collapseAllButtonsFn: () => void; + expandAllButtonsFn: () => void; + sortByAcquirementTimeFn: () => void; + sortInOrderFn: () => void; +} + +export function ListConfiguration(props: IProps): React.ReactElement { + return ( + <> + + + + + + ) +} diff --git a/src/Augmentation/ui/OwnedSourceFiles.tsx b/src/Augmentation/ui/OwnedSourceFiles.tsx index 24107bcdf..99012b4a7 100644 --- a/src/Augmentation/ui/OwnedSourceFiles.tsx +++ b/src/Augmentation/ui/OwnedSourceFiles.tsx @@ -5,36 +5,37 @@ import * as React from "react"; import { Player } from "../../Player"; -import { Augmentations } from "../../Augmentation/Augmentations"; -import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { Settings } from "../../Settings/Settings"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; +import { SourceFiles } from "../../SourceFile/SourceFiles"; -import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; +import { SourceFileAccordion } from "../../ui/React/SourceFileAccordion"; export function OwnedSourceFiles(): React.ReactElement { - const sourceAugs = Player.augmentations.slice(); + const sourceSfs = Player.sourceFiles.slice(); if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sourceAugs.sort((aug1, aug2) => { - return aug1.name <= aug2.name ? -1 : 1; + sourceSfs.sort((sf1, sf2) => { + return sf1.n - sf2.n; }); } - const augs = sourceAugs.map((e) => { - const aug = Augmentations[e.name]; - - let level = null; - if (e.name === AugmentationNames.NeuroFluxGovernor) { - level = e.level; + const sfs = sourceSfs.map((e) => { + const srcFileKey = "SourceFile" + e.n; + const sfObj = SourceFiles[srcFileKey]; + if (sfObj == null) { + console.error(`Invalid source file number: ${e.n}`); + return null; } return ( - +
  • + +
  • ) }); return ( -
      {augs}
    - ) + <>{sfs} + ); } diff --git a/src/Augmentation/ui/PlayerMultipliers.tsx b/src/Augmentation/ui/PlayerMultipliers.tsx new file mode 100644 index 000000000..f263558de --- /dev/null +++ b/src/Augmentation/ui/PlayerMultipliers.tsx @@ -0,0 +1,96 @@ +/** + * React component for displaying the player's multipliers on the Augmentation UI page + */ +import * as React from "react"; + +import { Player } from "../../Player"; +import { numeralWrapper } from "../../ui/numeralFormat"; + +export function PlayerMultipliers(): React.ReactElement { + return ( + <> +

    Total Multipliers:

    + +
    +            {'Hacking Chance multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_chance_mult)}
    +        
    +
    +            {'Hacking Speed multiplier:  ' + numeralWrapper.formatPercentage(Player.hacking_speed_mult)}
    +        
    +
    +            {'Hacking Money multiplier:  ' + numeralWrapper.formatPercentage(Player.hacking_money_mult)}
    +        
    +
    +            {'Hacking Growth multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_grow_mult)}
    +        

    +
    +            {'Hacking Level multiplier:      ' + numeralWrapper.formatPercentage(Player.hacking_mult)}
    +        
    +
    +            {'Hacking Experience multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_exp_mult)}
    +        
    +
    +
    +            {'Strength Level multiplier:      ' + numeralWrapper.formatPercentage(Player.strength_mult)}
    +        
    +
    +            {'Strength Experience multiplier: ' + numeralWrapper.formatPercentage(Player.strength_exp_mult)}
    +        
    +
    +
    +            {'Defense Level multiplier:      ' + numeralWrapper.formatPercentage(Player.defense_mult)}
    +        
    +
    +            {'Defense Experience multiplier: ' + numeralWrapper.formatPercentage(Player.defense_exp_mult)}
    +        

    +
    +            {'Dexterity Level multiplier:      ' + numeralWrapper.formatPercentage(Player.dexterity_mult)}
    +        
    +
    +            {'Dexterity Experience multiplier: ' + numeralWrapper.formatPercentage(Player.dexterity_exp_mult)}
    +        

    +
    +            {'Agility Level multiplier:      ' + numeralWrapper.formatPercentage(Player.agility_mult)}
    +        
    +
    +            {'Agility Experience multiplier: ' + numeralWrapper.formatPercentage(Player.agility_exp_mult)}
    +        

    +
    +            {'Charisma Level multiplier:      ' + numeralWrapper.formatPercentage(Player.charisma_mult)}
    +        
    +
    +            {'Charisma Experience multiplier: ' + numeralWrapper.formatPercentage(Player.charisma_exp_mult)}
    +        

    +
    +            {'Hacknet Node production multiplier:         ' + numeralWrapper.formatPercentage(Player.hacknet_node_money_mult)}
    +        
    +
    +            {'Hacknet Node purchase cost multiplier:      ' + numeralWrapper.formatPercentage(Player.hacknet_node_purchase_cost_mult)}
    +        
    +
    +            {'Hacknet Node RAM upgrade cost multiplier:   ' + numeralWrapper.formatPercentage(Player.hacknet_node_ram_cost_mult)}
    +        
    +
    +            {'Hacknet Node Core purchase cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_core_cost_mult)}
    +        
    +
    +            {'Hacknet Node level upgrade cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_level_cost_mult)}
    +        

    +
    +            {'Company reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.company_rep_mult)}
    +        
    +
    +            {'Faction reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.faction_rep_mult)}
    +        
    +
    +            {'Salary multiplier: ' + numeralWrapper.formatPercentage(Player.work_money_mult)}
    +        

    +
    +            {'Crime success multiplier: ' + numeralWrapper.formatPercentage(Player.crime_success_mult)}
    +        
    +
    +            {'Crime money multiplier: ' + numeralWrapper.formatPercentage(Player.crime_money_mult)}
    +        
    + + ) +} diff --git a/src/Augmentation/ui/PurchasedAugmentations.tsx b/src/Augmentation/ui/PurchasedAugmentations.tsx index bd966dfe5..3bc54d837 100644 --- a/src/Augmentation/ui/PurchasedAugmentations.tsx +++ b/src/Augmentation/ui/PurchasedAugmentations.tsx @@ -20,7 +20,9 @@ export function PurchasedAugmentations(): React.ReactElement { } augs.push( - +
  • + +
  • ) } diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/Root.tsx index 565267f31..10e733556 100644 --- a/src/Augmentation/ui/Root.tsx +++ b/src/Augmentation/ui/Root.tsx @@ -4,9 +4,11 @@ */ import * as React from "react"; -import { Augmentations } from "../../Augmentation/Augmentations"; -import { Player } from "../../Player"; +import { InstalledAugmentationsAndSourceFiles } from "./InstalledAugmentationsAndSourceFiles"; +import { PlayerMultipliers } from "./PlayerMultipliers"; +import { PurchasedAugmentations } from "./PurchasedAugmentations"; +import { Player } from "../../Player"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { @@ -25,7 +27,7 @@ export class AugmentationsRoot extends React.Component { render() { return ( -
    +

    Purchased Augmentations

    Below is a list of all Augmentations you have purchased but not @@ -34,14 +36,14 @@ export class AugmentationsRoot extends React.Component {

    WARNING: Installing your Augmentations resets most of your progress, including: -

    +


    - Stats/Skill levels and Experience

    - Money

    - Scripts on every computer but your home computer

    - Purchased servers

    - Hacknet Nodes

    - Faction/Company reputation

    -

    - Stocks

    +

    - Stocks


    Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations you have ever @@ -62,10 +64,19 @@ export class AugmentationsRoot extends React.Component { text="Backup Save (Export)" tooltip="It's always a good idea to backup/export your save!" /> + -

      +

      Installed Augmentations

      +

      + { + `List of all Augmentations ${Player.sourceFiles.length > 0 ? "and Source Files " : ""} ` + + `that have been installed. You have gained the effects of these.` + } +

      + -
    +

    +
    ) } diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index a4803866a..7f443c14a 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -25,235 +25,232 @@ class BitNode { } -export let BitNodes: IMap = {}; +export const BitNodes: IMap = {}; -export function initBitNodes() { - BitNodes = {}; - BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode", - "The first BitNode created by the Enders to imprison the minds of humans. It became " + - "the prototype and testing-grounds for all of the BitNodes that followed.

    " + - "This is the first BitNode that you play through. It has no special " + - "modifications or mechanics.

    " + - "Destroying this BitNode will give you Source-File 1, or if you already have " + - "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " + - "lets the player start with 32GB of RAM on his/her home computer when entering a " + - "new BitNode, and also increases all of the player's multipliers by:

    " + - "Level 1: 16%
    " + - "Level 2: 24%
    " + - "Level 3: 28%"); - BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs - "From the shadows, they rose.

    Organized crime groups quickly filled the void of power " + - "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " + - "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " + - "factions quickly rose to the top of the modern world.

    " + - "In this BitNode:

    " + - "Your hacking level is reduced by 20%
    " + - "The growth rate and maximum amount of money available on servers are significantly decreased
    " + - "The amount of money gained from crimes and Infiltration is tripled
    " + - "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " + - "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " + - "will earn the player money and reputation with the corresponding Faction
    " + - "Every Augmentation in the game will be available through the Factions listed above
    " + - "For every Faction NOT listed above, reputation gains are halved
    " + - "You will no longer gain passive reputation with Factions

    " + - "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " + - "once your karma decreases to a certain value. " + - "It also increases the player's crime success rate, crime money, and charisma multipliers by:

    " + - "Level 1: 24%
    " + - "Level 2: 36%
    " + - "Level 3: 42%"); - BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization", - "Our greatest illusion is that a healthy society can revolve around a " + - "single-minded pursuit of wealth.

    " + - "Sometime in the early 21st century economic and political globalization turned " + - "the world into a corporatocracy, and it never looked back. Now, the privileged " + - "elite will happily bankrupt their own countrymen, decimate their own community, " + - "and evict their neighbors from houses in their desperate bid to increase their wealth.

    " + - "In this BitNode you can create and manage your own corporation. Running a successful corporation " + - "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:

    " + - "The price and reputation cost of all Augmentations is tripled
    " + - "The starting and maximum amount of money on servers is reduced by 75%
    " + - "Server growth rate is reduced by 80%
    " + - "You now only need 75 favour with a faction in order to donate to it, rather than 150

    " + - "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " + - "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine", - "The Singularity has arrived. The human race is gone, replaced " + - "by artificially superintelligent beings that are more machine than man.

    " + - "In this BitNode, progressing is significantly harder. Experience gain rates " + - "for all stats are reduced. Most methods of earning money will now give significantly less.

    " + - "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " + - "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " + - "purchasing/installing Augmentations, and creating programs.

    " + - "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " + - "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " + - "that you can use."); - BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman", - "They said it couldn't be done. They said the human brain, " + - "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " + - "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " + - "by 1's and 0's. They were wrong.

    " + - "In this BitNode:

    " + - "The base security level of servers is doubled
    " + - "The starting money on servers is halved, but the maximum money remains the same
    " + - "Most methods of earning money now give significantly less
    " + - "Infiltration gives 50% more reputation and money
    " + - "Corporations have 50% lower valuations and are therefore less profitable
    " + - "Augmentations are more expensive
    " + - "Hacking experience gain rates are reduced

    " + - "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " + - "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " + - "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " + - "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " + - "in the game.

    " + - "In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " + - "and will also raise all of your hacking-related multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain", - "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " + - "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " + - "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " + - "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " + - "than the humans that had created them.

    " + - "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " + - "for progression. Furthermore:

    " + - "Hacking and Hacknet Nodes will be less profitable
    " + - "Your hacking level is reduced by 65%
    " + - "Hacking experience gain from scripts is reduced by 75%
    " + - "Corporations have 80% lower valuations and are therefore less profitable
    " + - "Working for companies is 50% less profitable
    " + - "Crimes and Infiltration are 25% less profitable

    " + - "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " + - "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " + - "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans", - "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " + - "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " + - "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " + - "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " + - "and more intelligent than the humans that had created them.

    " + - "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " + - "functionality through Netscript. Furthermore:

    " + - "The rank you gain from Bladeburner contracts/operations is reduced by 40%
    " + - "Bladeburner skills cost twice as many skill points
    " + - "Augmentations are 3x more expensive
    " + - "Hacking and Hacknet Nodes will be significantly less profitable
    " + - "Your hacking level is reduced by 65%
    " + - "Hacking experience gain from scripts is reduced by 75%
    " + - "Corporations have 80% lower valuations and are therefore less profitable
    " + - "Working for companies is 50% less profitable
    " + - "Crimes and Infiltration are 25% less profitable

    " + - "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " + - "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " + - "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); - BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps", - "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.

    " + - "In this BitNode:

    " + - "You start with $250 million
    " + - "The only way to earn money is by trading on the stock market
    " + - "You start with a WSE membership and access to the TIX API
    " + - "You are able to short stocks and place different types of orders (limit/stop)
    " + - "You can immediately donate to factions to gain reputation

    " + - "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + - "Level 1: Permanent access to WSE and TIX API
    " + - "Level 2: Ability to short stocks in other BitNodes
    " + - "Level 3: Ability to use limit/stop orders in other BitNodes

    " + - "This Source-File also increases your hacking growth multipliers by: " + - "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); - BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed", - "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " + - "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " + - "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " + - "abandoned the project and dissociated themselves from it.

    " + - "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " + - "hashes, which can be spent on a variety of different upgrades.

    " + - "In this BitNode:

    " + - "Your stats are significantly decreased
    " + - "You cannnot purchase additional servers
    " + - "Hacking is significantly less profitable

    " + - "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + - "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + - "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + - "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + - "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + - "when installing Augmentations)"); - BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are", - "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + - "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + - "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " + - "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.

    " + - "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

    " + - "1. Re-sleeve: Purchase and transfer your consciousness into a new body
    " + - "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously

    " + - "In this BitNode:

    " + - "Your stats are significantly decreased
    " + - "All methods of gaining money are half as profitable (except Stock Market)
    " + - "Purchased servers are more expensive, have less max RAM, and a lower maximum limit
    " + - "Augmentations are 5x as expensive and require twice as much reputation

    " + - "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " + - "Each level of this Source-File also grants you a Duplicate Sleeve"); - BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.", - "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " + - "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " + - "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.

    " + - "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " + - "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + - "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

    " + - "In this BitNode:

    " + - "Your hacking stat and experience gain are halved
    " + - "The starting and maximum amount of money available on servers is significantly decreased
    " + - "The growth rate of servers is significantly reduced
    " + - "Weakening a server is twice as effective
    " + - "Company wages are decreased by 50%
    " + - "Corporation valuations are 99% lower and are therefore significantly less profitable
    " + - "Hacknet Node production is significantly decreased
    " + - "Crime and Infiltration are more lucrative
    " + - "Augmentations are twice as expensive

    " + - "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + - "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + - "This Source-File also increases the player's company salary and reputation gain multipliers by:

    " + - "Level 1: 32%
    " + - "Level 2: 48%
    " + - "Level 3: 56%"); - BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", - "To iterate is human, to recurse divine.

    " + - "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + - "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + - "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " + - "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); - //Books: Frontera, Shiner - BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes - BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON"); - BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON"); - BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON"); - BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON"); - BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON"); - BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON"); - BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON"); - BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON"); - BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON"); - BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON"); - BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON"); -} +BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode", + "The first BitNode created by the Enders to imprison the minds of humans. It became " + + "the prototype and testing-grounds for all of the BitNodes that followed.

    " + + "This is the first BitNode that you play through. It has no special " + + "modifications or mechanics.

    " + + "Destroying this BitNode will give you Source-File 1, or if you already have " + + "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " + + "lets the player start with 32GB of RAM on his/her home computer when entering a " + + "new BitNode, and also increases all of the player's multipliers by:

    " + + "Level 1: 16%
    " + + "Level 2: 24%
    " + + "Level 3: 28%"); +BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs + "From the shadows, they rose.

    Organized crime groups quickly filled the void of power " + + "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " + + "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " + + "factions quickly rose to the top of the modern world.

    " + + "In this BitNode:

    " + + "Your hacking level is reduced by 20%
    " + + "The growth rate and maximum amount of money available on servers are significantly decreased
    " + + "The amount of money gained from crimes and Infiltration is tripled
    " + + "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " + + "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " + + "will earn the player money and reputation with the corresponding Faction
    " + + "Every Augmentation in the game will be available through the Factions listed above
    " + + "For every Faction NOT listed above, reputation gains are halved
    " + + "You will no longer gain passive reputation with Factions

    " + + "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " + + "once your karma decreases to a certain value. " + + "It also increases the player's crime success rate, crime money, and charisma multipliers by:

    " + + "Level 1: 24%
    " + + "Level 2: 36%
    " + + "Level 3: 42%"); +BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization", + "Our greatest illusion is that a healthy society can revolve around a " + + "single-minded pursuit of wealth.

    " + + "Sometime in the early 21st century economic and political globalization turned " + + "the world into a corporatocracy, and it never looked back. Now, the privileged " + + "elite will happily bankrupt their own countrymen, decimate their own community, " + + "and evict their neighbors from houses in their desperate bid to increase their wealth.

    " + + "In this BitNode you can create and manage your own corporation. Running a successful corporation " + + "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:

    " + + "The price and reputation cost of all Augmentations is tripled
    " + + "The starting and maximum amount of money on servers is reduced by 75%
    " + + "Server growth rate is reduced by 80%
    " + + "You now only need 75 favour with a faction in order to donate to it, rather than 150

    " + + "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " + + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine", + "The Singularity has arrived. The human race is gone, replaced " + + "by artificially superintelligent beings that are more machine than man.

    " + + "In this BitNode, progressing is significantly harder. Experience gain rates " + + "for all stats are reduced. Most methods of earning money will now give significantly less.

    " + + "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " + + "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " + + "purchasing/installing Augmentations, and creating programs.

    " + + "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " + + "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " + + "that you can use."); +BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman", + "They said it couldn't be done. They said the human brain, " + + "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " + + "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " + + "by 1's and 0's. They were wrong.

    " + + "In this BitNode:

    " + + "The base security level of servers is doubled
    " + + "The starting money on servers is halved, but the maximum money remains the same
    " + + "Most methods of earning money now give significantly less
    " + + "Infiltration gives 50% more reputation and money
    " + + "Corporations have 50% lower valuations and are therefore less profitable
    " + + "Augmentations are more expensive
    " + + "Hacking experience gain rates are reduced

    " + + "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " + + "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " + + "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " + + "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " + + "in the game.

    " + + "In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function, " + + "and will also raise all of your hacking-related multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain", + "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " + + "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " + + "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " + + "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " + + "than the humans that had created them.

    " + + "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " + + "for progression. Furthermore:

    " + + "Hacking and Hacknet Nodes will be less profitable
    " + + "Your hacking level is reduced by 65%
    " + + "Hacking experience gain from scripts is reduced by 75%
    " + + "Corporations have 80% lower valuations and are therefore less profitable
    " + + "Working for companies is 50% less profitable
    " + + "Crimes and Infiltration are 25% less profitable

    " + + "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " + + "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " + + "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans", + "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " + + "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " + + "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " + + "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " + + "and more intelligent than the humans that had created them.

    " + + "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " + + "functionality through Netscript. Furthermore:

    " + + "The rank you gain from Bladeburner contracts/operations is reduced by 40%
    " + + "Bladeburner skills cost twice as many skill points
    " + + "Augmentations are 3x more expensive
    " + + "Hacking and Hacknet Nodes will be significantly less profitable
    " + + "Your hacking level is reduced by 65%
    " + + "Hacking experience gain from scripts is reduced by 75%
    " + + "Corporations have 80% lower valuations and are therefore less profitable
    " + + "Working for companies is 50% less profitable
    " + + "Crimes and Infiltration are 25% less profitable

    " + + "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " + + "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " + + "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%"); +BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps", + "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.

    " + + "In this BitNode:

    " + + "You start with $250 million
    " + + "The only way to earn money is by trading on the stock market
    " + + "You start with a WSE membership and access to the TIX API
    " + + "You are able to short stocks and place different types of orders (limit/stop)
    " + + "You can immediately donate to factions to gain reputation

    " + + "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + + "Level 1: Permanent access to WSE and TIX API
    " + + "Level 2: Ability to short stocks in other BitNodes
    " + + "Level 3: Ability to use limit/stop orders in other BitNodes

    " + + "This Source-File also increases your hacking growth multipliers by: " + + "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); +BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed", + "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " + + "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " + + "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " + + "abandoned the project and dissociated themselves from it.

    " + + "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " + + "hashes, which can be spent on a variety of different upgrades.

    " + + "In this BitNode:

    " + + "Your stats are significantly decreased
    " + + "You cannnot purchase additional servers
    " + + "Hacking is significantly less profitable

    " + + "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + + "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + + "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + + "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + + "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + + "when installing Augmentations)"); +BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are", + "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + + "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + + "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " + + "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.

    " + + "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

    " + + "1. Re-sleeve: Purchase and transfer your consciousness into a new body
    " + + "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously

    " + + "In this BitNode:

    " + + "Your stats are significantly decreased
    " + + "All methods of gaining money are half as profitable (except Stock Market)
    " + + "Purchased servers are more expensive, have less max RAM, and a lower maximum limit
    " + + "Augmentations are 5x as expensive and require twice as much reputation

    " + + "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " + + "Each level of this Source-File also grants you a Duplicate Sleeve"); +BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.", + "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " + + "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " + + "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.

    " + + "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " + + "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + + "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

    " + + "In this BitNode:

    " + + "Your hacking stat and experience gain are halved
    " + + "The starting and maximum amount of money available on servers is significantly decreased
    " + + "The growth rate of servers is significantly reduced
    " + + "Weakening a server is twice as effective
    " + + "Company wages are decreased by 50%
    " + + "Corporation valuations are 99% lower and are therefore significantly less profitable
    " + + "Hacknet Node production is significantly decreased
    " + + "Crime and Infiltration are more lucrative
    " + + "Augmentations are twice as expensive

    " + + "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + + "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + + "This Source-File also increases the player's company salary and reputation gain multipliers by:

    " + + "Level 1: 32%
    " + + "Level 2: 48%
    " + + "Level 3: 56%"); +BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", + "To iterate is human, to recurse divine.

    " + + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + + "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + + "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " + + "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); +// Books: Frontera, Shiner +BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes +BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON"); +BitNodes["BitNode15"] = new BitNode(15, "", "COMING SOON"); +BitNodes["BitNode16"] = new BitNode(16, "", "COMING SOON"); +BitNodes["BitNode17"] = new BitNode(17, "", "COMING SOON"); +BitNodes["BitNode18"] = new BitNode(18, "", "COMING SOON"); +BitNodes["BitNode19"] = new BitNode(19, "", "COMING SOON"); +BitNodes["BitNode20"] = new BitNode(20, "", "COMING SOON"); +BitNodes["BitNode21"] = new BitNode(21, "", "COMING SOON"); +BitNodes["BitNode22"] = new BitNode(22, "", "COMING SOON"); +BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON"); +BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON"); export function initBitNodeMultipliers(p: IPlayer) { if (p.bitNodeN == null) { diff --git a/src/engine.jsx b/src/engine.jsx index 488c21eb6..57f397c8d 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -15,7 +15,6 @@ import { import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { BitNodes, - initBitNodes, initBitNodeMultipliers } from "./BitNode/BitNode"; import { Bladeburner } from "./Bladeburner"; @@ -310,8 +309,8 @@ const Engine = { loadAugmentationsContent: function() { Engine.hideAllContent(); Engine.Display.augmentationsContent.style.display = "block"; - displayAugmentationsContent(Engine.Display.augmentationsContent); routing.navigateTo(Page.Augmentations); + displayAugmentationsContent(Engine.Display.augmentationsContent); MainMenuLinks.Augmentations.classList.add("active"); }, @@ -488,13 +487,20 @@ const Engine = { Engine.Display.activeScriptsContent.style.display = "none"; clearHacknetNodesUI(); Engine.Display.createProgramContent.style.display = "none"; + Engine.Display.factionsContent.style.display = "none"; - ReactDOM.unmountComponentAtNode(Engine.Display.factionContent); + Engine.Display.factionContent.style.display = "none"; + ReactDOM.unmountComponentAtNode(Engine.Display.factionContent); + Engine.Display.augmentationsContent.style.display = "none"; + ReactDOM.unmountComponentAtNode(Engine.Display.augmentationsContent); + Engine.Display.tutorialContent.style.display = "none"; + Engine.Display.locationContent.style.display = "none"; ReactDOM.unmountComponentAtNode(Engine.Display.locationContent); + Engine.Display.workInProgressContent.style.display = "none"; Engine.Display.redPillContent.style.display = "none"; Engine.Display.cinematicTextContent.style.display = "none"; @@ -1038,7 +1044,6 @@ const Engine = { // Load game from save or create new game if (loadGame(saveString)) { - initBitNodes(); initBitNodeMultipliers(Player); Engine.setDisplayElements(); // Sets variables for important DOM elements Engine.init(); // Initialize buttons, work, etc. @@ -1160,7 +1165,6 @@ const Engine = { } else { // No save found, start new game console.log("Initializing new game"); - initBitNodes(); initBitNodeMultipliers(Player); initSpecialServerIps(); Engine.setDisplayElements(); // Sets variables for important DOM elements diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index b7d382fe5..5833f29f7 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -45,12 +45,12 @@ export class Accordion extends React.Component { render() { return ( -
    - - -
    + <> + + + ) } } diff --git a/src/ui/React/AugmentationAccordion.tsx b/src/ui/React/AugmentationAccordion.tsx index 430a9800c..98b95f8f1 100644 --- a/src/ui/React/AugmentationAccordion.tsx +++ b/src/ui/React/AugmentationAccordion.tsx @@ -2,7 +2,7 @@ * React Component for displaying a single Augmentation as an accordion. * * The header of the accordion contains the Augmentation's name (and level, if - * applicable), and the accordion's panel contains the Augmentation's level. + * applicable), and the accordion's panel contains the Augmentation's description. */ import * as React from "react"; @@ -26,8 +26,8 @@ export function AugmentationAccordion(props: IProps): React.ReactElement { return ( {displayName}

    } - panelContent={

    {props.aug.info}

    } + headerContent={<>{displayName}} + panelContent={

    } /> ) } diff --git a/src/ui/React/SourceFileAccordion.tsx b/src/ui/React/SourceFileAccordion.tsx new file mode 100644 index 000000000..2f5172e36 --- /dev/null +++ b/src/ui/React/SourceFileAccordion.tsx @@ -0,0 +1,35 @@ +/** + * React Component for displaying a single Source-File as an accordion. + * + * The header of the accordion contains the Source-Files's name and level, + * and the accordion's panel contains the Source-File's description. + */ +import * as React from "react"; + +import { Accordion } from "./Accordion"; + +import { SourceFile } from "../../SourceFile/SourceFile"; + +type IProps = { + level: number, + sf: SourceFile, +} + +export function SourceFileAccordion(props: IProps): React.ReactElement { + const maxLevel = props.sf.n === 3 ? "∞" : "3"; + + return ( + + {props.sf.name} +
    + {`Level ${props.level} / ${maxLevel}`} + + } + panelContent={ +

    + } + /> + ) +} From 664267bff059ea7b671fd3d75429bb0ad2a5e055 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 15 May 2019 00:37:11 -0700 Subject: [PATCH 9/9] Removed unused imports in engine --- src/engine.jsx | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/engine.jsx b/src/engine.jsx index 57f397c8d..1e8e8731b 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -1,27 +1,22 @@ import { - formatNumber, convertTimeMsToTimeElapsedString, replaceAt } from "../utils/StringHelperFunctions"; -import { loxBoxCreate, logBoxUpdateText, logBoxOpened } from "../utils/LogBox"; +import { logBoxUpdateText, logBoxOpened } from "../utils/LogBox"; import { updateActiveScriptsItems } from "./ActiveScriptsUI"; import { Augmentations } from "./Augmentation/Augmentations"; import { - installAugmentations, initAugmentations, displayAugmentationsContent, - PlayerOwnedAugmentation } from "./Augmentation/AugmentationHelpers"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { - BitNodes, initBitNodeMultipliers } from "./BitNode/BitNode"; import { Bladeburner } from "./Bladeburner"; import { CharacterOverviewComponent } from "./ui/React/CharacterOverview"; import { cinematicTextFlag } from "./CinematicText"; import { generateRandomContract } from "./CodingContractGenerator"; -import { CompanyPositions } from "./Company/CompanyPositions"; import { initCompanies } from "./Company/Companies"; import { Corporation } from "./Corporation/Corporation"; import { CONSTANTS } from "./Constants"; @@ -53,7 +48,6 @@ import { } from "./NetscriptWorker"; import { Player } from "./Player"; import { prestigeAugmentation } from "./Prestige"; -import { Programs } from "./Programs/Programs"; import { displayCreateProgramContent, getNumAvailableCreateProgram, @@ -66,16 +60,11 @@ import { scriptEditorInit, updateScriptEditorContent } from "./Script/ScriptHelpers"; -import { AllServers, initForeignServers } from "./Server/AllServers"; +import { initForeignServers } from "./Server/AllServers"; import { Settings } from "./Settings/Settings"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; +import { initSpecialServerIps } from "./Server/SpecialServerIps"; import { - SpecialServerIps, - initSpecialServerIps -} from "./Server/SpecialServerIps"; -import { - StockMarket, - SymbolToStockMap, initSymbolToStockMap, stockMarketCycle, processStockPrices, @@ -103,9 +92,7 @@ import { initializeMainMenuLinks, MainMenuLinks } from "./ui/MainMenu/Links"; import { dialogBoxCreate } from "../utils/DialogBox"; import { gameOptionsBoxClose, gameOptionsBoxOpen } from "../utils/GameOptions"; -import { getRandomInt } from "../utils/helpers/getRandomInt"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; -import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { createElement } from "../utils/uiHelpers/createElement"; import { exceptionAlert } from "../utils/helpers/exceptionAlert"; import { removeLoadingScreen } from "../utils/uiHelpers/removeLoadingScreen";