From 2d374093922c0f0a19fff26df41f9f4e036605ab Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 14 May 2019 20:56:59 -0700 Subject: [PATCH 01/39] 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 02/39] 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 03/39] 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 04/39] 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 05/39] 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 06/39] 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 07/39] 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 08/39] 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 09/39] 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 a2551f98c219da9fea70f7adac4b803a20a98a21 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Fri, 17 May 2019 15:51:28 -0700 Subject: [PATCH 10/39] Fixed documentation typos in v0.47.0 --- .../netscriptsingularityfunctions.rst | 2 - .../netscript/tixapi/getStockSaleGain.rst | 2 +- src/Hacknet/HacknetHelpers.jsx | 19 ++++- src/Hacknet/HacknetServer.ts | 13 +-- src/Locations/LocationsHelpers.ts | 81 +++++++++++-------- src/Script/RamCalculations.js | 69 ++++++++++------ src/Script/RunningScript.ts | 8 +- src/StockMarket/BuyingAndSelling.ts | 2 +- 8 files changed, 117 insertions(+), 79 deletions(-) diff --git a/doc/source/netscript/netscriptsingularityfunctions.rst b/doc/source/netscript/netscriptsingularityfunctions.rst index d36d901ae..b9ed4ea56 100644 --- a/doc/source/netscript/netscriptsingularityfunctions.rst +++ b/doc/source/netscript/netscriptsingularityfunctions.rst @@ -16,8 +16,6 @@ You can use the Singularity Functions in other BitNodes if and only if you have Source-File 4 will open up additional Singularity Functions that you can use in other BitNodes. If your Source-File 4 is upgraded all the way to level 3, then you will be able to access all of the Singularity Functions. -Note that Singularity Functions require twice as much RAM outside of BitNode-4 - .. toctree:: :caption: Functions: diff --git a/doc/source/netscript/tixapi/getStockSaleGain.rst b/doc/source/netscript/tixapi/getStockSaleGain.rst index 09c81fdd2..4716f6514 100644 --- a/doc/source/netscript/tixapi/getStockSaleGain.rst +++ b/doc/source/netscript/tixapi/getStockSaleGain.rst @@ -4,7 +4,7 @@ getStockSaleGain() Netscript Function .. js:function:: getStockSaleGain(sym, shares, posType) :param string sym: Stock symbol - :param number shares: Number of shares to purchase + :param number shares: Number of shares to sell :param string posType: Specifies whether the order is a "Long" or "Short" position. The values "L" or "S" can also be used. :RAM cost: 2 GB diff --git a/src/Hacknet/HacknetHelpers.jsx b/src/Hacknet/HacknetHelpers.jsx index f40934c96..6172593e2 100644 --- a/src/Hacknet/HacknetHelpers.jsx +++ b/src/Hacknet/HacknetHelpers.jsx @@ -1,3 +1,13 @@ +/** + * Generic helper/utility functions for the Hacknet mechanic: + * - Purchase nodes/upgrades + * - Calculating maximum number of upgrades + * - Processing Hacknet earnings + * - Updating Hash Manager capacity + * - Purchasing hash upgrades + * + * TODO Should probably split the different types of functions into their own modules + */ import { HacknetNode, BaseCostForHacknetNode, @@ -26,7 +36,7 @@ import { ITutorial } from "../InteractiveTutorial"; import { Player } from "../Player"; -import { AddToAllServers, AllServers } from "../Server/AllServers"; +import { AllServers } from "../Server/AllServers"; import { GetServerByHostname } from "../Server/ServerHelpers"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { Page, routing } from "../ui/navigationTracking"; @@ -115,7 +125,7 @@ export function getCostOfNextHacknetServer() { return BaseCostForHacknetServer * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult; } -//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node +// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's level export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) { if (maxLevel == null) { throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`); @@ -149,6 +159,7 @@ export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) { return 0; } +// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's RAM export function getMaxNumberRamUpgrades(nodeObj, maxLevel) { if (maxLevel == null) { throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`); @@ -177,6 +188,7 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) { return 0; } +// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cores export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) { if (maxLevel == null) { throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`); @@ -193,7 +205,7 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) { return levelsToMax; } - //Use a binary search to find the max possible number of upgrades + // Use a binary search to find the max possible number of upgrades while (min <= max) { let curr = (min + max) / 2 | 0; if (curr != maxLevel && @@ -212,6 +224,7 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) { return 0; } +// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cache export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) { if (maxLevel == null) { throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`); diff --git a/src/Hacknet/HacknetServer.ts b/src/Hacknet/HacknetServer.ts index 236ff06e9..591704d26 100644 --- a/src/Hacknet/HacknetServer.ts +++ b/src/Hacknet/HacknetServer.ts @@ -176,33 +176,28 @@ export class HacknetServer extends BaseServer implements IHacknetNode { return totalCost; } - // Process this Hacknet Server in the game loop. - // Returns the number of hashes generated + // Process this Hacknet Server in the game loop. Returns the number of hashes generated process(numCycles: number=1): number { const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000; return this.hashRate * seconds; } - // Returns a boolean indicating whether the cache was successfully upgraded upgradeCache(levels: number): void { this.cache = Math.min(HacknetServerMaxCache, Math.round(this.cache + levels)); this.updateHashCapacity(); } - // Returns a boolean indicating whether the number of cores was successfully upgraded upgradeCore(levels: number, prodMult: number): void { this.cores = Math.min(HacknetServerMaxCores, Math.round(this.cores + levels)); this.updateHashRate(prodMult); } - // Returns a boolean indicating whether the level was successfully upgraded upgradeLevel(levels: number, prodMult: number): void { this.level = Math.min(HacknetServerMaxLevel, Math.round(this.level + levels)); this.updateHashRate(prodMult); } - // Returns a boolean indicating whether the RAM was successfully upgraded upgradeRam(levels: number, prodMult: number): boolean { for (let i = 0; i < levels; ++i) { this.maxRam *= 2; @@ -212,10 +207,8 @@ export class HacknetServer extends BaseServer implements IHacknetNode { return true; } - - /** - * Whenever a script is run, we must update this server's hash rate - */ + + // Whenever a script is run, we must update this server's hash rate runScript(script: RunningScript, prodMult?: number): void { super.runScript(script); if (prodMult != null && typeof prodMult === "number") { diff --git a/src/Locations/LocationsHelpers.ts b/src/Locations/LocationsHelpers.ts index f50a27c2a..602f1d330 100644 --- a/src/Locations/LocationsHelpers.ts +++ b/src/Locations/LocationsHelpers.ts @@ -40,13 +40,15 @@ import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseBu import { removeElementById } from "../../utils/uiHelpers/removeElementById"; /** - * Create a pop-up box that lets the player confirm traveling to a different city - * If settings are configured to suppress this popup, just instantly travel + * Create a pop-up box that lets the player confirm traveling to a different city. + * If settings are configured to suppress this popup, just instantly travel. * The actual "Travel" implementation is implemented in the UI, and is passed in - * as an argument + * as an argument. + * @param {CityName} destination - City that the player is traveling to + * @param {Function} travelFn - Function that changes the player's state for traveling */ type TravelFunction = (to: CityName) => void; -export function createTravelPopup(destination: CityName, travelFn: TravelFunction) { +export function createTravelPopup(destination: CityName, travelFn: TravelFunction): void { const cost = CONSTANTS.TravelCost; if (Settings.SuppressTravelConfirmation) { @@ -80,10 +82,10 @@ export function createTravelPopup(destination: CityName, travelFn: TravelFunctio /** * Create a pop-up box that lets the player purchase a server. - * @param ram - Amount of RAM (GB) on server - * @param p - Player object + * @param {number} ram - Amount of RAM (GB) on server + * @param {IPlayer} p - Player object */ -export function createPurchaseServerPopup(ram: number, p: IPlayer) { +export function createPurchaseServerPopup(ram: number, p: IPlayer): void { const cost = getPurchaseServerCost(ram); if (cost === Infinity) { dialogBoxCreate("Something went wrong when trying to purchase this server. Please contact developer"); @@ -111,6 +113,7 @@ export function createPurchaseServerPopup(ram: number, p: IPlayer) { /** * Create a popup that lets the player start a Corporation + * @param {IPlayer} p - Player object */ export function createStartCorporationPopup(p: IPlayer) { if (!p.canAccessCorporation() || p.hasCorporation()) { return; } @@ -172,8 +175,10 @@ export function createStartCorporationPopup(p: IPlayer) { if (worldHeader instanceof HTMLElement) { worldHeader.click(); worldHeader.click(); } - dialogBoxCreate("Congratulations! You just started your own corporation with government seed money. " + - "You can visit and manage your company in the City"); + dialogBoxCreate( + "Congratulations! You just started your own corporation with government seed money. " + + "You can visit and manage your company in the City" + ); removeElementById(popupId); return false; } @@ -187,21 +192,23 @@ export function createStartCorporationPopup(p: IPlayer) { /** * Create a popup that lets the player upgrade the cores on his/her home computer - * @param p - Player object + * @param {IPlayer} p - Player object */ export function createUpgradeHomeCoresPopup(p: IPlayer) { const currentCores = p.getHomeComputer().cpuCores; if (currentCores >= 8) { return; } // Max of 8 cores - //Cost of purchasing another cost is found by indexing this array with number of current cores - const allCosts = [0, - 10e9, // 1->2 Cores - 10 bn - 250e9, // 2->3 Cores - 250 bn - 5e12, // 3->4 Cores - 5 trillion - 100e12, // 4->5 Cores - 100 trillion - 1e15, // 5->6 Cores - 1 quadrillion - 20e15, // 6->7 Cores - 20 quadrillion - 200e15]; // 7->8 Cores - 200 quadrillion + // Cost of purchasing another cost is found by indexing this array with number of current cores + const allCosts = [ + 0, + 10e9, + 250e9, + 5e12, + 100e12, + 1e15, + 20e15, + 200e15 + ]; const cost: number = allCosts[currentCores]; const yesBtn = yesNoBoxGetYesButton(); @@ -215,8 +222,10 @@ export function createUpgradeHomeCoresPopup(p: IPlayer) { } else { p.loseMoney(cost); p.getHomeComputer().cpuCores++; - dialogBoxCreate("You purchased an additional CPU Core for your home computer! It now has " + - p.getHomeComputer().cpuCores + " cores."); + dialogBoxCreate( + "You purchased an additional CPU Core for your home computer! It now has " + + p.getHomeComputer().cpuCores + " cores." + ); } yesNoBoxClose(); }); @@ -226,15 +235,17 @@ export function createUpgradeHomeCoresPopup(p: IPlayer) { yesNoBoxClose(); }); - yesNoBoxCreate("Would you like to purchase an additional CPU Core for your home computer? Each CPU Core " + - "lets you start with an additional Core Node in Hacking Missions.

    " + - "Purchasing an additional core (for a total of " + (p.getHomeComputer().cpuCores + 1) + ") will " + - "cost " + numeralWrapper.formatMoney(cost)); + yesNoBoxCreate( + "Would you like to purchase an additional CPU Core for your home computer? Each CPU Core " + + "lets you start with an additional Core Node in Hacking Missions.

    " + + "Purchasing an additional core (for a total of " + (p.getHomeComputer().cpuCores + 1) + ") will " + + "cost " + numeralWrapper.formatMoney(cost) + ); } /** * Create a popup that lets the player upgrade the RAM on his/her home computer - * @param p - Player object + * @param {IPlayer} p - Player object */ export function createUpgradeHomeRamPopup(p: IPlayer) { const cost: number = p.getUpgradeHomeRamCost(); @@ -255,15 +266,17 @@ export function createUpgradeHomeRamPopup(p: IPlayer) { yesNoBoxClose(); }); - yesNoBoxCreate("Would you like to purchase additional RAM for your home computer?

    " + - "This will upgrade your RAM from " + ram + "GB to " + ram*2 + "GB.

    " + - "This will cost " + numeralWrapper.format(cost, '$0.000a')); + yesNoBoxCreate( + "Would you like to purchase additional RAM for your home computer?

    " + + "This will upgrade your RAM from " + ram + "GB to " + ram*2 + "GB.

    " + + "This will cost " + numeralWrapper.format(cost, '$0.000a') + ); } /** * Attempt to purchase a TOR router - * @param p - Player object + * @param {IPlayer} p - Player object */ export function purchaseTorRouter(p: IPlayer) { if (p.hasTorRouter()) { @@ -285,7 +298,9 @@ export function purchaseTorRouter(p: IPlayer) { p.getHomeComputer().serversOnNetwork.push(darkweb.ip); darkweb.serversOnNetwork.push(p.getHomeComputer().ip); - dialogBoxCreate("You have purchased a Tor router!
    " + - "You now have access to the dark web from your home computer
    " + - "Use the scan/scan-analyze commands to search for the dark web connection."); + dialogBoxCreate( + "You have purchased a Tor router!
    " + + "You now have access to the dark web from your home computer
    " + + "Use the scan/scan-analyze commands to search for the dark web connection." + ); } diff --git a/src/Script/RamCalculations.js b/src/Script/RamCalculations.js index 3013b8c2f..1d2a77823 100644 --- a/src/Script/RamCalculations.js +++ b/src/Script/RamCalculations.js @@ -1,4 +1,10 @@ -// Calculate a script's RAM usage +/** + * Implements RAM Calculation functionality. + * + * Uses the acorn.js library to parse a script's code into an AST and + * recursively walk through that AST, calculating RAM usage along + * the way + */ import * as walk from "acorn-walk"; import { RamCalculationErrorCode } from "./RamCalculationErrorCodes"; @@ -15,19 +21,26 @@ const specialReferenceWHILE = "__SPECIAL_referenceWhile"; // The global scope of a script is registered under this key during parsing. const memCheckGlobalKey = ".__GLOBAL__"; -// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only, -// rather than NetscriptEvaluator. This is useful because NetscriptJS code does -// not work under NetscriptEvaluator. +/** + * Parses code into an AST and walks through it recursively to calculate + * RAM usage. Also accounts for imported modules. + * @param {Script[]} otherScripts - All other scripts on the server. Used to account for imported scripts + * @param {string} codeCopy - The code being parsed + * @param {WorkerScript} workerScript - Object containing RAM costs of Netscript functions. Also used to + * keep track of what functions have/havent been accounted for + */ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { try { - // Maps dependent identifiers to their dependencies. - // - // The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. - // It depends on all the functions declared in the module, all the global scopes - // of its imports, and any identifiers referenced in this global scope. Each - // function depends on all the identifiers referenced internally. - // We walk the dependency graph to calculate RAM usage, given that some identifiers - // reference Netscript functions which have a RAM cost. + /** + * Maps dependent identifiers to their dependencies. + * + * The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. + * It depends on all the functions declared in the module, all the global scopes + * of its imports, and any identifiers referenced in this global scope. Each + * function depends on all the identifiers referenced internally. + * We walk the dependency graph to calculate RAM usage, given that some identifiers + * reference Netscript functions which have a RAM cost. + */ let dependencyMap = {}; // Scripts we've parsed. @@ -48,19 +61,20 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { } } - // Splice all the references in. - //Spread syntax not supported in edge, use Object.assign instead - //dependencyMap = {...dependencyMap, ...result.dependencyMap}; + // Splice all the references in dependencyMap = Object.assign(dependencyMap, result.dependencyMap); } + // Parse the initial module, which is the "main" script that is being run const initialModule = "__SPECIAL_INITIAL_MODULE__"; parseCode(code, initialModule); + // Process additional modules, which occurs if the "main" script has any imports while (parseQueue.length > 0) { - // Get the code from the server. const nextModule = parseQueue.shift(); + // Additional modules can either be imported from the web (in which case we use + // a dynamic import), or from other in-game scripts let code; if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) { try { @@ -91,7 +105,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { } if (script == null) { - return RamCalculationErrorCode.ImportError; // No such script on the server + return RamCalculationErrorCode.ImportError; // No such script on the server } code = script.code; @@ -136,10 +150,8 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { } } - // Check if this identifier is a function in the workerscript env. + // Check if this identifier is a function in the workerScript environment. // If it is, then we need to get its RAM cost. - // - // TODO it would be simpler to just reference a dictionary. try { function applyFuncRam(func) { if (typeof func === "function") { @@ -170,7 +182,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { workerScript.loadedFns[ref] = true; } - // This accounts for namespaces (Bladeburner, CodingCOntract) + // This accounts for namespaces (Bladeburner, CodingCpntract, etc.) let func; if (ref in workerScript.env.vars.bladeburner) { func = workerScript.env.vars.bladeburner[ref]; @@ -196,9 +208,12 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { } } -// Parses one script and calculates its ram usage, for the global scope and each function. -// Returns a cost map and a dependencyMap for the module. Returns a reference map to be joined -// onto the main reference map, and a list of modules that need to be parsed. +/** + * Helper function that parses a single script. It returns a map of all dependencies, + * which are items in the code's AST that potentially need to be evaluated + * for RAM usage calculations. It also returns an array of additional modules + * that need to be parsed (i.e. are 'import'ed scripts). + */ function parseOnlyCalculateDeps(code, currentModule) { const ast = parse(code, {sourceType:"module", ecmaVersion: 8}); @@ -296,6 +311,12 @@ function parseOnlyCalculateDeps(code, currentModule) { return {dependencyMap: dependencyMap, additionalModules: additionalModules}; } +/** + * Calculate's a scripts RAM Usage + * @param {string} codeCopy - The script's code + * @param {Script[]} otherScripts - All other scripts on the server. + * Used to account for imported scripts + */ export async function calculateRamUsage(codeCopy, otherScripts) { // We don't need a real WorkerScript for this. Just an object that keeps // track of whatever's needed for RAM calculations diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index eca819276..59bde86b2 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -69,22 +69,20 @@ export class RunningScript { if (script == null) { return; } this.filename = script.filename; this.args = args; - - this.server = script.server; //IP Address only + this.server = script.server; this.ramUsage = script.ramUsage; } log(txt: string): void { if (this.logs.length > Settings.MaxLogCapacity) { - //Delete first element and add new log entry to the end. - //TODO Eventually it might be better to replace this with circular array - //to improve performance this.logs.shift(); } + let logEntry = txt; if (FconfSettings.ENABLE_TIMESTAMPS) { logEntry = "[" + getTimestamp() + "] " + logEntry; } + this.logs.push(logEntry); this.logUpd = true; } diff --git a/src/StockMarket/BuyingAndSelling.ts b/src/StockMarket/BuyingAndSelling.ts index 21a32313b..e628595af 100644 --- a/src/StockMarket/BuyingAndSelling.ts +++ b/src/StockMarket/BuyingAndSelling.ts @@ -57,7 +57,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip if (totalPrice == null) { return false; } if (Player.money.lt(totalPrice)) { if (tixApi) { - workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`); + workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this position. You need ${numeralWrapper.formatMoney(totalPrice)}`); } else if (opts.suppressDialog !== true) { dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`); } From 086fc67ecc97bb0856be4c277741f46f5ddb83b8 Mon Sep 17 00:00:00 2001 From: Sotisi Date: Mon, 13 May 2019 12:58:18 +0200 Subject: [PATCH 11/39] Fixed O(n) runtime of "Find Largest Prime Factor" for high primes to 0(sqrt(n)). --- src/data/codingcontracttypes.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index 30ad69963..22a045024 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -65,16 +65,15 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ solver: (data: number, ans: string) => { let fac: number = 2; let n: number = data; - while (n > fac) { - if (n % fac === 0) { + while (Math.sqrt(n) > fac-1) { + while (n % fac === 0) { n = Math.round(n / fac); - fac = 2; } else { ++fac; } } - return fac === parseInt(ans, 10); + return (n===1?(fac-1):n) === parseInt(ans, 10); }, }, { From 9f94d0838a15021eed5b4ea88c74d5388857066f Mon Sep 17 00:00:00 2001 From: Sotisi Date: Mon, 13 May 2019 14:09:20 +0200 Subject: [PATCH 12/39] removed faulty else, was left prior by accident. --- src/data/codingcontracttypes.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index 22a045024..b05fcce00 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -68,9 +68,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ while (Math.sqrt(n) > fac-1) { while (n % fac === 0) { n = Math.round(n / fac); - } else { - ++fac; } + ++fac; } return (n===1?(fac-1):n) === parseInt(ans, 10); From 6841f2493215dd3f4089bff0d09b0d4748a7818c Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 19 May 2019 13:56:49 -0700 Subject: [PATCH 13/39] Optimized Largest Prime factor coding contract solver. --- src/Hacknet/GetMaxNumberUpgradeFns.js | 4 ++++ src/data/codingcontracttypes.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 src/Hacknet/GetMaxNumberUpgradeFns.js diff --git a/src/Hacknet/GetMaxNumberUpgradeFns.js b/src/Hacknet/GetMaxNumberUpgradeFns.js new file mode 100644 index 000000000..093ee2baa --- /dev/null +++ b/src/Hacknet/GetMaxNumberUpgradeFns.js @@ -0,0 +1,4 @@ +/** + * Utility functions for calculating the maximum number of Hacknet upgrades the player + * can purchase for a Node with his/her current money + */ diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index b05fcce00..f3d533ac9 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -65,14 +65,14 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ solver: (data: number, ans: string) => { let fac: number = 2; let n: number = data; - while (Math.sqrt(n) > fac-1) { + while (n > ((fac-1) * (fac-1))) { while (n % fac === 0) { n = Math.round(n / fac); } ++fac; } - return (n===1?(fac-1):n) === parseInt(ans, 10); + return (n===1 ? (fac-1) : n) === parseInt(ans, 10); }, }, { From 6effda29a93a64a81998841a2b3ce782f4ef6af5 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 22 May 2019 17:23:30 -0700 Subject: [PATCH 14/39] Implemented second-order forecasts for stocks --- src/Constants.ts | 4 ++ src/StockMarket/Stock.ts | 67 ++++++++++++++++++++ src/StockMarket/StockMarket.jsx | 29 ++++----- src/StockMarket/ui/StockTickerHeaderText.tsx | 1 + 4 files changed, 85 insertions(+), 16 deletions(-) diff --git a/src/Constants.ts b/src/Constants.ts index ec47776ad..0a4927aec 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -221,6 +221,10 @@ export let CONSTANTS: IMap = { LatestUpdate: ` + v0.47.0 + * + * Scripts now start/stop instantly + v0.47.0 * Stock Market changes: ** Implemented spread. Stock's now have bid and ask prices at which transactions occur diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index 1ada0e621..5cd0afc1c 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -100,6 +100,12 @@ export class Stock { */ otlkMag: number; + /** + * Forecast of outlook magnitude. Essentially a second-order forecast. + * Unlike 'otlkMag', this number is on an absolute scale from 0-100 (rather than 0-50) + */ + otlkMagForecast: number; + /** * Average price of stocks that the player owns in the LONG position */ @@ -173,6 +179,7 @@ export class Stock { this.mv = toNumber(p.mv); this.b = p.b; this.otlkMag = p.otlkMag; + this.otlkMagForecast = this.getAbsoluteForecast(); this.cap = getRandomInt(this.price * 1e3, this.price * 25e3); this.spreadPerc = toNumber(p.spreadPerc); this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10); @@ -189,11 +196,62 @@ export class Stock { this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5; } + /** + * Set the stock to a new price. Also updates the stock's previous price tracker + */ changePrice(newPrice: number): void { this.lastPrice = this.price; this.price = newPrice; } + /** + * Change the stock's forecast during a stock market 'tick'. + * The way a stock's forecast changes depends on various internal properties, + * but is ultimately determined by RNG + */ + cycleForecast(changeAmt: number=0.1): void { + const increaseChance = this.getForecastIncreaseChance(); + + if (Math.random() < increaseChance) { + // Forecast increases + if (this.b) { + this.otlkMag += changeAmt; + } else { + this.otlkMag -= changeAmt; + } + } else { + // Forecast decreases + if (this.b) { + this.otlkMag -= changeAmt; + } else { + this.otlkMag += changeAmt; + } + } + + this.otlkMag = Math.min(this.otlkMag, 50); + if (this.otlkMag < 0) { + this.otlkMag *= -1; + this.b = !this.b; + } + } + + /** + * "Flip" the stock's second-order forecast. This can occur during a + * stock market "cycle" (determined by RNG). It is used to simulate + * RL stock market cycles and introduce volatility + */ + flipForecastForecast(): void { + const diff = this.otlkMagForecast - 50; + this.otlkMagForecast = 50 + (-1 * diff); + } + + /** + * Returns the stock's absolute forecast, which is a number between 0-100 + */ + getAbsoluteForecast(): number { + return this.b ? 50 + this.otlkMag : 50 - this.otlkMag; + } + /** * Return the price at which YOUR stock is bought (market ask price). Accounts for spread */ @@ -208,6 +266,15 @@ export class Stock { return this.price * (1 - (this.spreadPerc / 100)); } + /** + * Returns the chance (0-1 decimal) that a stock has of having its forecast increase + */ + getForecastIncreaseChance(): number { + const diff = this.otlkMagForecast - this.getAbsoluteForecast(); + + return (50 + Math.min(Math.max(diff, -45), 45)) / 100; + } + /** * Serialize the Stock to a JSON save state. */ diff --git a/src/StockMarket/StockMarket.jsx b/src/StockMarket/StockMarket.jsx index f71e8efa3..2d2511026 100644 --- a/src/StockMarket/StockMarket.jsx +++ b/src/StockMarket/StockMarket.jsx @@ -200,9 +200,15 @@ export function stockMarketCycle() { if (!(stock instanceof Stock)) { continue; } let thresh = 0.6; if (stock.b) { thresh = 0.4; } - if (Math.random() < thresh) { - stock.b = !stock.b; - if (stock.otlkMag < 5) { stock.otlkMag += 0.1; } + const roll = Math.random(); + if (roll < 0.4) { + stock.flipForecastForecast(); + } else if (roll < 0.6) { + stock.otlkMagForecast += 0.5; + stock.otlkMagForecast = Math.min(stock.otlkMagForecast * 1.02, 50); + } else if (roll < 0.8) { + stock.otlkMagForecast -= 0.5; + stock.otlkMagForecast = otlkMagForecast * (1 / 1.02); } } } @@ -264,23 +270,14 @@ export function processStockPrices(numCycles=1) { } let otlkMagChange = stock.otlkMag * av; - if (stock.otlkMag <= 0.1) { + if (stock.otlkMag < 1) { otlkMagChange = 1; } - if (c < 0.5) { - stock.otlkMag += otlkMagChange; - } else { - stock.otlkMag -= otlkMagChange; - } - if (stock.otlkMag > 50) { stock.otlkMag = 50; } // Cap so the "forecast" is between 0 and 100 - if (stock.otlkMag < 0) { - stock.otlkMag *= -1; - stock.b = !stock.b; - } + stock.cycleForecast(otlkMagChange); // Shares required for price movement gradually approaches max over time - stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement); - stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement); + stock.shareTxUntilMovementUp = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement); + stock.shareTxUntilMovementDown = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement); } displayStockMarketContent(); diff --git a/src/StockMarket/ui/StockTickerHeaderText.tsx b/src/StockMarket/ui/StockTickerHeaderText.tsx index 4ac3a8548..3227f17aa 100644 --- a/src/StockMarket/ui/StockTickerHeaderText.tsx +++ b/src/StockMarket/ui/StockTickerHeaderText.tsx @@ -27,6 +27,7 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement { let plusOrMinus = stock.b; // True for "+", false for "-" if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus } hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1); + hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; } let styleMarkup = { From c485fdfa87090613e408fdbde04e685d865c3676 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 22 May 2019 19:12:06 -0700 Subject: [PATCH 15/39] Removed stock market price movement. Now only forecast is influenced by big transactions --- doc/source/netscript/netscriptixapi.rst | 2 - .../netscript/tixapi/getStockPurchaseCost.rst | 15 - .../netscript/tixapi/getStockSaleGain.rst | 15 - src/Constants.ts | 6 +- src/NetscriptFunctions.js | 46 -- src/StockMarket/BuyingAndSelling.ts | 11 +- src/StockMarket/OrderProcessing.ts | 107 +---- src/StockMarket/Stock.ts | 13 +- src/StockMarket/StockMarket.jsx | 13 +- src/StockMarket/StockMarketHelpers.ts | 307 +------------ src/StockMarket/ui/StockTicker.tsx | 24 - test/Netscript/DynamicRamCalculationTests.js | 10 - test/StockMarketTests.js | 421 ++++++++---------- 13 files changed, 236 insertions(+), 754 deletions(-) delete mode 100644 doc/source/netscript/tixapi/getStockPurchaseCost.rst delete mode 100644 doc/source/netscript/tixapi/getStockSaleGain.rst diff --git a/doc/source/netscript/netscriptixapi.rst b/doc/source/netscript/netscriptixapi.rst index 76636efc5..a836ade0a 100644 --- a/doc/source/netscript/netscriptixapi.rst +++ b/doc/source/netscript/netscriptixapi.rst @@ -22,8 +22,6 @@ access even after you 'reset' by installing Augmentations getStockBidPrice() getStockPosition() getStockMaxShares() - getStockPurchaseCost() - getStockSaleGain() buyStock() sellStock() shortStock() diff --git a/doc/source/netscript/tixapi/getStockPurchaseCost.rst b/doc/source/netscript/tixapi/getStockPurchaseCost.rst deleted file mode 100644 index ef1de43bf..000000000 --- a/doc/source/netscript/tixapi/getStockPurchaseCost.rst +++ /dev/null @@ -1,15 +0,0 @@ -getStockPurchaseCost() Netscript Function -========================================= - -.. js:function:: getStockPurchaseCost(sym, shares, posType) - - :param string sym: Stock symbol - :param number shares: Number of shares to purchase - :param string posType: Specifies whether the order is a "Long" or "Short" position. - The values "L" or "S" can also be used. - :RAM cost: 2 GB - - Calculates and returns how much it would cost to buy a given number of - shares of a stock. This takes into account :ref:`spread `, - :ref:`large transactions influencing the price of the stock ` - and commission fees. diff --git a/doc/source/netscript/tixapi/getStockSaleGain.rst b/doc/source/netscript/tixapi/getStockSaleGain.rst deleted file mode 100644 index 4716f6514..000000000 --- a/doc/source/netscript/tixapi/getStockSaleGain.rst +++ /dev/null @@ -1,15 +0,0 @@ -getStockSaleGain() Netscript Function -===================================== - -.. js:function:: getStockSaleGain(sym, shares, posType) - - :param string sym: Stock symbol - :param number shares: Number of shares to sell - :param string posType: Specifies whether the order is a "Long" or "Short" position. - The values "L" or "S" can also be used. - :RAM cost: 2 GB - - Calculates and returns how much you would gain from selling a given number of - shares of a stock. This takes into account :ref:`spread `, - :ref:`large transactions influencing the price of the stock ` - and commission fees. diff --git a/src/Constants.ts b/src/Constants.ts index 0a4927aec..41a0985f0 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -222,7 +222,11 @@ export let CONSTANTS: IMap = { LatestUpdate: ` v0.47.0 - * + * Stock Market changes: + ** Transactions no longer influence stock prices (but they still influence forecast) + ** Removed getStockPurchaseCost() and getStockSaleGain() Netscript functions + ** + * Scripts now start/stop instantly v0.47.0 diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 394dd0156..aa4c877dc 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -98,10 +98,6 @@ import { cancelOrder, displayStockMarketContent, } from "./StockMarket/StockMarket"; -import { - getBuyTransactionCost, - getSellTransactionGain, -} from "./StockMarket/StockMarketHelpers"; import { OrderTypes } from "./StockMarket/data/OrderTypes"; import { PositionTypes } from "./StockMarket/data/PositionTypes"; import { StockSymbols } from "./StockMarket/data/StockSymbols"; @@ -1496,48 +1492,6 @@ function NetscriptFunctions(workerScript) { return stock.maxShares; }, - getStockPurchaseCost: function(symbol, shares, posType) { - updateDynamicRam("getStockPurchaseCost", getRamCost("getStockPurchaseCost")); - checkTixApiAccess("getStockPurchaseCost"); - const stock = getStockFromSymbol(symbol, "getStockPurchaseCost"); - shares = Math.round(shares); - - let pos; - const sanitizedPosType = posType.toLowerCase(); - if (sanitizedPosType.includes("l")) { - pos = PositionTypes.Long; - } else if (sanitizedPosType.includes("s")) { - pos = PositionTypes.Short; - } else { - return Infinity; - } - - const res = getBuyTransactionCost(stock, shares, pos); - if (res == null) { return Infinity; } - - return res; - }, - getStockSaleGain: function(symbol, shares, posType) { - updateDynamicRam("getStockSaleGain", getRamCost("getStockSaleGain")); - checkTixApiAccess("getStockSaleGain"); - const stock = getStockFromSymbol(symbol, "getStockSaleGain"); - shares = Math.round(shares); - - let pos; - const sanitizedPosType = posType.toLowerCase(); - if (sanitizedPosType.includes("l")) { - pos = PositionTypes.Long; - } else if (sanitizedPosType.includes("s")) { - pos = PositionTypes.Short; - } else { - return 0; - } - - const res = getSellTransactionGain(stock, shares, pos); - if (res == null) { return 0; } - - return res; - }, buyStock: function(symbol, shares) { updateDynamicRam("buyStock", getRamCost("buyStock")); checkTixApiAccess("buyStock"); diff --git a/src/StockMarket/BuyingAndSelling.ts b/src/StockMarket/BuyingAndSelling.ts index e628595af..ba6ec3630 100644 --- a/src/StockMarket/BuyingAndSelling.ts +++ b/src/StockMarket/BuyingAndSelling.ts @@ -6,8 +6,7 @@ import { Stock } from "./Stock"; import { getBuyTransactionCost, getSellTransactionGain, - processBuyTransactionPriceMovement, - processSellTransactionPriceMovement + processTransactionForecastMovement, } from "./StockMarketHelpers"; import { PositionTypes } from "./data/PositionTypes"; @@ -81,7 +80,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission; stock.playerShares = Math.round(stock.playerShares + shares); stock.playerAvgPx = newTotal / stock.playerShares; - processBuyTransactionPriceMovement(stock, shares, PositionTypes.Long); + processTransactionForecastMovement(stock, shares); if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { opts.rerenderFn(); } @@ -138,7 +137,7 @@ export function sellStock(stock: Stock, shares: number, workerScript: WorkerScri stock.playerAvgPx = 0; } - processSellTransactionPriceMovement(stock, shares, PositionTypes.Long); + processTransactionForecastMovement(stock, shares); if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { opts.rerenderFn(); @@ -211,7 +210,7 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission; stock.playerShortShares = Math.round(stock.playerShortShares + shares); stock.playerAvgShortPx = newTotal / stock.playerShortShares; - processBuyTransactionPriceMovement(stock, shares, PositionTypes.Short); + processTransactionForecastMovement(stock, shares); if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { opts.rerenderFn(); @@ -278,7 +277,7 @@ export function sellShort(stock: Stock, shares: number, workerScript: WorkerScri if (stock.playerShortShares === 0) { stock.playerAvgShortPx = 0; } - processSellTransactionPriceMovement(stock, shares, PositionTypes.Short); + processTransactionForecastMovement(stock, shares); if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { opts.rerenderFn(); diff --git a/src/StockMarket/OrderProcessing.ts b/src/StockMarket/OrderProcessing.ts index 8324fec76..f77e29a08 100644 --- a/src/StockMarket/OrderProcessing.ts +++ b/src/StockMarket/OrderProcessing.ts @@ -107,8 +107,6 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) { const stockMarket = refs.stockMarket; const orderBook = stockMarket["Orders"]; const stockOrders = orderBook[stock.symbol]; - const isLimit = (order.type === OrderTypes.LimitBuy || order.type === OrderTypes.LimitSell); - let sharesTransacted = 0; // When orders are executed, the buying and selling functions shouldn't // emit popup dialog boxes. This options object configures the functions for that @@ -120,124 +118,37 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) { let res = true; let isBuy = false; switch (order.type) { - case OrderTypes.LimitBuy: { + case OrderTypes.LimitBuy: + case OrderTypes.StopBuy: isBuy = true; - - // We only execute limit orders until the price fails to match the order condition - const isLong = (order.pos === PositionTypes.Long); - const firstShares = Math.min(order.shares, isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown); - - // First transaction to trigger movement - let res = (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts)); - if (res) { - sharesTransacted = firstShares; - } else { - break; - } - - let remainingShares = order.shares - firstShares; - let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement); - for (let i = 0; i < remainingIterations; ++i) { - if (isLong && stock.price > order.price) { - break; - } else if (!isLong && stock.price < order.price) { - break; - } - - const shares = Math.min(remainingShares, stock.shareTxForMovement); - let res = (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts)); - if (res) { - sharesTransacted += shares; - remainingShares -= shares; - } else { - break; - } - } - break; - } - case OrderTypes.StopBuy: { - isBuy = true; - sharesTransacted = order.shares; if (order.pos === PositionTypes.Long) { res = buyStock(stock, order.shares, null, opts) && res; } else if (order.pos === PositionTypes.Short) { res = shortStock(stock, order.shares, null, opts) && res; } break; - } - case OrderTypes.LimitSell: { - // TODO need to account for player's shares here - // We only execute limit orders until the price fails to match the order condition - const isLong = (order.pos === PositionTypes.Long); - const totalShares = Math.min((isLong ? stock.playerShares : stock.playerShortShares), order.shares); - if (totalShares === 0) { - return; // Player has no shares - } - const firstShares = Math.min(totalShares, isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp); - - // First transaction to trigger movement - if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) { - sharesTransacted = firstShares; - } else { - break; - } - - let remainingShares = totalShares - firstShares; - let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement); - for (let i = 0; i < remainingIterations; ++i) { - if (isLong && stock.price < order.price) { - break; - } else if (!isLong && stock.price > order.price) { - break; - } - - const shares = Math.min(remainingShares, stock.shareTxForMovement); - if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) { - sharesTransacted += shares; - remainingShares -= shares; - } else { - break; - } - } - break; - } - case OrderTypes.StopSell: { + case OrderTypes.LimitSell: + case OrderTypes.StopSell: if (order.pos === PositionTypes.Long) { - sharesTransacted = Math.min(stock.playerShares, order.shares); - if (sharesTransacted <= 0) { return; } - res = sellStock(stock, sharesTransacted, null, opts) && res; + res = sellStock(stock, order.shares, null, opts) && res; } else if (order.pos === PositionTypes.Short) { - sharesTransacted = Math.min(stock.playerShortShares, order.shares); - if (sharesTransacted <= 0) { return; } - res = sellShort(stock, sharesTransacted, null, opts) && res; + res = sellShort(stock, order.shares, null, opts) && res; } break; - } default: console.warn(`Invalid order type: ${order.type}`); return; } - if (isLimit) { - res = (sharesTransacted > 0); - } - // Position type, for logging/message purposes const pos = order.pos === PositionTypes.Long ? "Long" : "Short"; if (res) { for (let i = 0; i < stockOrders.length; ++i) { if (order == stockOrders[i]) { - // Limit orders might only transact a certain # of shares, so we have the adjust the order qty. - stockOrders[i].shares -= sharesTransacted; - if (stockOrders[i].shares <= 0) { - stockOrders.splice(i, 1); - dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` + - `(${numeralWrapper.formatBigNumber(Math.round(sharesTransacted))} share)`); - } else { - dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was partially filled ` + - `(${numeralWrapper.formatBigNumber(Math.round(sharesTransacted))} shares transacted, ${stockOrders[i].shares} shares remaining`); - } + stockOrders.splice(i, 1); + dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` + + `(${numeralWrapper.formatBigNumber(Math.round(order.shares))} shares)`); refs.rerenderFn(); return; } diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index 5cd0afc1c..2147b7c16 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -131,12 +131,6 @@ export class Stock { */ price: number; - /** - * Percentage by which the stock's price changes for a transaction-induced - * price movement. - */ - readonly priceMovementPerc: number; - /** * How many shares need to be transacted in order to trigger a price movement */ @@ -146,8 +140,7 @@ export class Stock { * How many share transactions remaining until a price movement occurs * (separately tracked for upward and downward movements) */ - shareTxUntilMovementDown: number; - shareTxUntilMovementUp: number; + shareTxUntilMovement: number; /** * Spread percentage. The bid/ask prices for this stock are N% above or below @@ -182,10 +175,8 @@ export class Stock { this.otlkMagForecast = this.getAbsoluteForecast(); this.cap = getRandomInt(this.price * 1e3, this.price * 25e3); this.spreadPerc = toNumber(p.spreadPerc); - this.priceMovementPerc = this.spreadPerc / (getRandomInt(10, 30) / 10); this.shareTxForMovement = toNumber(p.shareTxForMovement); - this.shareTxUntilMovementDown = this.shareTxForMovement; - this.shareTxUntilMovementUp = this.shareTxForMovement; + this.shareTxUntilMovement = this.shareTxForMovement; // Total shares is determined by market cap, and is rounded to nearest 100k let totalSharesUnrounded: number = (p.marketCap / this.price); diff --git a/src/StockMarket/StockMarket.jsx b/src/StockMarket/StockMarket.jsx index 2d2511026..42fc315a6 100644 --- a/src/StockMarket/StockMarket.jsx +++ b/src/StockMarket/StockMarket.jsx @@ -7,12 +7,6 @@ import { import { Order } from "./Order"; import { processOrders } from "./OrderProcessing"; import { Stock } from "./Stock"; -import { - getBuyTransactionCost, - getSellTransactionGain, - processBuyTransactionPriceMovement, - processSellTransactionPriceMovement -} from "./StockMarketHelpers"; import { getStockMarket4SDataCost, getStockMarket4STixApiCost @@ -205,10 +199,10 @@ export function stockMarketCycle() { stock.flipForecastForecast(); } else if (roll < 0.6) { stock.otlkMagForecast += 0.5; - stock.otlkMagForecast = Math.min(stock.otlkMagForecast * 1.02, 50); + stock.otlkMagForecast = Math.min(stock.otlkMagForecast * 1.02, 100); } else if (roll < 0.8) { stock.otlkMagForecast -= 0.5; - stock.otlkMagForecast = otlkMagForecast * (1 / 1.02); + stock.otlkMagForecast = stock.otlkMagForecast * (1 / 1.02); } } } @@ -276,8 +270,7 @@ export function processStockPrices(numCycles=1) { stock.cycleForecast(otlkMagChange); // Shares required for price movement gradually approaches max over time - stock.shareTxUntilMovementUp = Math.min(stock.shareTxUntilMovementUp + 5, stock.shareTxForMovement); - stock.shareTxUntilMovementDown = Math.min(stock.shareTxUntilMovementDown + 5, stock.shareTxForMovement); + stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovement + 10, stock.shareTxForMovement); } displayStockMarketContent(); diff --git a/src/StockMarket/StockMarketHelpers.ts b/src/StockMarket/StockMarketHelpers.ts index 6393cd53c..cfb37d9c0 100644 --- a/src/StockMarket/StockMarketHelpers.ts +++ b/src/StockMarket/StockMarketHelpers.ts @@ -6,34 +6,7 @@ import { CONSTANTS } from "../Constants"; export const forecastChangePerPriceMovement = 0.005; /** - * Given a stock, calculates the amount by which the stock price is multiplied - * for an 'upward' price movement. This does not actually increase the stock's price, - * just calculates the multiplier - * @param {Stock} stock - Stock for price movement - * @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args - */ -export function calculateIncreasingPriceMovement(stock: Stock): number | null { - if (!(stock instanceof Stock)) { return null; } - - return (1 + (stock.priceMovementPerc / 100)); -} - -/** - * Given a stock, calculates the amount by which the stock price is multiplied - * for a "downward" price movement. This does not actually increase the stock's price, - * just calculates the multiplier - * @param {Stock} stock - Stock for price movement - * @returns {number | null} Number by which stock's price should be multiplied. Null for invalid args - */ -export function calculateDecreasingPriceMovement(stock: Stock): number | null { - if (!(stock instanceof Stock)) { return null; } - - return (1 - (stock.priceMovementPerc / 100)); -} - -/** - * Calculate the total cost of a "buy" transaction. This accounts for spread, - * price movements, and commission. + * Calculate the total cost of a "buy" transaction. This accounts for spread and commission. * @param {Stock} stock - Stock being purchased * @param {number} shares - Number of shares being transacted * @param {PositionTypes} posType - Long or short position @@ -50,135 +23,15 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos // If the number of shares doesn't trigger a price movement, its a simple calculation if (isLong) { - if (shares <= stock.shareTxUntilMovementUp) { - return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission; - } + return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission; } else { - if (shares <= stock.shareTxUntilMovementDown) { - return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission; - } - } - - // Calculate how many iterations of price changes we need to account for - const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown; - let remainingShares = shares - firstShares; - let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement); - - // The initial cost calculation takes care of the first "iteration" - let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice(); - let totalCost = (firstShares * currPrice); - - const increasingMvmt = calculateIncreasingPriceMovement(stock)!; - const decreasingMvmt = calculateDecreasingPriceMovement(stock)!; - - function processPriceMovement() { - if (isLong) { - currPrice *= increasingMvmt; - } else { - currPrice *= decreasingMvmt; - } - } - - for (let i = 1; i < numIterations; ++i) { - processPriceMovement(); - - const amt = Math.min(stock.shareTxForMovement, remainingShares); - totalCost += (amt * currPrice); - remainingShares -= amt; - } - - return totalCost + CONSTANTS.StockMarketCommission; -} - -/** - * Processes a buy transaction's resulting price AND forecast movement. - * @param {Stock} stock - Stock being purchased - * @param {number} shares - Number of shares being transacted - * @param {PositionTypes} posType - Long or short position - */ -export function processBuyTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void { - if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; } - - // Cap the 'shares' arg at the stock's maximum shares. This'll prevent - // hanging in the case when a really big number is passed in - shares = Math.min(shares, stock.maxShares); - - const isLong = (posType === PositionTypes.Long); - - let currPrice = stock.price; - function processPriceMovement() { - if (isLong) { - currPrice *= calculateIncreasingPriceMovement(stock)!; - } else { - currPrice *= calculateDecreasingPriceMovement(stock)!; - } - } - - // If there's only going to be one iteration - const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown; - if (shares <= firstShares) { - function triggerMovement() { - processPriceMovement(); - stock.changePrice(currPrice); - stock.otlkMag -= (forecastChangePerPriceMovement); - } - - if (isLong) { - stock.shareTxUntilMovementUp -= shares; - if (stock.shareTxUntilMovementUp <= 0) { - stock.shareTxUntilMovementUp = stock.shareTxForMovement; - triggerMovement(); - } - } else { - stock.shareTxUntilMovementDown -= shares; - if (stock.shareTxUntilMovementDown <= 0) { - stock.shareTxUntilMovementDown = stock.shareTxForMovement; - triggerMovement(); - } - } - - return; - } - - // Calculate how many iterations of price changes we need to account for - let remainingShares = shares - firstShares; - let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement); - - for (let i = 1; i < numIterations; ++i) { - processPriceMovement(); - } - - // If on the offchance we end up perfectly at the next price movement - if (isLong) { - stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement); - if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) { - // The shareTxUntilMovementUp ended up at 0 at the end of the "processing" - ++numIterations; - stock.shareTxUntilMovementUp = stock.shareTxForMovement; - processPriceMovement(); - } - } else { - stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement); - if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) { - // The shareTxUntilMovementDown ended up at 0 at the end of the "processing" - ++numIterations; - stock.shareTxUntilMovementDown = stock.shareTxForMovement; - processPriceMovement(); - } - } - - stock.changePrice(currPrice); - - // Forecast always decreases in magnitude - const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1)); - if (stock.otlkMag > 10) { - stock.otlkMag -= forecastChange; + return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission; } } /** * Calculate the TOTAL amount of money gained from a sale (NOT net profit). This accounts - * for spread, price movements, and commission. + * for spread and commission. * @param {Stock} stock - Stock being sold * @param {number} shares - Number of sharse being transacted * @param {PositionTypes} posType - Long or short position @@ -192,104 +45,39 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po shares = Math.min(shares, stock.maxShares); const isLong = (posType === PositionTypes.Long); - const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp; + if (isLong) { + return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission; + } else { + // Calculating gains for a short position requires calculating the profit made + const origCost = shares * stock.playerAvgShortPx; + const profit = ((stock.playerAvgShortPx - stock.getAskPrice()) * shares) - CONSTANTS.StockMarketCommission; - // If the number of shares doesn't trigger a price mvoement, its a simple calculation - if (shares <= firstShares) { - if (isLong) { - return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission; - } else { - // Calculating gains for a short position requires calculating the profit made - const origCost = shares * stock.playerAvgShortPx; - const profit = ((stock.playerAvgShortPx - stock.getAskPrice()) * shares) - CONSTANTS.StockMarketCommission; - - return origCost + profit; - } + return origCost + profit; } - - // Calculate how many iterations of price changes we need to account for - let remainingShares = shares - firstShares; - let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement); - - // Helper function to calculate gain for a single iteration - function calculateGain(thisPrice: number, thisShares: number) { - if (isLong) { - return thisShares * thisPrice; - } else { - const origCost = thisShares * stock.playerAvgShortPx; - const profit = ((stock.playerAvgShortPx - thisPrice) * thisShares); - - return origCost + profit; - } - } - - // The initial cost calculation takes care of the first "iteration" - let currPrice = isLong ? stock.getBidPrice() : stock.getAskPrice(); - let totalGain = calculateGain(currPrice, firstShares); - for (let i = 1; i < numIterations; ++i) { - // Price movement - if (isLong) { - currPrice *= calculateDecreasingPriceMovement(stock)!; - } else { - currPrice *= calculateIncreasingPriceMovement(stock)!; - } - - const amt = Math.min(stock.shareTxForMovement, remainingShares); - totalGain += calculateGain(currPrice, amt); - remainingShares -= amt; - } - - return totalGain - CONSTANTS.StockMarketCommission; } /** - * Processes a sell transaction's resulting price movement + * Processes a stock's change in forecast whenever it is transacted * @param {Stock} stock - Stock being sold * @param {number} shares - Number of sharse being transacted * @param {PositionTypes} posType - Long or short position */ -export function processSellTransactionPriceMovement(stock: Stock, shares: number, posType: PositionTypes): void { +export function processTransactionForecastMovement(stock: Stock, shares: number): void { if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; } // Cap the 'shares' arg at the stock's maximum shares. This'll prevent // hanging in the case when a really big number is passed in shares = Math.min(shares, stock.maxShares); - const isLong = (posType === PositionTypes.Long); - const firstShares = isLong ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp; - - let currPrice = stock.price; - function processPriceMovement() { - if (isLong) { - currPrice *= calculateDecreasingPriceMovement(stock)!; - } else { - currPrice *= calculateIncreasingPriceMovement(stock)!; - } - } - // If there's only going to be one iteration at most + const firstShares = stock.shareTxUntilMovement; if (shares <= firstShares) { - function triggerPriceMovement() { - processPriceMovement(); - stock.changePrice(currPrice); + stock.shareTxUntilMovement -= shares; + if (stock.shareTxUntilMovement <= 0) { + stock.shareTxUntilMovement = stock.shareTxForMovement; stock.otlkMag -= (forecastChangePerPriceMovement); } - if (isLong) { - stock.shareTxUntilMovementDown -= shares; - if (stock.shareTxUntilMovementDown <= 0) { - stock.shareTxUntilMovementDown = stock.shareTxForMovement; - triggerPriceMovement(); - } - } else { - stock.shareTxUntilMovementUp -= shares; - if (stock.shareTxUntilMovementUp <= 0) { - stock.shareTxUntilMovementUp = stock.shareTxForMovement; - triggerPriceMovement(); - } - } - - return; } @@ -297,28 +85,13 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number let remainingShares = shares - firstShares; let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement); - for (let i = 1; i < numIterations; ++i) { - processPriceMovement(); - } - // If on the offchance we end up perfectly at the next price movement - if (isLong) { - stock.shareTxUntilMovementDown = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementDown) % stock.shareTxForMovement); - if (stock.shareTxUntilMovementDown === stock.shareTxForMovement || stock.shareTxUntilMovementDown <= 0) { - ++numIterations; - stock.shareTxUntilMovementDown = stock.shareTxForMovement; - processPriceMovement(); - } - } else { - stock.shareTxUntilMovementUp = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovementUp) % stock.shareTxForMovement); - if (stock.shareTxUntilMovementUp === stock.shareTxForMovement || stock.shareTxUntilMovementUp <= 0) { - ++numIterations; - stock.shareTxUntilMovementUp = stock.shareTxForMovement; - processPriceMovement(); - } + stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement); + if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) { + ++numIterations; + stock.shareTxUntilMovement = stock.shareTxForMovement; } - stock.changePrice(currPrice); // Forecast always decreases in magnitude const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1)); @@ -341,44 +114,8 @@ export function calculateBuyMaxAmount(stock: Stock, posType: PositionTypes, mone const isLong = (posType === PositionTypes.Long); - const increasingMvmt = calculateIncreasingPriceMovement(stock); - const decreasingMvmt = calculateDecreasingPriceMovement(stock); - if (increasingMvmt == null || decreasingMvmt == null) { return 0; } - let remainingMoney = money - CONSTANTS.StockMarketCommission; let currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice(); - // No price movement - const firstShares = isLong ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown; - const firstIterationCost = firstShares * currPrice; - if (remainingMoney < firstIterationCost) { - return Math.floor(remainingMoney / currPrice); - } - - // We'll avoid any accidental infinite loops by having a hardcoded maximum number of - // iterations - let numShares = firstShares; - remainingMoney -= firstIterationCost; - for (let i = 0; i < 10e3; ++i) { - if (isLong) { - currPrice *= increasingMvmt; - } else { - currPrice *= decreasingMvmt; - } - - const affordableShares = Math.floor(remainingMoney / currPrice); - const actualShares = Math.min(stock.shareTxForMovement, affordableShares); - - // Can't afford any more, so we're done - if (actualShares <= 0) { break; } - - numShares += actualShares; - - let cost = actualShares * currPrice; - remainingMoney -= cost; - - if (remainingMoney <= 0) { break; } - } - - return Math.floor(numShares); + return Math.floor(remainingMoney / currPrice); } diff --git a/src/StockMarket/ui/StockTicker.tsx b/src/StockMarket/ui/StockTicker.tsx index 42389c5e1..69f729111 100644 --- a/src/StockMarket/ui/StockTicker.tsx +++ b/src/StockMarket/ui/StockTicker.tsx @@ -117,12 +117,6 @@ export class StockTicker extends React.Component { let costTxt = `Purchasing ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` + `will cost ${numeralWrapper.formatMoney(cost)}. `; - const amtNeededForMovement = this.state.position === PositionTypes.Long ? stock.shareTxUntilMovementUp : stock.shareTxUntilMovementDown; - const causesMovement = qty > amtNeededForMovement; - if (causesMovement) { - costTxt += `WARNING: Purchasing this many shares will influence the stock price`; - } - return costTxt; } @@ -151,12 +145,6 @@ export class StockTicker extends React.Component { let costTxt = `Selling ${numeralWrapper.formatBigNumber(qty)} shares (${this.state.position === PositionTypes.Long ? "Long" : "Short"}) ` + `will result in a gain of ${numeralWrapper.formatMoney(cost)}. `; - const amtNeededForMovement = this.state.position === PositionTypes.Long ? stock.shareTxUntilMovementDown : stock.shareTxUntilMovementUp; - const causesMovement = qty > amtNeededForMovement; - if (causesMovement) { - costTxt += `WARNING: Selling this many shares will influence the stock price`; - } - return costTxt; } @@ -357,11 +345,7 @@ export class StockTicker extends React.Component { render() { // Determine if the player's intended transaction will cause a price movement - let causesMovement: boolean = false; const qty = this.getQuantity(); - if (!isNaN(qty)) { - causesMovement = qty > this.props.stock.shareTxForMovement; - } return (
  • @@ -400,14 +384,6 @@ export class StockTicker extends React.Component { - { - causesMovement && -

    - WARNING: Buying/Selling {numeralWrapper.formatBigNumber(qty)} shares may affect - the stock's price. This applies during the transaction itself as well. See Investopedia - for more details. -

    - } Date: Sun, 2 Jun 2019 15:21:08 -0400 Subject: [PATCH 16/39] recompile ns2 scripts if dependencies change --- src/NetscriptJSEvaluator.js | 58 ++++++++++++++++++++++++++++++++----- src/Script/Script.ts | 18 ++++++++++-- src/Server/BaseServer.ts | 2 +- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/NetscriptJSEvaluator.js b/src/NetscriptJSEvaluator.js index 0f8032f90..65b714c06 100644 --- a/src/NetscriptJSEvaluator.js +++ b/src/NetscriptJSEvaluator.js @@ -1,10 +1,22 @@ import { makeRuntimeRejectMsg } from "./NetscriptEvaluator"; +import { Script } from "./Script/Script"; // Makes a blob that contains the code of a given script. export function makeScriptBlob(code) { return new Blob([code], {type: "text/javascript"}); } +class ScriptUrl { + /** + * @param {string} filename + * @param {string} url + */ + constructor(filename, url) { + this.filename = filename; + this.url = url; + } +} + // Begin executing a user JS script, and return a promise that resolves // or rejects when the script finishes. // - script is a script to execute (see Script.js). We depend only on .filename and .code. @@ -15,9 +27,9 @@ export function makeScriptBlob(code) { // running the main function of the script. export async function executeJSScript(scripts = [], workerScript) { let loadedModule; - let urlStack = null; + let urls = null; let script = workerScript.getScript(); - if (script.module === "") { + if (shouldCompile(script, scripts)) { // The URL at the top is the one we want to import. It will // recursively import all the other modules in the urlStack. // @@ -25,8 +37,9 @@ export async function executeJSScript(scripts = [], workerScript) { // but not really behaves like import. Particularly, it cannot // load fully dynamic content. So we hide the import from webpack // by placing it inside an eval call. - urlStack = _getScriptUrls(script, scripts, []); - script.module = await eval('import(urlStack[urlStack.length - 1])'); + urls = _getScriptUrls(script, scripts, []); + script.module = await eval('import(urls[urls.length - 1].url)'); + script.dependencies = urls.map(u => u.filename); } loadedModule = script.module; @@ -41,12 +54,31 @@ export async function executeJSScript(scripts = [], workerScript) { return loadedModule.main(ns); } finally { // Revoke the generated URLs - if (urlStack != null) { - for (const url in urlStack) URL.revokeObjectURL(url); + if (urls != null) { + for (const b in urls) URL.revokeObjectURL(b.url); } }; } +/** Returns whether we should compile the script parameter. + * + * @param {Script} script + * @param {Script[]} scripts + */ +function shouldCompile(script, scripts) { + if (script.module === "") return true; + return script.dependencies.some(dep => { + const depScript = scripts.find(s => s.filename == dep); + + // If the script is not present on the server, we should recompile, if only to get any necessary + // compilation errors. + if (!depScript) return true; + + const depIsMoreRecent = depScript.updateTimestamp > script.updateTimestamp + return depIsMoreRecent; + }); +} + // Gets a stack of blob urls, the top/right-most element being // the blob url for the named script on the named server. // @@ -58,8 +90,18 @@ export async function executeJSScript(scripts = [], workerScript) { // different parts of the tree. That hasn't presented any problem with during // testing, but it might be an idea for the future. Would require a topo-sort // then url-izing from leaf-most to root-most. +/** + * @param {Script} script + * @param {Script[]} scripts + * @param {Script[]} seen + * @returns {ScriptUrl[]} All of the compiled scripts, with the final one + * in the list containing the blob corresponding to + * the script parameter. + */ +// BUG: apparently seen is never consulted. Oops. export function _getScriptUrls(script, scripts, seen) { // Inspired by: https://stackoverflow.com/a/43834063/91401 + /** @type {ScriptUrl[]} */ const urlStack = []; seen.push(script); try { @@ -86,7 +128,7 @@ export function _getScriptUrls(script, scripts, seen) { // The top url in the stack is the replacement import file for this script. urlStack.push(...urls); - return [prefix, urls[urls.length - 1], suffix].join(''); + return [prefix, urls[urls.length - 1].url, suffix].join(''); } ); @@ -96,7 +138,7 @@ export function _getScriptUrls(script, scripts, seen) { // If we successfully transformed the code, create a blob url for it and // push that URL onto the top of the stack. - urlStack.push(URL.createObjectURL(makeScriptBlob(transformedCode))); + urlStack.push(new ScriptUrl(script.filename, URL.createObjectURL(makeScriptBlob(transformedCode)))); return urlStack; } catch (err) { // If there is an error, we need to clean up the URLs. diff --git a/src/Script/Script.ts b/src/Script/Script.ts index cb3200a6c..dc8a69367 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -31,6 +31,14 @@ export class Script { // This is only applicable for NetscriptJS module: any = ""; + // The timestamp when when the script was last updated. + updateTimestamp: number = 0; + + // Only used with NS2 scripts; the list of dependency script filenames. This is constructed + // whenever the script is first evaluated, and therefore may be out of date if the script + // has been updated since it was last run. + dependencies: string[] = []; + // Amount of RAM this Script requres to run ramUsage: number = 0; @@ -85,11 +93,17 @@ export class Script { } this.filename = filenameElem!.value; this.server = serverIp; - this.updateRamUsage(otherScripts); - this.module = ""; + this.updateRamUsage(otherScripts); + this.markUpdated(); } } + // Marks that this script has been updated. Causes recompilation of NS2 modules. + markUpdated() { + this.module = ""; + this.updateTimestamp = Date.now(); + } + /** * Calculates and updates the script's RAM usage based on its code * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index 74f297189..143570abf 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -233,7 +233,7 @@ export class BaseServer { let script = this.scripts[i]; script.code = code; script.updateRamUsage(this.scripts); - script.module = ""; + script.markUpdated(); ret.overwritten = true; ret.success = true; return ret; From 1236ad252b8d916561abd2f2c4565f025f69e333 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:32:35 -0400 Subject: [PATCH 17/39] markUpdated() for Script --- src/NetscriptFunctions.js | 3 ++- src/NetscriptWorker.js | 2 +- src/Script/Script.ts | 10 +++++++++- src/Server/BaseServer.ts | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index aa4c877dc..92e64bf9c 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1139,7 +1139,7 @@ function NetscriptFunctions(workerScript) { var oldScript = destServer.scripts[i]; oldScript.code = sourceScript.code; oldScript.ramUsage = sourceScript.ramUsage; - oldScript.module = ""; + oldScript.markUpdated();; return true; } } @@ -1901,6 +1901,7 @@ function NetscriptFunctions(workerScript) { } mode === "w" ? script.code = data : script.code += data; script.updateRamUsage(server.scripts); + script.markUpdated(); } else { // Write to text file let txtFile = getTextFile(fn, server); diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 319693358..095ba401d 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -534,7 +534,7 @@ export function loadAllRunningScripts() { // Reset modules on all scripts for (let i = 0; i < server.scripts.length; ++i) { - server.scripts[i].module = ""; + server.scripts[i].markUpdated(); } if (skipScriptLoad) { diff --git a/src/Script/Script.ts b/src/Script/Script.ts index cb3200a6c..9cc395d56 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -68,6 +68,14 @@ export class Script { } } + /** + * Marks this script as having been updated. It will be recompiled next time something tries + * to exec it. + */ + markUpdated() { + this.module = ""; + } + /** * Save a script from the script editor * @param {string} code - The new contents of the script @@ -86,7 +94,7 @@ export class Script { this.filename = filenameElem!.value; this.server = serverIp; this.updateRamUsage(otherScripts); - this.module = ""; + this.markUpdated(); } } diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index 74f297189..143570abf 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -233,7 +233,7 @@ export class BaseServer { let script = this.scripts[i]; script.code = code; script.updateRamUsage(this.scripts); - script.module = ""; + script.markUpdated(); ret.overwritten = true; ret.success = true; return ret; From 3ef9042051acb2db53d4b850cf04c030b4cbb5e4 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:38:45 -0400 Subject: [PATCH 18/39] fix two cases where markUpdated was not properly called --- src/NetscriptFunctions.js | 2 +- src/NetscriptWorker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index aa4c877dc..cbc0c6162 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1139,7 +1139,7 @@ function NetscriptFunctions(workerScript) { var oldScript = destServer.scripts[i]; oldScript.code = sourceScript.code; oldScript.ramUsage = sourceScript.ramUsage; - oldScript.module = ""; + oldScript.module.markUpdated(); return true; } } diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 319693358..095ba401d 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -534,7 +534,7 @@ export function loadAllRunningScripts() { // Reset modules on all scripts for (let i = 0; i < server.scripts.length; ++i) { - server.scripts[i].module = ""; + server.scripts[i].markUpdated(); } if (skipScriptLoad) { From 2201dfc371e06199dd55aba2cbd1d465421f5d05 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:40:13 -0400 Subject: [PATCH 19/39] fix typo --- src/NetscriptFunctions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index cbc0c6162..5fef44888 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1139,7 +1139,7 @@ function NetscriptFunctions(workerScript) { var oldScript = destServer.scripts[i]; oldScript.code = sourceScript.code; oldScript.ramUsage = sourceScript.ramUsage; - oldScript.module.markUpdated(); + oldScript.markUpdated(); return true; } } From d45689c7dff0d3e5852459273bb9fa44228b739e Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 20:17:27 -0400 Subject: [PATCH 20/39] use sequence numbers rather than timestamps for script expiration --- src/NetscriptJSEvaluator.js | 2 +- src/Script/Script.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NetscriptJSEvaluator.js b/src/NetscriptJSEvaluator.js index 65b714c06..d3241f557 100644 --- a/src/NetscriptJSEvaluator.js +++ b/src/NetscriptJSEvaluator.js @@ -74,7 +74,7 @@ function shouldCompile(script, scripts) { // compilation errors. if (!depScript) return true; - const depIsMoreRecent = depScript.updateTimestamp > script.updateTimestamp + const depIsMoreRecent = depScript.moduleSequenceNumber > script.moduleSequenceNumber return depIsMoreRecent; }); } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index dc8a69367..3b9df8135 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -15,6 +15,8 @@ import { } from "../../utils/JSONReviver"; import { roundToTwo } from "../../utils/helpers/roundToTwo"; +let globalModuleSequenceNumber = 0; + export class Script { // Initializes a Script Object from a JSON save state static fromJSON(value: any): Script { @@ -32,7 +34,7 @@ export class Script { module: any = ""; // The timestamp when when the script was last updated. - updateTimestamp: number = 0; + moduleSequenceNumber: number; // Only used with NS2 scripts; the list of dependency script filenames. This is constructed // whenever the script is first evaluated, and therefore may be out of date if the script @@ -51,6 +53,7 @@ export class Script { this.ramUsage = 0; this.server = server; // IP of server this script is on this.module = ""; + this.moduleSequenceNumber = ++globalModuleSequenceNumber; if (this.code !== "") { this.updateRamUsage(otherScripts); } }; @@ -101,7 +104,7 @@ export class Script { // Marks that this script has been updated. Causes recompilation of NS2 modules. markUpdated() { this.module = ""; - this.updateTimestamp = Date.now(); + this.moduleSequenceNumber = ++globalModuleSequenceNumber; } /** From eecb0c0f010e59d3b337a40e09e6f1a65d13af89 Mon Sep 17 00:00:00 2001 From: Heikki Aitakangas Date: Sat, 25 May 2019 21:20:24 +0300 Subject: [PATCH 21/39] Update script.module immediately, so we avoid accidentally evaling the same module several times --- src/NetscriptJSEvaluator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetscriptJSEvaluator.js b/src/NetscriptJSEvaluator.js index 0f8032f90..c1632b0ae 100644 --- a/src/NetscriptJSEvaluator.js +++ b/src/NetscriptJSEvaluator.js @@ -26,9 +26,9 @@ export async function executeJSScript(scripts = [], workerScript) { // load fully dynamic content. So we hide the import from webpack // by placing it inside an eval call. urlStack = _getScriptUrls(script, scripts, []); - script.module = await eval('import(urlStack[urlStack.length - 1])'); + script.module = new Promise(resolve => resolve(eval('import(urlStack[urlStack.length - 1])'))); } - loadedModule = script.module; + loadedModule = await script.module; let ns = workerScript.env.vars; From e5e3fec1a941267ed5bc49e69c9fdd411f1e97b5 Mon Sep 17 00:00:00 2001 From: James Aguilar <799564+jaguilar@users.noreply.github.com> Date: Sat, 1 Jun 2019 22:48:48 -0400 Subject: [PATCH 22/39] close dialog boxes on escape --- utils/DialogBox.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/utils/DialogBox.js b/utils/DialogBox.js index 127740271..aa165bb94 100644 --- a/utils/DialogBox.js +++ b/utils/DialogBox.js @@ -1,3 +1,5 @@ +import { KEY } from "./helpers/keyCodes"; + /** * Create and display a pop-up dialog box. * This dialog box does not allow for any interaction and should close when clicking @@ -9,28 +11,31 @@ let dialogBoxes = []; $(document).click(function(event) { if (dialogBoxOpened && dialogBoxes.length >= 1) { if (!$(event.target).closest(dialogBoxes[0]).length){ - dialogBoxes[0].remove(); - dialogBoxes.splice(0, 1); - if (dialogBoxes.length == 0) { - dialogBoxOpened = false; - } else { - dialogBoxes[0].style.visibility = "visible"; - } + closeTopmostDialogBox(); } } }); +function closeTopmostDialogBox() { + if (!dialogBoxOpened || dialogBoxes.length === 0) return; + dialogBoxes[0].remove(); + dialogBoxes.shift(); + if (dialogBoxes.length == 0) { + dialogBoxOpened = false; + } else { + dialogBoxes[0].style.visibility = "visible"; + } +} // Dialog box close buttons $(document).on('click', '.dialog-box-close-button', function( event ) { - if (dialogBoxOpened && dialogBoxes.length >= 1) { - dialogBoxes[0].remove(); - dialogBoxes.splice(0, 1); - if (dialogBoxes.length == 0) { - dialogBoxOpened = false; - } else { - dialogBoxes[0].style.visibility = "visible"; - } + closeTopmostDialogBox(); +}); + +document.addEventListener("keydown", function (event) { + if (event.keyCode == KEY.ESC && dialogBoxOpened) { + closeTopmostDialogBox(); + event.preventDefault(); } }); From 0b4968d148adaa386dc73bacc795131b6abfded0 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 2 Jun 2019 20:28:02 -0700 Subject: [PATCH 23/39] Re-added the getStockPurchaseCost() and getStockSaleGain() functions so we don't break user scripts --- doc/source/netscript/netscriptixapi.rst | 2 + .../netscript/tixapi/getStockPurchaseCost.rst | 14 ++++++ .../netscript/tixapi/getStockSaleGain.rst | 14 ++++++ src/Constants.ts | 4 +- src/NetscriptFunctions.js | 46 +++++++++++++++++++ src/StockMarket/StockMarket.jsx | 3 ++ 6 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 doc/source/netscript/tixapi/getStockPurchaseCost.rst create mode 100644 doc/source/netscript/tixapi/getStockSaleGain.rst diff --git a/doc/source/netscript/netscriptixapi.rst b/doc/source/netscript/netscriptixapi.rst index a836ade0a..76636efc5 100644 --- a/doc/source/netscript/netscriptixapi.rst +++ b/doc/source/netscript/netscriptixapi.rst @@ -22,6 +22,8 @@ access even after you 'reset' by installing Augmentations getStockBidPrice() getStockPosition() getStockMaxShares() + getStockPurchaseCost() + getStockSaleGain() buyStock() sellStock() shortStock() diff --git a/doc/source/netscript/tixapi/getStockPurchaseCost.rst b/doc/source/netscript/tixapi/getStockPurchaseCost.rst new file mode 100644 index 000000000..a686d1bca --- /dev/null +++ b/doc/source/netscript/tixapi/getStockPurchaseCost.rst @@ -0,0 +1,14 @@ +getStockPurchaseCost() Netscript Function +========================================= + + .. js:function:: getStockPurchaseCost(sym, shares, posType) + + :param string sym: Stock symbol + :param number shares: Number of shares to purchase + :param string posType: Specifies whether the order is a "Long" or "Short" position. + The values "L" or "S" can also be used. + :RAM cost: 2 GB + + Calculates and returns how much it would cost to buy a given number of + shares of a stock. This takes into account :ref:`spread ` + and commission fees. diff --git a/doc/source/netscript/tixapi/getStockSaleGain.rst b/doc/source/netscript/tixapi/getStockSaleGain.rst new file mode 100644 index 000000000..2dcbf8b78 --- /dev/null +++ b/doc/source/netscript/tixapi/getStockSaleGain.rst @@ -0,0 +1,14 @@ +getStockSaleGain() Netscript Function +===================================== + + .. js:function:: getStockSaleGain(sym, shares, posType) + + :param string sym: Stock symbol + :param number shares: Number of shares to sell + :param string posType: Specifies whether the order is a "Long" or "Short" position. + The values "L" or "S" can also be used. + :RAM cost: 2 GB + + Calculates and returns how much you would gain from selling a given number of + shares of a stock. This takes into account :ref:`spread ` + and commission fees. diff --git a/src/Constants.ts b/src/Constants.ts index 41a0985f0..49e72d2be 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -224,10 +224,10 @@ export let CONSTANTS: IMap = { v0.47.0 * Stock Market changes: ** Transactions no longer influence stock prices (but they still influence forecast) - ** Removed getStockPurchaseCost() and getStockSaleGain() Netscript functions - ** * Scripts now start/stop instantly + * Improved performance when starting up many copies of a new script (by Ornedan) + * Dialog boxes can now be closed with the ESC key (by jaguilar) v0.47.0 * Stock Market changes: diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index aa4c877dc..8ad911b79 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -98,6 +98,10 @@ import { cancelOrder, displayStockMarketContent, } from "./StockMarket/StockMarket"; +import { + getBuyTransactionCost, + getSellTransactionGain, +} from "./StockMarket/StockMarketHelpers"; import { OrderTypes } from "./StockMarket/data/OrderTypes"; import { PositionTypes } from "./StockMarket/data/PositionTypes"; import { StockSymbols } from "./StockMarket/data/StockSymbols"; @@ -1492,6 +1496,48 @@ function NetscriptFunctions(workerScript) { return stock.maxShares; }, + getStockPurchaseCost: function(symbol, shares, posType) { + updateDynamicRam("getStockPurchaseCost", getRamCost("getStockPurchaseCost")); + checkTixApiAccess("getStockPurchaseCost"); + const stock = getStockFromSymbol(symbol, "getStockPurchaseCost"); + shares = Math.round(shares); + + let pos; + const sanitizedPosType = posType.toLowerCase(); + if (sanitizedPosType.includes("l")) { + pos = PositionTypes.Long; + } else if (sanitizedPosType.includes("s")) { + pos = PositionTypes.Short; + } else { + return Infinity; + } + + const res = getBuyTransactionCost(stock, shares, pos); + if (res == null) { return Infinity; } + + return res; + }, + getStockSaleGain: function(symbol, shares, posType) { + updateDynamicRam("getStockSaleGain", getRamCost("getStockSaleGain")); + checkTixApiAccess("getStockSaleGain"); + const stock = getStockFromSymbol(symbol, "getStockSaleGain"); + shares = Math.round(shares); + + let pos; + const sanitizedPosType = posType.toLowerCase(); + if (sanitizedPosType.includes("l")) { + pos = PositionTypes.Long; + } else if (sanitizedPosType.includes("s")) { + pos = PositionTypes.Short; + } else { + return 0; + } + + const res = getSellTransactionGain(stock, shares, pos); + if (res == null) { return 0; } + + return res; + }, buyStock: function(symbol, shares) { updateDynamicRam("buyStock", getRamCost("buyStock")); checkTixApiAccess("buyStock"); diff --git a/src/StockMarket/StockMarket.jsx b/src/StockMarket/StockMarket.jsx index 42fc315a6..3dd1365a5 100644 --- a/src/StockMarket/StockMarket.jsx +++ b/src/StockMarket/StockMarket.jsx @@ -203,6 +203,9 @@ export function stockMarketCycle() { } else if (roll < 0.8) { stock.otlkMagForecast -= 0.5; stock.otlkMagForecast = stock.otlkMagForecast * (1 / 1.02); + } else if (roll < 0.9) { + stock.b = !stock.b; + stock.flipForecastForecast(); } } } From 2f8eac07ee5f95fdf3185c7c220bbcadf1db81d8 Mon Sep 17 00:00:00 2001 From: J <799564+jaguilar@users.noreply.github.com> Date: Sun, 2 Jun 2019 23:53:07 -0400 Subject: [PATCH 24/39] fix indentation --- src/Script/Script.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Script/Script.ts b/src/Script/Script.ts index f1a8f3bf0..6e11fd08b 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -105,7 +105,7 @@ export class Script { this.filename = filenameElem!.value; this.server = serverIp; this.updateRamUsage(otherScripts); - this.markUpdated(); + this.markUpdated(); } } From 74587f269e05beab2af356894f8625eb26dafc40 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 2 Jun 2019 20:57:39 -0700 Subject: [PATCH 25/39] Updated changelog --- src/Constants.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Constants.ts b/src/Constants.ts index 49e72d2be..e263a65d7 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -228,6 +228,8 @@ export let CONSTANTS: IMap = { * Scripts now start/stop instantly * Improved performance when starting up many copies of a new script (by Ornedan) * Dialog boxes can now be closed with the ESC key (by jaguilar) + * NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar) + * write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar) v0.47.0 * Stock Market changes: From 8398fd47f0f945de7204ef34506a6ac533c2e24c Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 2 Jun 2019 23:29:56 -0700 Subject: [PATCH 26/39] Changed the way stock market cycles occur. Stock second-order forecast now changes normally like before --- src/Constants.ts | 1 + src/Script/Script.ts | 7 +-- src/Server/data/servers.ts | 75 +++++++++++++------------ src/StockMarket/PlayerInfluencing.ts | 6 ++ src/StockMarket/Stock.ts | 12 ++++ src/StockMarket/StockMarket.jsx | 21 ++++--- src/StockMarket/StockMarketConstants.ts | 5 ++ src/StockMarket/StockMarketHelpers.ts | 3 + src/engine.jsx | 9 --- test/StockMarketTests.js | 2 + 10 files changed, 80 insertions(+), 61 deletions(-) create mode 100644 src/StockMarket/PlayerInfluencing.ts create mode 100644 src/StockMarket/StockMarketConstants.ts diff --git a/src/Constants.ts b/src/Constants.ts index e263a65d7..7db9f67c9 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -224,6 +224,7 @@ export let CONSTANTS: IMap = { v0.47.0 * Stock Market changes: ** Transactions no longer influence stock prices (but they still influence forecast) + ** Changed the way stock's behave, particularly with regard to how the stock forecast occasionally "flips" * Scripts now start/stop instantly * Improved performance when starting up many copies of a new script (by Ornedan) diff --git a/src/Script/Script.ts b/src/Script/Script.ts index 6e11fd08b..7436e93a5 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -85,6 +85,7 @@ export class Script { */ markUpdated() { this.module = ""; + this.moduleSequenceNumber = ++globalModuleSequenceNumber; } /** @@ -109,12 +110,6 @@ export class Script { } } - // Marks that this script has been updated. Causes recompilation of NS2 modules. - markUpdated() { - this.module = ""; - this.moduleSequenceNumber = ++globalModuleSequenceNumber; - } - /** * Calculates and updates the script's RAM usage based on its code * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports diff --git a/src/Server/data/servers.ts b/src/Server/data/servers.ts index 7d75dcc65..738c1c10f 100644 --- a/src/Server/data/servers.ts +++ b/src/Server/data/servers.ts @@ -2,6 +2,7 @@ // This could actually be a JSON file as it should be constant metadata to be imported... import { IMinMaxRange } from "../../types"; +import { LocationName } from "../../Locations/data/LocationNames"; /** * The metadata describing the base state of servers on the network. @@ -82,7 +83,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 15, numOpenPortsRequired: 5, - organizationName: "ECorp", + organizationName: LocationName.AevumECorp, requiredHackingSkill: { max: 1400, min: 1050, @@ -98,7 +99,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 15, numOpenPortsRequired: 5, - organizationName: "MegaCorp", + organizationName: LocationName.Sector12MegaCorp, requiredHackingSkill: { max: 1350, min: 1100, @@ -117,7 +118,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 14, numOpenPortsRequired: 5, - organizationName: "Bachman & Associates", + organizationName: LocationName.AevumBachmanAndAssociates, requiredHackingSkill: { max: 1150, min: 900, @@ -144,7 +145,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 14, numOpenPortsRequired: 5, - organizationName: "Blade Industries", + organizationName: LocationName.Sector12BladeIndustries, requiredHackingSkill: { max: 1200, min: 900, @@ -164,7 +165,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 14, numOpenPortsRequired: 5, - organizationName: "New World Order", + organizationName: LocationName.VolhavenNWO, requiredHackingSkill: { max: 1300, min: 950, @@ -190,7 +191,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 14, numOpenPortsRequired: 5, - organizationName: "Clarke Incorporated", + organizationName: LocationName.AevumClarkeIncorporated, requiredHackingSkill: { max: 1250, min: 950, @@ -220,7 +221,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 13, numOpenPortsRequired: 5, - organizationName: "OmniTek Incorporated", + organizationName: LocationName.VolhavenOmniTekIncorporated, requiredHackingSkill: { max: 1100, min: 900, @@ -242,7 +243,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 13, numOpenPortsRequired: 5, - organizationName: "FourSigma", + organizationName: LocationName.Sector12FourSigma, requiredHackingSkill: { max: 1250, min: 900, @@ -264,7 +265,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 13, numOpenPortsRequired: 5, - organizationName: "KuaiGong International", + organizationName: LocationName.ChongqingKuaiGongInternational, requiredHackingSkill: { max: 1300, min: 950, @@ -291,7 +292,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 12, numOpenPortsRequired: 5, - organizationName: "Fulcrum Technologies", + organizationName: LocationName.AevumFulcrumTechnologies, requiredHackingSkill: { max: 1250, min: 950, @@ -307,7 +308,7 @@ export const serverMetadata: IServerMetadata[] = [ moneyAvailable: 1e6, networkLayer: 15, numOpenPortsRequired: 5, - organizationName: "Fulcrum Technologies Assets", + organizationName: LocationName.AevumFulcrumTechnologies, requiredHackingSkill: { max: 1600, min: 1100, @@ -327,7 +328,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 12, numOpenPortsRequired: 5, - organizationName: "Storm Technologies", + organizationName: LocationName.IshimaStormTechnologies, requiredHackingSkill: { max: 1075, min: 875, @@ -349,7 +350,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 9, numOpenPortsRequired: 5, - organizationName: "DefComm", + organizationName: LocationName.NewTokyoDefComm, requiredHackingSkill: { max: 1050, min: 850, @@ -398,7 +399,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 12, numOpenPortsRequired: 5, - organizationName: "Helios Labs", + organizationName: LocationName.VolhavenHeliosLabs, requiredHackingSkill: { max: 900, min: 800, @@ -425,7 +426,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 12, numOpenPortsRequired: 5, - organizationName: "VitaLife", + organizationName: LocationName.NewTokyoVitaLife, requiredHackingSkill: { max: 900, min: 775, @@ -447,7 +448,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 9, numOpenPortsRequired: 5, - organizationName: "Icarus Microsystems", + organizationName: LocationName.Sector12IcarusMicrosystems, requiredHackingSkill: { max: 925, min: 850, @@ -473,7 +474,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 9, numOpenPortsRequired: 4, - organizationName: "Universal Energy", + organizationName: LocationName.Sector12UniversalEnergy, requiredHackingSkill: { max: 900, min: 800, @@ -575,7 +576,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 7, numOpenPortsRequired: 5, - organizationName: "Galactic Cybersystems", + organizationName: LocationName.AevumGalacticCybersystems, requiredHackingSkill: { max: 875, min: 825, @@ -598,7 +599,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 7, numOpenPortsRequired: 5, - organizationName: "AeroCorp", + organizationName: LocationName.AevumAeroCorp, requiredHackingSkill: { max: 925, min: 850, @@ -625,7 +626,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 8, numOpenPortsRequired: 5, - organizationName: "Omnia Cybersystems", + organizationName: LocationName.VolhavenOmniaCybersystems, requiredHackingSkill: { max: 950, min: 850, @@ -700,7 +701,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 9, numOpenPortsRequired: 5, - organizationName: "Solaris Space Systems", + organizationName: LocationName.ChongqingSolarisSpaceSystems, requiredHackingSkill: { max: 850, min: 750, @@ -722,7 +723,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 8, numOpenPortsRequired: 5, - organizationName: "Delta One", + organizationName: LocationName.Sector12DeltaOne, requiredHackingSkill: { max: 900, min: 800, @@ -749,7 +750,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 7, numOpenPortsRequired: 4, - organizationName: "Global Pharmaceuticals", + organizationName: LocationName.NewTokyoGlobalPharmaceuticals, requiredHackingSkill: { max: 850, min: 750, @@ -771,7 +772,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 10, numOpenPortsRequired: 4, - organizationName: "Nova Medical", + organizationName: LocationName.IshimaNovaMedical, requiredHackingSkill: { max: 850, min: 775, @@ -845,7 +846,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 6, numOpenPortsRequired: 4, - organizationName: "Lexo Corporation", + organizationName: LocationName.VolhavenLexoCorp, requiredHackingSkill: { max: 750, min: 650, @@ -871,7 +872,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 6, numOpenPortsRequired: 3, - organizationName: "Rho Construction", + organizationName: LocationName.AevumRhoConstruction, requiredHackingSkill: { max: 525, min: 475, @@ -898,7 +899,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 6, numOpenPortsRequired: 4, - organizationName: "Alpha Enterprises", + organizationName: LocationName.Sector12AlphaEnterprises, requiredHackingSkill: { max: 600, min: 500, @@ -924,7 +925,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 6, numOpenPortsRequired: 4, - organizationName: "Aevum Police Network", + organizationName: LocationName.AevumPolice, requiredHackingSkill: { max: 450, min: 400, @@ -955,7 +956,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 5, numOpenPortsRequired: 3, - organizationName: "Rothman University Network", + organizationName: LocationName.Sector12RothmanUniversity, requiredHackingSkill: { max: 430, min: 370, @@ -981,7 +982,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 5, numOpenPortsRequired: 5, - organizationName: "ZB Institute of Technology Network", + organizationName: LocationName.VolhavenZBInstituteOfTechnology, requiredHackingSkill: { max: 775, min: 725, @@ -1012,7 +1013,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 5, numOpenPortsRequired: 3, - organizationName: "Summit University Network", + organizationName: LocationName.AevumSummitUniversity, requiredHackingSkill: { max: 475, min: 425, @@ -1034,7 +1035,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 5, numOpenPortsRequired: 4, - organizationName: "SysCore Securities", + organizationName: LocationName.VolhavenSysCoreSecurities, requiredHackingSkill: { max: 650, min: 550, @@ -1110,7 +1111,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 4, numOpenPortsRequired: 3, - organizationName: "CompuTek", + organizationName: LocationName.VolhavenCompuTek, requiredHackingSkill: { max: 400, min: 300, @@ -1134,7 +1135,7 @@ export const serverMetadata: IServerMetadata[] = [ moneyAvailable: 275000000, networkLayer: 4, numOpenPortsRequired: 3, - organizationName: "Netlink Technologies", + organizationName: LocationName.AevumNetLinkTechnologies, requiredHackingSkill: { max: 425, min: 375, @@ -1174,7 +1175,7 @@ export const serverMetadata: IServerMetadata[] = [ moneyAvailable: 2000000, networkLayer: 1, numOpenPortsRequired: 0, - organizationName: "Food N Stuff Supermarket", + organizationName: LocationName.Sector12FoodNStuff, requiredHackingSkill: 1, serverGrowth: 5, }, @@ -1196,7 +1197,7 @@ export const serverMetadata: IServerMetadata[] = [ moneyAvailable: 2500000, networkLayer: 1, numOpenPortsRequired: 0, - organizationName: "Joe's Guns", + organizationName: "Joes Guns", requiredHackingSkill: 10, serverGrowth: 20, }, @@ -1305,7 +1306,7 @@ export const serverMetadata: IServerMetadata[] = [ }, networkLayer: 3, numOpenPortsRequired: 2, - organizationName: "Omega Software", + organizationName: LocationName.IshimaOmegaSoftware, requiredHackingSkill: { max: 220, min: 180, diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts new file mode 100644 index 000000000..d026263b1 --- /dev/null +++ b/src/StockMarket/PlayerInfluencing.ts @@ -0,0 +1,6 @@ +/** + * Implementation of the mechanisms that allow the player to affect the + * Stock Market + */ +import { Server } from "../Server/Server"; +import { StockMarket } from "./StockMarket"; diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index 2147b7c16..9fe82eec1 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -226,6 +226,18 @@ export class Stock { } } + /** + * Change's the stock's second-order forecast during a stock market 'tick'. + * The change for the second-order forecast to increase is 50/50 + */ + cycleForecastForecast(changeAmt: number=0.1): void { + if (Math.random() < 0.5) { + this.otlkMagForecast = Math.min(this.otlkMagForecast + changeAmt, 100); + } else { + this.otlkMagForecast = Math.max(this.otlkMagForecast - changeAmt, 0); + } + } + /** * "Flip" the stock's second-order forecast. This can occur during a * stock market "cycle" (determined by RNG). It is used to simulate diff --git a/src/StockMarket/StockMarket.jsx b/src/StockMarket/StockMarket.jsx index 3dd1365a5..8b6993150 100644 --- a/src/StockMarket/StockMarket.jsx +++ b/src/StockMarket/StockMarket.jsx @@ -7,6 +7,7 @@ import { import { Order } from "./Order"; import { processOrders } from "./OrderProcessing"; import { Stock } from "./Stock"; +import { TicksPerCycle } from "./StockMarketConstants"; import { getStockMarket4SDataCost, getStockMarket4STixApiCost @@ -172,6 +173,7 @@ export function initStockMarket() { StockMarket.storedCycles = 0; StockMarket.lastUpdate = 0; + StockMarket.ticksUntilCycle = TicksPerCycle; } export function initSymbolToStockMap() { @@ -192,18 +194,11 @@ export function stockMarketCycle() { for (const name in StockMarket) { const stock = StockMarket[name]; if (!(stock instanceof Stock)) { continue; } - let thresh = 0.6; - if (stock.b) { thresh = 0.4; } + const roll = Math.random(); if (roll < 0.4) { stock.flipForecastForecast(); - } else if (roll < 0.6) { - stock.otlkMagForecast += 0.5; - stock.otlkMagForecast = Math.min(stock.otlkMagForecast * 1.02, 100); - } else if (roll < 0.8) { - stock.otlkMagForecast -= 0.5; - stock.otlkMagForecast = stock.otlkMagForecast * (1 / 1.02); - } else if (roll < 0.9) { + } else if (roll < 0.55) { stock.b = !stock.b; stock.flipForecastForecast(); } @@ -227,6 +222,13 @@ export function processStockPrices(numCycles=1) { StockMarket.lastUpdate = timeNow; StockMarket.storedCycles -= cyclesPerStockUpdate; + // Cycle + --StockMarket.ticksUntilCycle; + if (StockMarket.ticksUntilCycle <= 0) { + stockMarketCycle(); + StockMarket.ticksUntilCycle = TicksPerCycle; + } + var v = Math.random(); for (const name in StockMarket) { const stock = StockMarket[name]; @@ -271,6 +273,7 @@ export function processStockPrices(numCycles=1) { otlkMagChange = 1; } stock.cycleForecast(otlkMagChange); + stock.cycleForecastForecast(otlkMagChange / 2); // Shares required for price movement gradually approaches max over time stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovement + 10, stock.shareTxForMovement); diff --git a/src/StockMarket/StockMarketConstants.ts b/src/StockMarket/StockMarketConstants.ts new file mode 100644 index 000000000..ce0a6a4ad --- /dev/null +++ b/src/StockMarket/StockMarketConstants.ts @@ -0,0 +1,5 @@ +/** + * How many stock market 'ticks' before a 'cycle' is triggered. + * A 'tick' is whenver stock prices update + */ +export const TicksPerCycle = 100; diff --git a/src/StockMarket/StockMarketHelpers.ts b/src/StockMarket/StockMarketHelpers.ts index cfb37d9c0..c9533cef2 100644 --- a/src/StockMarket/StockMarketHelpers.ts +++ b/src/StockMarket/StockMarketHelpers.ts @@ -1,3 +1,6 @@ +/** + * Stock Market Helper Functions + */ import { Stock } from "./Stock"; import { PositionTypes } from "./data/PositionTypes"; import { CONSTANTS } from "../Constants"; diff --git a/src/engine.jsx b/src/engine.jsx index 20f2ff35b..b5d725494 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -70,7 +70,6 @@ import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { initSpecialServerIps } from "./Server/SpecialServerIps"; import { initSymbolToStockMap, - stockMarketCycle, processStockPrices, displayStockMarketContent } from "./StockMarket/StockMarket"; @@ -778,7 +777,6 @@ const Engine = { checkFactionInvitations: 100, passiveFactionGrowth: 600, messages: 150, - sCr: 1500, mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner) contractGeneration: 3000, // Generate Coding Contracts }, @@ -901,13 +899,6 @@ const Engine = { } } - if (Engine.Counters.sCr <= 0) { - if (Player.hasWseAccount) { - stockMarketCycle(); - } - Engine.Counters.sCr = 1500; - } - if (Engine.Counters.mechanicProcess <= 0) { if (Player.corporation instanceof Corporation) { Player.corporation.process(); diff --git a/test/StockMarketTests.js b/test/StockMarketTests.js index 93acfeb37..e6bb9d60e 100644 --- a/test/StockMarketTests.js +++ b/test/StockMarketTests.js @@ -308,6 +308,8 @@ describe("Stock Market Tests", function() { expect(StockMarket["storedCycles"]).to.equal(0); expect(StockMarket).to.have.property("lastUpdate"); expect(StockMarket["lastUpdate"]).to.equal(0); + expect(StockMarket).to.have.property("ticksUntilCycle"); + expect(StockMarket["ticksUntilCycle"]).to.be.a("number"); }); }); From 35f8a5115a9473e1350755449d4a2932b7c057b6 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 3 Jun 2019 22:21:36 -0700 Subject: [PATCH 27/39] Finished implementing player influencing on stock 2nd-order forecasts. Balanced recent stock market changes --- src/Constants.ts | 4 +- src/NetscriptFunctions.js | 14 +++- .../Player/PlayerObjectGeneralMethods.js | 14 ++-- src/StockMarket/IStockMarket.ts | 5 +- src/StockMarket/PlayerInfluencing.ts | 76 ++++++++++++++++++- src/StockMarket/Stock.ts | 16 +++- .../{StockMarket.jsx => StockMarket.tsx} | 75 ++++++++---------- src/StockMarket/StockMarketConstants.ts | 2 +- src/StockMarket/StockMarketHelpers.ts | 8 +- test/StockMarketTests.js | 58 +++++++++++++- 10 files changed, 209 insertions(+), 63 deletions(-) rename src/StockMarket/{StockMarket.jsx => StockMarket.tsx} (82%) diff --git a/src/Constants.ts b/src/Constants.ts index 7db9f67c9..4b9ae078d 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -224,7 +224,9 @@ export let CONSTANTS: IMap = { v0.47.0 * Stock Market changes: ** Transactions no longer influence stock prices (but they still influence forecast) - ** Changed the way stock's behave, particularly with regard to how the stock forecast occasionally "flips" + ** Changed the way stocks behave, particularly with regard to how the stock forecast occasionally "flips" + ** Hacking & growing a server can potentially affect the way the corresponding stock's forecast changes + ** Working for a company positively affects the way the corresponding stock's forecast changes * Scripts now start/stop instantly * Improved performance when starting up many copies of a new script (by Ornedan) diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index dd23c18c7..cb751f9ae 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -90,6 +90,10 @@ import { shortStock, sellShort, } from "./StockMarket/BuyingAndSelling"; +import { + influenceStockThroughServerHack, + influenceStockThroughServerGrow, +} from "./StockMarket/PlayerInfluencing"; import { Stock } from "./StockMarket/Stock"; import { StockMarket, @@ -439,7 +443,7 @@ function NetscriptFunctions(workerScript) { } return out; }, - hack : function(ip, { threads: requestedThreads } = {}){ + hack : function(ip, { threads: requestedThreads, stock } = {}){ updateDynamicRam("hack", getRamCost("hack")); if (ip === undefined) { throw makeRuntimeRejectMsg(workerScript, "Hack() call has incorrect number of arguments. Takes 1 argument"); @@ -501,6 +505,9 @@ function NetscriptFunctions(workerScript) { workerScript.scriptRef.log("Script SUCCESSFULLY hacked " + server.hostname + " for $" + formatNumber(moneyGained, 2) + " and " + formatNumber(expGainedOnSuccess, 4) + " exp (t=" + threads + ")"); } server.fortify(CONSTANTS.ServerFortifyAmount * Math.min(threads, maxThreadNeeded)); + if (stock) { + influenceStockThroughServerHack(server, moneyGained); + } return Promise.resolve(moneyGained); } else { // Player only gains 25% exp for failure? @@ -555,7 +562,7 @@ function NetscriptFunctions(workerScript) { return Promise.resolve(true); }); }, - grow : function(ip, { threads: requestedThreads } = {}){ + grow : function(ip, { threads: requestedThreads, stock } = {}){ updateDynamicRam("grow", getRamCost("grow")); const threads = resolveNetscriptRequestedThreads(workerScript, "grow", requestedThreads); if (ip === undefined) { @@ -596,6 +603,9 @@ function NetscriptFunctions(workerScript) { } workerScript.scriptRef.onlineExpGained += expGain; Player.gainHackingExp(expGain); + if (stock) { + influenceStockThroughServerGrow(server, moneyAfter - moneyBefore); + } return Promise.resolve(moneyAfter/moneyBefore); }); }, diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js index 385c836cf..6a2f4b404 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.js +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.js @@ -39,6 +39,7 @@ import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServer import { applySourceFile } from "../../SourceFile/applySourceFile"; import { SourceFiles } from "../../SourceFile/SourceFiles"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; +import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing"; import Decimal from "decimal.js"; @@ -580,8 +581,8 @@ export function startWork(companyName) { } export function work(numCycles) { - //Cap the number of cycles being processed to whatever would put you at - //the work time limit (8 hours) + // Cap the number of cycles being processed to whatever would put you at + // the work time limit (8 hours) var overMax = false; if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { overMax = true; @@ -589,21 +590,24 @@ export function work(numCycles) { } this.timeWorked += Engine._idleSpeed * numCycles; - this.workRepGainRate = this.getWorkRepGain(); + this.workRepGainRate = this.getWorkRepGain(); this.processWorkEarnings(numCycles); - //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money + // If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { return this.finishWork(false); } - var comp = Companies[this.companyName], companyRep = "0"; + const comp = Companies[this.companyName]; + let companyRep = "0"; if (comp == null || !(comp instanceof Company)) { console.error(`Could not find Company: ${this.companyName}`); } else { companyRep = comp.playerReputation; } + influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles); + const position = this.jobs[this.companyName]; var txt = document.getElementById("work-in-progress-text"); diff --git a/src/StockMarket/IStockMarket.ts b/src/StockMarket/IStockMarket.ts index e743e9173..eccfca12b 100644 --- a/src/StockMarket/IStockMarket.ts +++ b/src/StockMarket/IStockMarket.ts @@ -5,6 +5,7 @@ export type IStockMarket = { [key: string]: Stock; } & { lastUpdate: number; - storedCycles: number; Orders: IOrderBook; -} + storedCycles: number; + ticksUntilCycle: number; +}; diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts index d026263b1..0162475f4 100644 --- a/src/StockMarket/PlayerInfluencing.ts +++ b/src/StockMarket/PlayerInfluencing.ts @@ -2,5 +2,79 @@ * Implementation of the mechanisms that allow the player to affect the * Stock Market */ -import { Server } from "../Server/Server"; +import { Stock } from "./Stock"; import { StockMarket } from "./StockMarket"; + +import { Company } from "../Company/Company"; +import { Server } from "../Server/Server"; + +// Change in second-order forecast due to hacks/grows +const forecastForecastChangeFromHack = 0.1; + +// Change in second-order forecast due to company work +const forecastForecastChangeFromCompanyWork = 0.001; + +/** + * Potentially decreases a stock's second-order forecast when its corresponding + * server is hacked. The chance of the hack decreasing the stock's second-order + * forecast is dependent on what percentage of the server's money is hacked + * @param {Server} server - Server being hack()ed + * @param {number} moneyHacked - Amount of money stolen from the server + */ +export function influenceStockThroughServerHack(server: Server, moneyHacked: number): void { + const orgName = server.organizationName; + let stock: Stock | null = null; + if (typeof orgName === "string" && orgName !== "") { + stock = StockMarket[orgName]; + } + if (!(stock instanceof Stock)) { return; } + + const percTotalMoneyHacked = moneyHacked / server.moneyMax; + if (Math.random() < percTotalMoneyHacked) { + console.log(`Influencing stock ${stock.name}`); + stock.changeForecastForecast(stock.otlkMagForecast - forecastForecastChangeFromHack); + } +} + +/** + * Potentially increases a stock's second-order forecast when its corresponding + * server is grown (grow()). The chance of the grow() to increase the stock's + * second-order forecast is dependent on how much money is added to the server + * @param {Server} server - Server being grow()n + * @param {number} moneyHacked - Amount of money added to the server + */ +export function influenceStockThroughServerGrow(server: Server, moneyGrown: number): void { + const orgName = server.organizationName; + let stock: Stock | null = null; + if (typeof orgName === "string" && orgName !== "") { + stock = StockMarket[orgName]; + } + if (!(stock instanceof Stock)) { return; } + + const percTotalMoneyGrown = moneyGrown / server.moneyMax; + if (Math.random() < percTotalMoneyGrown) { + console.log(`Influencing stock ${stock.name}`); + stock.changeForecastForecast(stock.otlkMagForecast + forecastForecastChangeFromHack); + } +} + +/** + * Potentially increases a stock's second-order forecast when the player works for + * its corresponding company. + * @param {Company} company - Company being worked for + * @param {number} performanceMult - Effectiveness of player's work. Affects influence + * @param {number} cyclesOfWork - # game cycles of work being processed + */ +export function influenceStockThroughCompanyWork(company: Company, performanceMult: number, cyclesOfWork: number): void { + const compName = company.name; + let stock: Stock | null = null; + if (typeof compName === "string" && compName !== "") { + stock = StockMarket[compName]; + } + if (!(stock instanceof Stock)) { return; } + + if (Math.random() < 0.001 * cyclesOfWork) { + const change = forecastForecastChangeFromCompanyWork * performanceMult; + stock.changeForecastForecast(stock.otlkMagForecast + change); + } +} diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index 9fe82eec1..c4214fc21 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -187,6 +187,18 @@ export class Stock { this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5; } + /** + * Safely set the stock's second-order forecast to a new value + */ + changeForecastForecast(newff: number): void { + this.otlkMagForecast = newff; + if (this.otlkMagForecast > 100) { + this.otlkMagForecast = 100; + } else if (this.otlkMagForecast < 0) { + this.otlkMagForecast = 0; + } + } + /** * Set the stock to a new price. Also updates the stock's previous price tracker */ @@ -232,9 +244,9 @@ export class Stock { */ cycleForecastForecast(changeAmt: number=0.1): void { if (Math.random() < 0.5) { - this.otlkMagForecast = Math.min(this.otlkMagForecast + changeAmt, 100); + this.changeForecastForecast(this.otlkMagForecast + changeAmt); } else { - this.otlkMagForecast = Math.max(this.otlkMagForecast - changeAmt, 0); + this.changeForecastForecast(this.otlkMagForecast - changeAmt); } } diff --git a/src/StockMarket/StockMarket.jsx b/src/StockMarket/StockMarket.tsx similarity index 82% rename from src/StockMarket/StockMarket.jsx rename to src/StockMarket/StockMarket.tsx index 8b6993150..22902bc50 100644 --- a/src/StockMarket/StockMarket.jsx +++ b/src/StockMarket/StockMarket.tsx @@ -4,14 +4,12 @@ import { shortStock, sellShort, } from "./BuyingAndSelling"; +import { IOrderBook } from "./IOrderBook"; +import { IStockMarket } from "./IStockMarket"; import { Order } from "./Order"; import { processOrders } from "./OrderProcessing"; import { Stock } from "./Stock"; import { TicksPerCycle } from "./StockMarketConstants"; -import { - getStockMarket4SDataCost, - getStockMarket4STixApiCost -} from "./StockMarketCosts"; import { InitStockMetadata } from "./data/InitStockMetadata"; import { OrderTypes } from "./data/OrderTypes"; import { PositionTypes } from "./data/PositionTypes"; @@ -21,6 +19,7 @@ import { StockMarketRoot } from "./ui/Root"; import { CONSTANTS } from "../Constants"; import { WorkerScript } from "../Netscript/WorkerScript"; import { Player } from "../Player"; +import { IMap } from "../types"; import { Page, routing } from ".././ui/navigationTracking"; import { numeralWrapper } from ".././ui/numeralFormat"; @@ -28,17 +27,17 @@ import { numeralWrapper } from ".././ui/numeralFormat"; import { dialogBoxCreate } from "../../utils/DialogBox"; import { Reviver } from "../../utils/JSONReviver"; -import React from "react"; -import ReactDOM from "react-dom"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; -export let StockMarket = {}; // Maps full stock name -> Stock object -export let SymbolToStockMap = {}; // Maps symbol -> Stock object +export let StockMarket: IStockMarket | IMap = {}; // Maps full stock name -> Stock object +export let SymbolToStockMap: IMap = {}; // Maps symbol -> Stock object -export function placeOrder(stock, shares, price, type, position, workerScript=null) { +export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null) { const tixApi = (workerScript instanceof WorkerScript); if (!(stock instanceof Stock)) { if (tixApi) { - workerScript.log(`ERROR: Invalid stock passed to placeOrder() function`); + workerScript!.log(`ERROR: Invalid stock passed to placeOrder() function`); } else { dialogBoxCreate(`ERROR: Invalid stock passed to placeOrder() function`); } @@ -46,7 +45,7 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu } if (typeof shares !== "number" || typeof price !== "number") { if (tixApi) { - workerScript.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"); + workerScript!.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"); } else { dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"); } @@ -55,7 +54,7 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu const order = new Order(stock.symbol, shares, price, type, position); if (StockMarket["Orders"] == null) { - const orders = {}; + const orders: IOrderBook = {}; for (const name in StockMarket) { const stk = StockMarket[name]; if (!(stk instanceof Stock)) { continue; } @@ -68,7 +67,7 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu // Process to see if it should be executed immediately const processOrderRefs = { rerenderFn: displayStockMarketContent, - stockMarket: StockMarket, + stockMarket: StockMarket as IStockMarket, symbolToStockMap: SymbolToStockMap, } processOrders(stock, order.type, order.pos, processOrderRefs); @@ -78,7 +77,15 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu } // Returns true if successfully cancels an order, false otherwise -export function cancelOrder(params, workerScript=null) { +interface ICancelOrderParams { + order?: Order; + pos?: PositionTypes; + price?: number; + shares?: number; + stock?: Stock; + type?: OrderTypes; +} +export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null) { var tixApi = (workerScript instanceof WorkerScript); if (StockMarket["Orders"] == null) {return false;} if (params.order && params.order instanceof Order) { @@ -108,42 +115,24 @@ export function cancelOrder(params, workerScript=null) { stockOrders.splice(i, 1); displayStockMarketContent(); if (tixApi) { - workerScript.scriptRef.log("Successfully cancelled order: " + orderTxt); + workerScript!.scriptRef.log("Successfully cancelled order: " + orderTxt); } return true; } } if (tixApi) { - workerScript.scriptRef.log("Failed to cancel order: " + orderTxt); + workerScript!.scriptRef.log("Failed to cancel order: " + orderTxt); } return false; } return false; } -export function loadStockMarket(saveString) { +export function loadStockMarket(saveString: string) { if (saveString === "") { StockMarket = {}; } else { StockMarket = JSON.parse(saveString, Reviver); - - // Backwards compatibility for v0.47.0 - const orderBook = StockMarket["Orders"]; - if (orderBook != null) { - // For each order, set its 'stockSymbol' property equal to the - // symbol of its 'stock' property - for (const stockSymbol in orderBook) { - const ordersForStock = orderBook[stockSymbol]; - if (Array.isArray(ordersForStock)) { - for (const order of ordersForStock) { - if (order instanceof Order && order.stock instanceof Stock) { - order.stockSymbol = order.stock.symbol; - } - } - } - } - console.log(`Converted Stock Market order book to v0.47.0 format`); - } } } @@ -163,7 +152,7 @@ export function initStockMarket() { StockMarket[name] = new Stock(metadata); } - const orders = {}; + const orders: IOrderBook = {}; for (const name in StockMarket) { const stock = StockMarket[name]; if (!(stock instanceof Stock)) { continue; } @@ -196,11 +185,13 @@ export function stockMarketCycle() { if (!(stock instanceof Stock)) { continue; } const roll = Math.random(); - if (roll < 0.4) { + if (roll < 0.2) { stock.flipForecastForecast(); - } else if (roll < 0.55) { + StockMarket.ticksUntilCycle = 4 * TicksPerCycle; + } else if (roll < 0.65) { stock.b = !stock.b; stock.flipForecastForecast(); + StockMarket.ticksUntilCycle = TicksPerCycle; } } } @@ -226,7 +217,6 @@ export function processStockPrices(numCycles=1) { --StockMarket.ticksUntilCycle; if (StockMarket.ticksUntilCycle <= 0) { stockMarketCycle(); - StockMarket.ticksUntilCycle = TicksPerCycle; } var v = Math.random(); @@ -251,7 +241,7 @@ export function processStockPrices(numCycles=1) { const c = Math.random(); const processOrderRefs = { rerenderFn: displayStockMarketContent, - stockMarket: StockMarket, + stockMarket: StockMarket as IStockMarket, symbolToStockMap: SymbolToStockMap, } if (c < chc) { @@ -282,7 +272,7 @@ export function processStockPrices(numCycles=1) { displayStockMarketContent(); } -let stockMarketContainer = null; +let stockMarketContainer: HTMLElement | null = null; function setStockMarketContainer() { stockMarketContainer = document.getElementById("stock-market-container"); document.removeEventListener("DOMContentLoaded", setStockMarketContainer); @@ -301,6 +291,7 @@ export function displayStockMarketContent() { } if (stockMarketContainer instanceof HTMLElement) { + const castedStockMarket = StockMarket as IStockMarket; ReactDOM.render( , stockMarketContainer ) diff --git a/src/StockMarket/StockMarketConstants.ts b/src/StockMarket/StockMarketConstants.ts index ce0a6a4ad..ddcdbb34e 100644 --- a/src/StockMarket/StockMarketConstants.ts +++ b/src/StockMarket/StockMarketConstants.ts @@ -2,4 +2,4 @@ * How many stock market 'ticks' before a 'cycle' is triggered. * A 'tick' is whenver stock prices update */ -export const TicksPerCycle = 100; +export const TicksPerCycle = 80; diff --git a/src/StockMarket/StockMarketHelpers.ts b/src/StockMarket/StockMarketHelpers.ts index c9533cef2..9126edc93 100644 --- a/src/StockMarket/StockMarketHelpers.ts +++ b/src/StockMarket/StockMarketHelpers.ts @@ -6,7 +6,7 @@ import { PositionTypes } from "./data/PositionTypes"; import { CONSTANTS } from "../Constants"; // Amount by which a stock's forecast changes during each price movement -export const forecastChangePerPriceMovement = 0.005; +export const forecastChangePerPriceMovement = 0.01; /** * Calculate the total cost of a "buy" transaction. This accounts for spread and commission. @@ -97,10 +97,8 @@ export function processTransactionForecastMovement(stock: Stock, shares: number) // Forecast always decreases in magnitude - const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1)); - if (stock.otlkMag > 10) { - stock.otlkMag -= forecastChange; - } + const forecastChange = forecastChangePerPriceMovement * (numIterations - 1); + stock.otlkMag = Math.max(6, stock.otlkMag - forecastChange); } /** diff --git a/test/StockMarketTests.js b/test/StockMarketTests.js index e6bb9d60e..62b2ac90c 100644 --- a/test/StockMarketTests.js +++ b/test/StockMarketTests.js @@ -128,6 +128,22 @@ describe("Stock Market Tests", function() { }); }); + describe("#changeForecastForecast()", function() { + it("should get the stock's second-order forecast property", function() { + stock.changeForecastForecast(99); + expect(stock.otlkMagForecast).to.equal(99); + stock.changeForecastForecast(1); + expect(stock.otlkMagForecast).to.equal(1); + }); + + it("should prevent values outside of 0-100", function() { + stock.changeForecastForecast(101); + expect(stock.otlkMagForecast).to.equal(100); + stock.changeForecastForecast(-1); + expect(stock.otlkMagForecast).to.equal(0); + }); + }); + describe("#changePrice()", function() { it("should set both the last price and current price properties", function() { const newPrice = 20e3; @@ -139,7 +155,28 @@ describe("Stock Market Tests", function() { describe("#cycleForecast()", function() { it("should appropriately change the otlkMag by the given amount when b=true", function() { + stock.getForecastIncreaseChance = () => { return 1; } + stock.cycleForecast(5); + expect(stock.otlkMag).to.equal(ctorParams.otlkMag + 5); + stock.getForecastIncreaseChance = () => { return 0; } + stock.cycleForecast(10); + expect(stock.otlkMag).to.equal(ctorParams.otlkMag - 5); + }); + + it("should NOT(!) the stock's 'b' property if it causes 'otlkMag' to go below 0", function() { + stock.getForecastIncreaseChance = () => { return 0; } + stock.cycleForecast(25); + expect(stock.otlkMag).to.equal(5); + expect(stock.b).to.equal(false); + }); + }); + + describe("#cycleForecastForecast()", function() { + it("should increase the stock's second-order forecast by a given amount", function() { + const expected = [65, 75]; + stock.cycleForecastForecast(5); + expect(stock.otlkMagForecast).to.be.oneOf(expected); }); }); @@ -266,6 +303,10 @@ describe("Stock Market Tests", function() { stock.otlkMagForecast = 50; stock.otlkMag = 0; expect(stock.getForecastIncreaseChance()).to.equal(0.5); + + stock.otlkMagForecast = 25; + stock.otlkMag = 5; // Asolute forecast of 45 + expect(stock.getForecastIncreaseChance()).to.equal(0.3); }); }); }); @@ -343,12 +384,14 @@ describe("Stock Market Tests", function() { const stock = StockMarket[stockName]; if (!(stock instanceof Stock)) { continue; } initialValues[stock.symbol] = { - price: stock.price, + b: stock.b, otlkMag: stock.otlkMag, + price: stock.price, } } // Don't know or care how many exact cycles are required + StockMarket.lastUpdate = new Date().getTime() - 5e3; processStockPrices(1e9); // Both price and 'otlkMag' should be different @@ -356,7 +399,14 @@ describe("Stock Market Tests", function() { const stock = StockMarket[stockName]; if (!(stock instanceof Stock)) { continue; } expect(initialValues[stock.symbol].price).to.not.equal(stock.price); - expect(initialValues[stock.symbol].otlkMag).to.not.equal(stock.otlkMag); + // expect(initialValues[stock.symbol].otlkMag).to.not.equal(stock.otlkMag); + expect(initialValues[stock.symbol]).to.satisfy(function(initValue) { + if ((initValue.otlkMag !== stock.otlkMag) || (initValue.b !== stock.b)) { + return true; + } else { + return false; + } + }); } }); }); @@ -882,6 +932,10 @@ describe("Stock Market Tests", function() { }); describe("Order Processing", function() { + // TODO + }); + describe("Player Influencing", function() { + // TODO }); }); From dc5f4e6694f3681c1d0f5a9fe1ed0f1bf347d29b Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 3 Jun 2019 23:05:25 -0700 Subject: [PATCH 28/39] Updated documentation for new Stock Market changes. More rebalancing for recent stock market changes --- doc/source/basicgameplay/stockmarket.rst | 72 ++++++++++++-------- doc/source/netscript/basicfunctions/grow.rst | 2 + doc/source/netscript/basicfunctions/hack.rst | 2 + src/Constants.ts | 2 +- src/StockMarket/StockMarket.tsx | 7 +- src/StockMarket/StockMarketConstants.ts | 2 +- src/StockMarket/StockMarketHelpers.ts | 5 +- src/StockMarket/data/InitStockMetadata.ts | 12 ++-- 8 files changed, 66 insertions(+), 38 deletions(-) diff --git a/doc/source/basicgameplay/stockmarket.rst b/doc/source/basicgameplay/stockmarket.rst index 49b86e3bc..7e44d6c22 100644 --- a/doc/source/basicgameplay/stockmarket.rst +++ b/doc/source/basicgameplay/stockmarket.rst @@ -51,31 +51,6 @@ bid price. Note that this is reversed for a short position. Purchasing a stock in the short position will occur at the stock's bid price, and selling a stock in the short position will occur at the stock's ask price. -.. _gameplay_stock_market_spread_price_movement: - -Transactions Influencing Stock Price -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Buying or selling a large number of shares of a stock will influence that stock's price. - -Buying a stock in the long position will cause that stock's price to -increase. Selling a stock in the long position will cause the stock's price to decrease. -The reverse occurs for the short position. The effect of this depends on how many shares -are being transacted. More shares will have a bigger effect on the stock price. If -only a small number of shares are being transacted, the stock price may not be affected. - -Note that this "influencing" of the stock price **can happen in the middle of a transaction**. -For example, assume you are selling 1m shares of a stock. That stock's bid price -is currently $100. However, selling 100k shares of the stock causes its price to drop -by 1%. This means that for your transaction of 1m shares, the first 100k shares will be -sold at $100. Then the next 100k shares will be sold at $99. Then the next 100k shares will -be sold at $98.01, and so on. - -This is an important concept to keep in mind if you are trying to purchase/sell a -large number of shares, as **it can negatively affect your profits**. - -.. note:: On the Stock Market UI, the displayed profit of a position does not take - this "influencing" into account. - Transactions Influencing Stock Forecast ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In addition to influencing stock price, buying or selling a large number of shares @@ -142,7 +117,49 @@ A Stop Order to sell will execute if the stock's price >= order's price. However, the transaction causes the stock's price to increase before the order finishes executing all of the shares. Your Limit Order will stop executing, and will continue only when the stock's price drops back to - $5 or below. + $5 or below. + +.. _gameplay_stock_market_player_actions_influencing_stock: + +Player Actions Influencing Stocks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +It is possible for your actions elsewhere in the game to influence the stock market. + +Hacking + If a server has a corresponding stock (e.g. *foodnstuff* server -> FoodNStuff + stock), then hacking that server can + cause the corresponding stock's forecast to trend downwards in value + over time. + + This effect only occurs if you set the *stock* option to + true when calling the :js:func:`hack` function. The chance that hacking a + server will cause this effect is based on what percentage of the + server's total money you steal. + + A single hack will have a minor + effect, but continuously hacking a server for lots of money over time + will have a noticeable effect in making the stock's forecast trend downwards. + +Growing + If a server has a corresponding stock (e.g. *foodnstuff* server -> FoodNStuff + stock), then growing that server's money can cause the corresponding stock's + forecast to trend upwards in value over time. + + This effect only occurs if you set the *stock* option to true when calling the + :js:func:`grow` function. The chance that growing a server will cause this + effect is based on what percentage of the server's total money to add to it. + + A single grow operation will have a minor effect, but continuously growing + a server for lots of money over time will have a noticeable effect in making + the stock's forecast trend upwards. + +Working for a Company + If a company has a corresponding stock, then working for that company will + cause the corresponding stock's forecast to (slowly) trend upwards in value + over time. + + The potency of this effect is based on how "effective" you are when you work + (i.e. its based on your stats). Automating the Stock Market --------------------------- @@ -159,7 +176,8 @@ are hidden, although some of them can be made visible by purchasing the Four Sigma (4S) Market Data upgrade. Some examples of these properties are: * Volatility -* Likelihood of increasing or decreasing +* Likelihood of increasing or decreasing (i.e. the stock's forecast) +* Likelihood of forecast increasing or decreasing (i.e. the stock's second-order forecast) * How easily a stock's price/forecast is influenced by transactions * Spread percentage * Maximum price (not a real maximum, more of a "soft cap") diff --git a/doc/source/netscript/basicfunctions/grow.rst b/doc/source/netscript/basicfunctions/grow.rst index 026e825cd..fc5257f11 100644 --- a/doc/source/netscript/basicfunctions/grow.rst +++ b/doc/source/netscript/basicfunctions/grow.rst @@ -8,6 +8,8 @@ grow() Netscript Function * threads (*number*) - Number of threads to use for this function. Must be less than or equal to the number of threads the script is running with. + * stock (*boolean*) - If true, the function can affect the stock market. See + :ref:`gameplay_stock_market_player_actions_influencing_stock` :returns: The number by which the money on the server was multiplied for the growth :RAM cost: 0.15 GB diff --git a/doc/source/netscript/basicfunctions/hack.rst b/doc/source/netscript/basicfunctions/hack.rst index 569fb323d..e7f7718ea 100644 --- a/doc/source/netscript/basicfunctions/hack.rst +++ b/doc/source/netscript/basicfunctions/hack.rst @@ -8,6 +8,8 @@ hack() Netscript Function * threads (*number*) - Number of threads to use for this function. Must be less than or equal to the number of threads the script is running with. + * stock (*boolean*) - If true, the function can affect the stock market. See + :ref:`gameplay_stock_market_player_actions_influencing_stock` :returns: The amount of money stolen if the hack is successful, and zero otherwise :RAM cost: 0.1 GB diff --git a/src/Constants.ts b/src/Constants.ts index 4b9ae078d..1b9098645 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -229,7 +229,7 @@ export let CONSTANTS: IMap = { ** Working for a company positively affects the way the corresponding stock's forecast changes * Scripts now start/stop instantly - * Improved performance when starting up many copies of a new script (by Ornedan) + * Improved performance when starting up many copies of a new NetscriptJS script (by Ornedan) * Dialog boxes can now be closed with the ESC key (by jaguilar) * NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar) * write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar) diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index 22902bc50..b3d2bf290 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -185,10 +185,10 @@ export function stockMarketCycle() { if (!(stock instanceof Stock)) { continue; } const roll = Math.random(); - if (roll < 0.2) { + if (roll < 0.1) { stock.flipForecastForecast(); StockMarket.ticksUntilCycle = 4 * TicksPerCycle; - } else if (roll < 0.65) { + } else if (roll < 0.55) { stock.b = !stock.b; stock.flipForecastForecast(); StockMarket.ticksUntilCycle = TicksPerCycle; @@ -214,6 +214,9 @@ export function processStockPrices(numCycles=1) { StockMarket.storedCycles -= cyclesPerStockUpdate; // Cycle + if (StockMarket.ticksUntilCycle == null || typeof StockMarket.ticksUntilCycle !== "number") { + StockMarket.ticksUntilCycle = TicksPerCycle; + } --StockMarket.ticksUntilCycle; if (StockMarket.ticksUntilCycle <= 0) { stockMarketCycle(); diff --git a/src/StockMarket/StockMarketConstants.ts b/src/StockMarket/StockMarketConstants.ts index ddcdbb34e..789d17e82 100644 --- a/src/StockMarket/StockMarketConstants.ts +++ b/src/StockMarket/StockMarketConstants.ts @@ -2,4 +2,4 @@ * How many stock market 'ticks' before a 'cycle' is triggered. * A 'tick' is whenver stock prices update */ -export const TicksPerCycle = 80; +export const TicksPerCycle = 70; diff --git a/src/StockMarket/StockMarketHelpers.ts b/src/StockMarket/StockMarketHelpers.ts index 9126edc93..61c1bba2d 100644 --- a/src/StockMarket/StockMarketHelpers.ts +++ b/src/StockMarket/StockMarketHelpers.ts @@ -98,7 +98,10 @@ export function processTransactionForecastMovement(stock: Stock, shares: number) // Forecast always decreases in magnitude const forecastChange = forecastChangePerPriceMovement * (numIterations - 1); - stock.otlkMag = Math.max(6, stock.otlkMag - forecastChange); + const changeLimit = 6; + if (stock.otlkMag > changeLimit) { + stock.otlkMag = Math.max(changeLimit, stock.otlkMag - forecastChange); + } } /** diff --git a/src/StockMarket/data/InitStockMetadata.ts b/src/StockMarket/data/InitStockMetadata.ts index 2b10450f9..3e84c8cd0 100644 --- a/src/StockMarket/data/InitStockMetadata.ts +++ b/src/StockMarket/data/InitStockMetadata.ts @@ -672,8 +672,8 @@ export const InitStockMetadata: IConstructorParams[] = [ marketCap: 58e9, mv: { divisor: 100, - max: 430, - min: 400, + max: 400, + min: 200, }, name: LocationName.AevumNetLinkTechnologies, otlkMag: 1, @@ -750,8 +750,8 @@ export const InitStockMetadata: IConstructorParams[] = [ marketCap: 30e9, mv: { divisor: 100, - max: 300, - min: 260, + max: 275, + min: 100, }, name: "Sigma Cosmetics", otlkMag: 0, @@ -776,8 +776,8 @@ export const InitStockMetadata: IConstructorParams[] = [ marketCap: 42e9, mv: { divisor: 100, - max: 400, - min: 360, + max: 350, + min: 200, }, name: "Joes Guns", otlkMag: 1, From 63483837bc0b742436484067be145ba85176a92c Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 3 Jun 2019 23:10:10 -0700 Subject: [PATCH 29/39] Fixed version in changelog --- src/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Constants.ts b/src/Constants.ts index 1b9098645..babe87666 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -221,7 +221,7 @@ export let CONSTANTS: IMap = { LatestUpdate: ` - v0.47.0 + v0.47.1 * Stock Market changes: ** Transactions no longer influence stock prices (but they still influence forecast) ** Changed the way stocks behave, particularly with regard to how the stock forecast occasionally "flips" From 00f8c0a51ff42384b3e58c02e9dc93072a4d2f1b Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 4 Jun 2019 21:31:45 -0700 Subject: [PATCH 30/39] Removed debug stuff for beta branch --- src/StockMarket/PlayerInfluencing.ts | 2 -- src/StockMarket/ui/StockTickerHeaderText.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts index 0162475f4..f73006f2a 100644 --- a/src/StockMarket/PlayerInfluencing.ts +++ b/src/StockMarket/PlayerInfluencing.ts @@ -31,7 +31,6 @@ export function influenceStockThroughServerHack(server: Server, moneyHacked: num const percTotalMoneyHacked = moneyHacked / server.moneyMax; if (Math.random() < percTotalMoneyHacked) { - console.log(`Influencing stock ${stock.name}`); stock.changeForecastForecast(stock.otlkMagForecast - forecastForecastChangeFromHack); } } @@ -53,7 +52,6 @@ export function influenceStockThroughServerGrow(server: Server, moneyGrown: numb const percTotalMoneyGrown = moneyGrown / server.moneyMax; if (Math.random() < percTotalMoneyGrown) { - console.log(`Influencing stock ${stock.name}`); stock.changeForecastForecast(stock.otlkMagForecast + forecastForecastChangeFromHack); } } diff --git a/src/StockMarket/ui/StockTickerHeaderText.tsx b/src/StockMarket/ui/StockTickerHeaderText.tsx index 3227f17aa..1c1725df7 100644 --- a/src/StockMarket/ui/StockTickerHeaderText.tsx +++ b/src/StockMarket/ui/StockTickerHeaderText.tsx @@ -27,7 +27,7 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement { let plusOrMinus = stock.b; // True for "+", false for "-" if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus } hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1); - hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; + // hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; } let styleMarkup = { From a15041da7596530e322c6599a54a836befbbbb68 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 9 Jun 2019 15:12:33 -0700 Subject: [PATCH 31/39] More rebalancing for stock market changes. Transactions now affect second-order forecast (very slightly) --- src/StockMarket/OrderProcessing.ts | 2 +- src/StockMarket/Stock.ts | 28 ++++++ src/StockMarket/StockMarket.tsx | 8 +- src/StockMarket/StockMarketHelpers.ts | 16 ++-- src/StockMarket/data/InitStockMetadata.ts | 8 +- src/StockMarket/ui/StockTickerHeaderText.tsx | 2 +- test/StockMarketTests.js | 91 +++++++++++++++++++- 7 files changed, 134 insertions(+), 21 deletions(-) diff --git a/src/StockMarket/OrderProcessing.ts b/src/StockMarket/OrderProcessing.ts index f77e29a08..adfe0b967 100644 --- a/src/StockMarket/OrderProcessing.ts +++ b/src/StockMarket/OrderProcessing.ts @@ -96,7 +96,7 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi /** * Execute a Stop or Limit Order. * @param {Order} order - Order being executed - * @param {IStockMarket} stockMarket - Reference to StockMarket object + * @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function */ function executeOrder(order: Order, refs: IProcessOrderRefs) { const stock = refs.symbolToStockMap[order.stockSymbol]; diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index c4214fc21..a95455eba 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -6,6 +6,8 @@ import { } from "../../utils/JSONReviver"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; +export const StockForecastInfluenceLimit = 5; + export interface IConstructorParams { b: boolean; initPrice: number | IMinMaxRange; @@ -290,6 +292,32 @@ export class Stock { return (50 + Math.min(Math.max(diff, -45), 45)) / 100; } + /** + * Changes a stock's forecast. This is used when the stock is influenced + * by a transaction. The stock's forecast always goes towards 50, but the + * movement is capped by a certain threshold/limit + */ + influenceForecast(change: number): void { + if (this.otlkMag > StockForecastInfluenceLimit) { + this.otlkMag = Math.max(StockForecastInfluenceLimit, this.otlkMag - change); + } + } + + /** + * Changes a stock's second-order forecast. This is used when the stock is + * influenced by a transaction. The stock's second-order forecast always + * goes towards 50. + */ + influenceForecastForecast(change: number): void { + if (this.otlkMagForecast > 50) { + this.otlkMagForecast -= change; + this.otlkMagForecast = Math.max(50, this.otlkMagForecast); + } else if (this.otlkMagForecast < 50) { + this.otlkMagForecast += change; + this.otlkMagForecast = Math.min(50, this.otlkMagForecast); + } + } + /** * Serialize the Stock to a JSON save state. */ diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index b3d2bf290..c8fa589ed 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -187,12 +187,12 @@ export function stockMarketCycle() { const roll = Math.random(); if (roll < 0.1) { stock.flipForecastForecast(); - StockMarket.ticksUntilCycle = 4 * TicksPerCycle; } else if (roll < 0.55) { stock.b = !stock.b; stock.flipForecastForecast(); - StockMarket.ticksUntilCycle = TicksPerCycle; } + + StockMarket.ticksUntilCycle = TicksPerCycle; } } @@ -262,8 +262,8 @@ export function processStockPrices(numCycles=1) { } let otlkMagChange = stock.otlkMag * av; - if (stock.otlkMag < 1) { - otlkMagChange = 1; + if (stock.otlkMag < 5) { + otlkMagChange *= 10; } stock.cycleForecast(otlkMagChange); stock.cycleForecastForecast(otlkMagChange / 2); diff --git a/src/StockMarket/StockMarketHelpers.ts b/src/StockMarket/StockMarketHelpers.ts index 61c1bba2d..476305668 100644 --- a/src/StockMarket/StockMarketHelpers.ts +++ b/src/StockMarket/StockMarketHelpers.ts @@ -6,7 +6,7 @@ import { PositionTypes } from "./data/PositionTypes"; import { CONSTANTS } from "../Constants"; // Amount by which a stock's forecast changes during each price movement -export const forecastChangePerPriceMovement = 0.01; +export const forecastChangePerPriceMovement = 0.006; /** * Calculate the total cost of a "buy" transaction. This accounts for spread and commission. @@ -60,7 +60,8 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po } /** - * Processes a stock's change in forecast whenever it is transacted + * Processes a stock's change in forecast & second-order forecast + * whenever it is transacted * @param {Stock} stock - Stock being sold * @param {number} shares - Number of sharse being transacted * @param {PositionTypes} posType - Long or short position @@ -78,7 +79,8 @@ export function processTransactionForecastMovement(stock: Stock, shares: number) stock.shareTxUntilMovement -= shares; if (stock.shareTxUntilMovement <= 0) { stock.shareTxUntilMovement = stock.shareTxForMovement; - stock.otlkMag -= (forecastChangePerPriceMovement); + stock.influenceForecast(forecastChangePerPriceMovement); + stock.influenceForecastForecast(forecastChangePerPriceMovement * (stock.mv / 100)); } return; @@ -95,13 +97,11 @@ export function processTransactionForecastMovement(stock: Stock, shares: number) stock.shareTxUntilMovement = stock.shareTxForMovement; } - // Forecast always decreases in magnitude const forecastChange = forecastChangePerPriceMovement * (numIterations - 1); - const changeLimit = 6; - if (stock.otlkMag > changeLimit) { - stock.otlkMag = Math.max(changeLimit, stock.otlkMag - forecastChange); - } + const forecastForecastChange = forecastChange * (stock.mv / 100); + stock.influenceForecast(forecastChange); + stock.influenceForecastForecast(forecastForecastChange); } /** diff --git a/src/StockMarket/data/InitStockMetadata.ts b/src/StockMarket/data/InitStockMetadata.ts index 3e84c8cd0..44bad8a9a 100644 --- a/src/StockMarket/data/InitStockMetadata.ts +++ b/src/StockMarket/data/InitStockMetadata.ts @@ -761,8 +761,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 6, }, shareTxForMovement: { - max: 84e3, - min: 24e3, + max: 70e3, + min: 20e3, }, symbol: StockSymbols["Sigma Cosmetics"], }, @@ -787,8 +787,8 @@ export const InitStockMetadata: IConstructorParams[] = [ min: 6, }, shareTxForMovement: { - max: 64e3, - min: 18e3, + max: 52e3, + min: 15e3, }, symbol: StockSymbols["Joes Guns"], }, diff --git a/src/StockMarket/ui/StockTickerHeaderText.tsx b/src/StockMarket/ui/StockTickerHeaderText.tsx index 1c1725df7..3227f17aa 100644 --- a/src/StockMarket/ui/StockTickerHeaderText.tsx +++ b/src/StockMarket/ui/StockTickerHeaderText.tsx @@ -27,7 +27,7 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement { let plusOrMinus = stock.b; // True for "+", false for "-" if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus } hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1); - // hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; + hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; } let styleMarkup = { diff --git a/test/StockMarketTests.js b/test/StockMarketTests.js index 62b2ac90c..fdf7f07ac 100644 --- a/test/StockMarketTests.js +++ b/test/StockMarketTests.js @@ -8,7 +8,7 @@ import { } from "../src/StockMarket/BuyingAndSelling"; import { Order } from "../src/StockMarket/Order"; import { processOrders } from "../src/StockMarket/OrderProcessing"; -import { Stock } from "../src/StockMarket/Stock"; +import { Stock , StockForecastInfluenceLimit } from "../src/StockMarket/Stock"; import { deleteStockMarket, initStockMarket, @@ -40,7 +40,7 @@ describe("Stock Market Tests", function() { b: true, initPrice: 10e3, marketCap: 5e9, - mv: 1, + mv: 2, name: "MockStock", otlkMag: 20, spreadPerc: 1, @@ -309,6 +309,57 @@ describe("Stock Market Tests", function() { expect(stock.getForecastIncreaseChance()).to.equal(0.3); }); }); + + describe("#influenceForecast()", function() { + beforeEach(function() { + stock.otlkMag = 10; + }); + + it("should change the forecast's value towards 50", function() { + stock.influenceForecast(2); + expect(stock.otlkMag).to.equal(8); + }); + + it("should not care about whether the stock is in bull or bear mode", function() { + stock.b = true; + stock.influenceForecast(1); + expect(stock.otlkMag).to.equal(9); + + stock.b = false; + stock.influenceForecast(2); + expect(stock.otlkMag).to.equal(7); + }); + + it("should not influence the forecast beyond the limit", function() { + stock.influenceForecast(10); + expect(stock.otlkMag).to.equal(StockForecastInfluenceLimit); + + stock.influenceForecast(10); + expect(stock.otlkMag).to.equal(StockForecastInfluenceLimit); + }); + }); + + describe("#influenceForecastForecast()", function() { + it("should change the second-order forecast's value towards 50", function() { + stock.otlkMagForecast = 75; + stock.influenceForecastForecast(15); + expect(stock.otlkMagForecast).to.equal(60); + + stock.otlkMagForecast = 25; + stock.influenceForecastForecast(15); + expect(stock.otlkMagForecast).to.equal(40); + }); + + it("should not change the second-order forecast past 50", function() { + stock.otlkMagForecast = 40; + stock.influenceForecastForecast(20); + expect(stock.otlkMagForecast).to.equal(50); + + stock.otlkMagForecast = 60; + stock.influenceForecastForecast(20); + expect(stock.otlkMagForecast).to.equal(50); + }); + }); }); describe("StockMarket object", function() { @@ -508,6 +559,16 @@ describe("Stock Market Tests", function() { return origForecast - forecastChangePerPriceMovement * (n - 1); } + function getNthForecastForecast(origForecastForecast, n) { + if (stock.otlkMagForecast > 50) { + const expected = origForecastForecast - (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100)); + return expected < 50 ? 50 : expected; + } else if (stock.otlkMagForecast < 50) { + const expected = origForecastForecast + (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100)); + return expected > 50 ? 50 : expected; + } + } + describe("processTransactionForecastMovement() for buy transactions", function() { const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2); const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares; @@ -547,69 +608,85 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that triggers forecast movements", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); it("should properly evaluate SHORT transactions that triggers forecast movements", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); }); @@ -932,7 +1009,15 @@ describe("Stock Market Tests", function() { }); describe("Order Processing", function() { - // TODO + before(function() { + expect(initStockMarket).to.not.throw(); + expect(initSymbolToStockMap).to.not.throw(); + }); + + describe() + describe("executeOrder()", function() { + + }); }); describe("Player Influencing", function() { From 7301946236f6559f3eba9306825404a374b9440e Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sun, 9 Jun 2019 21:23:48 -0700 Subject: [PATCH 32/39] Added and Updated Stock Market tests for the new changes --- package-lock.json | 14 +- package.json | 2 + src/PersonObjects/IPlayer.ts | 1 + src/StockMarket/OrderProcessing.ts | 7 +- src/StockMarket/PlayerInfluencing.ts | 4 +- src/StockMarket/StockMarket.tsx | 10 +- src/StockMarket/StockMarketConstants.ts | 2 +- ...tockMarketTests.js => StockMarketTests.ts} | 410 +++++++++++++++--- 8 files changed, 375 insertions(+), 75 deletions(-) rename test/{StockMarketTests.js => StockMarketTests.ts} (73%) diff --git a/package-lock.json b/package-lock.json index 31729a31c..33b20a464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "bitburner", - "version": "0.46.2", + "version": "0.47.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -361,6 +361,18 @@ "integrity": "sha512-LAQ1d4OPfSJ/BMbI2DuizmYrrkD9JMaTdi2hQTlI53lQ4kRQPyZQRS4CYQ7O66bnBBnP/oYdRxbk++X0xuFU6A==", "dev": true }, + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, "@types/numeral": { "version": "0.0.25", "resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz", diff --git a/package.json b/package.json index c200e5fd4..aff6ee979 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,8 @@ "devDependencies": { "@babel/core": "^7.3.4", "@babel/preset-react": "^7.0.0", + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", "babel-loader": "^8.0.5", "beautify-lint": "^1.0.3", "benchmark": "^2.1.1", diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 09146f514..86da75d01 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -151,6 +151,7 @@ export interface IPlayer { reapplyAllSourceFiles(): void; regenerateHp(amt: number): void; recordMoneySource(amt: number, source: string): void; + setMoney(amt: number): void; startBladeburner(p: object): void; startClass(costMult: number, expMult: number, className: string): void; startCorporation(corpName: string, additionalShares?: number): void; diff --git a/src/StockMarket/OrderProcessing.ts b/src/StockMarket/OrderProcessing.ts index adfe0b967..bd3d8a628 100644 --- a/src/StockMarket/OrderProcessing.ts +++ b/src/StockMarket/OrderProcessing.ts @@ -22,7 +22,7 @@ import { numeralWrapper } from "../ui/numeralFormat"; import { dialogBoxCreate } from "../../utils/DialogBox"; -interface IProcessOrderRefs { +export interface IProcessOrderRefs { rerenderFn: () => void; stockMarket: IStockMarket; symbolToStockMap: IMap; @@ -49,7 +49,7 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi } let stockOrders = orderBook[stock.symbol]; if (stockOrders == null || !(stockOrders.constructor === Array)) { - console.error(`Invalid Order book for ${stock.symbol} in processOrders()`); + console.error(`Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`); stockOrders = []; return; } @@ -160,9 +160,6 @@ function executeOrder(order: Order, refs: IProcessOrderRefs) { if (isBuy) { dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` + `This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`); - } else { - dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` + - `This may be a bug, please report to game developer with details.`); } } } diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts index f73006f2a..9d810e0ef 100644 --- a/src/StockMarket/PlayerInfluencing.ts +++ b/src/StockMarket/PlayerInfluencing.ts @@ -9,10 +9,10 @@ import { Company } from "../Company/Company"; import { Server } from "../Server/Server"; // Change in second-order forecast due to hacks/grows -const forecastForecastChangeFromHack = 0.1; +export const forecastForecastChangeFromHack = 0.1; // Change in second-order forecast due to company work -const forecastForecastChangeFromCompanyWork = 0.001; +export const forecastForecastChangeFromCompanyWork = 0.001; /** * Potentially decreases a stock's second-order forecast when its corresponding diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index c8fa589ed..e08cf440d 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -33,7 +33,7 @@ import * as ReactDOM from "react-dom"; export let StockMarket: IStockMarket | IMap = {}; // Maps full stock name -> Stock object export let SymbolToStockMap: IMap = {}; // Maps symbol -> Stock object -export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null) { +export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null): boolean { const tixApi = (workerScript instanceof WorkerScript); if (!(stock instanceof Stock)) { if (tixApi) { @@ -85,7 +85,7 @@ interface ICancelOrderParams { stock?: Stock; type?: OrderTypes; } -export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null) { +export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null): boolean { var tixApi = (workerScript instanceof WorkerScript); if (StockMarket["Orders"] == null) {return false;} if (params.order && params.order instanceof Order) { @@ -263,7 +263,11 @@ export function processStockPrices(numCycles=1) { let otlkMagChange = stock.otlkMag * av; if (stock.otlkMag < 5) { - otlkMagChange *= 10; + if (stock.otlkMag <= 1) { + otlkMagChange = 1; + } else { + otlkMagChange *= 10; + } } stock.cycleForecast(otlkMagChange); stock.cycleForecastForecast(otlkMagChange / 2); diff --git a/src/StockMarket/StockMarketConstants.ts b/src/StockMarket/StockMarketConstants.ts index 789d17e82..9124e8e01 100644 --- a/src/StockMarket/StockMarketConstants.ts +++ b/src/StockMarket/StockMarketConstants.ts @@ -2,4 +2,4 @@ * How many stock market 'ticks' before a 'cycle' is triggered. * A 'tick' is whenver stock prices update */ -export const TicksPerCycle = 70; +export const TicksPerCycle = 75; diff --git a/test/StockMarketTests.js b/test/StockMarketTests.ts similarity index 73% rename from test/StockMarketTests.js rename to test/StockMarketTests.ts index fdf7f07ac..1034b754b 100644 --- a/test/StockMarketTests.js +++ b/test/StockMarketTests.ts @@ -1,19 +1,33 @@ import { CONSTANTS } from "../src/Constants"; import { Player } from "../src/Player"; +import { IMap } from "../src/types"; + +import { Company } from "../src/Company/Company"; +import { Server } from "../src/Server/Server"; + import { buyStock, sellStock, shortStock, sellShort, } from "../src/StockMarket/BuyingAndSelling"; +import { IStockMarket } from "../src/StockMarket/IStockMarket"; import { Order } from "../src/StockMarket/Order"; -import { processOrders } from "../src/StockMarket/OrderProcessing"; +import { + forecastForecastChangeFromCompanyWork, + forecastForecastChangeFromHack, + influenceStockThroughCompanyWork, + influenceStockThroughServerGrow, + influenceStockThroughServerHack, +} from "../src/StockMarket/PlayerInfluencing"; +import { processOrders, IProcessOrderRefs } from "../src/StockMarket/OrderProcessing"; import { Stock , StockForecastInfluenceLimit } from "../src/StockMarket/Stock"; import { + cancelOrder, deleteStockMarket, initStockMarket, initSymbolToStockMap, - loadStockMarket, + placeOrder, processStockPrices, StockMarket, SymbolToStockMap, @@ -35,7 +49,7 @@ describe("Stock Market Tests", function() { const commission = CONSTANTS.StockMarketCommission; // Generic Stock object that can be used by each test - let stock; + let stock: Stock; const ctorParams = { b: true, initPrice: 10e3, @@ -59,13 +73,13 @@ describe("Stock Market Tests", function() { describe("Stock Class", function() { describe("constructor", function() { it("should have default parameters", function() { - let defaultStock; + let defaultStock: Stock; function construct() { defaultStock = new Stock(); } expect(construct).to.not.throw(); - expect(defaultStock.name).to.equal(""); + expect(defaultStock!.name).to.equal(""); }); it("should properly initialize props from parameters", function() { @@ -85,7 +99,7 @@ describe("Stock Market Tests", function() { }); it ("should properly initialize props from range-values", function() { - let stock; + let stock: Stock; const params = { b: true, initPrice: { @@ -117,10 +131,10 @@ describe("Stock Market Tests", function() { } expect(construct).to.not.throw(); - expect(stock.price).to.be.within(params.initPrice.min, params.initPrice.max); - expect(stock.mv).to.be.within(params.mv.min / params.mv.divisor, params.mv.max / params.mv.divisor); - expect(stock.spreadPerc).to.be.within(params.spreadPerc.min / params.spreadPerc.divisor, params.spreadPerc.max / params.spreadPerc.divisor); - expect(stock.shareTxForMovement).to.be.within(params.shareTxForMovement.min, params.shareTxForMovement.max); + expect(stock!.price).to.be.within(params.initPrice.min, params.initPrice.max); + expect(stock!.mv).to.be.within(params.mv.min / params.mv.divisor, params.mv.max / params.mv.divisor); + expect(stock!.spreadPerc).to.be.within(params.spreadPerc.min / params.spreadPerc.divisor, params.spreadPerc.max / params.spreadPerc.divisor); + expect(stock!.shareTxForMovement).to.be.within(params.shareTxForMovement.min, params.shareTxForMovement.max); }); it("should round the 'totalShare' prop to the nearest 100k", function() { @@ -365,7 +379,7 @@ describe("Stock Market Tests", function() { describe("StockMarket object", function() { describe("Initialization", function() { // Keeps track of initialized stocks. Contains their symbols - const stocks = []; + const stocks: string[]= []; before(function() { expect(initStockMarket).to.not.throw(); @@ -430,7 +444,7 @@ describe("Stock Market Tests", function() { it("should trigger a price update when it has enough cycles", function() { // Get the initial prices - const initialValues = {}; + const initialValues: IMap = {}; for (const stockName in StockMarket) { const stock = StockMarket[stockName]; if (!(stock instanceof Stock)) { continue; } @@ -451,7 +465,7 @@ describe("Stock Market Tests", function() { if (!(stock instanceof Stock)) { continue; } expect(initialValues[stock.symbol].price).to.not.equal(stock.price); // expect(initialValues[stock.symbol].otlkMag).to.not.equal(stock.otlkMag); - expect(initialValues[stock.symbol]).to.satisfy(function(initValue) { + expect(initialValues[stock.symbol]).to.satisfy(function(initValue: any) { if ((initValue.otlkMag !== stock.otlkMag) || (initValue.b !== stock.b)) { return true; } else { @@ -483,7 +497,7 @@ describe("Stock Market Tests", function() { describe("Transaction Cost Calculator Functions", function() { describe("getBuyTransactionCost()", function() { it("should fail on invalid 'stock' argument", function() { - const res = getBuyTransactionCost({}, 10, PositionTypes.Long); + const res = getBuyTransactionCost({} as Stock, 10, PositionTypes.Long); expect(res).to.equal(null); }); @@ -516,7 +530,7 @@ describe("Stock Market Tests", function() { describe("getSellTransactionGain()", function() { it("should fail on invalid 'stock' argument", function() { - const res = getSellTransactionGain({}, 10, PositionTypes.Long); + const res = getSellTransactionGain({} as Stock, 10, PositionTypes.Long); expect(res).to.equal(null); }); @@ -555,11 +569,11 @@ describe("Stock Market Tests", function() { describe("Forecast Movement Processor Function", function() { // N = 1 is the original forecast - function getNthForecast(origForecast, n) { + function getNthForecast(origForecast: number, n: number) { return origForecast - forecastChangePerPriceMovement * (n - 1); } - function getNthForecastForecast(origForecastForecast, n) { + function getNthForecastForecast(origForecastForecast: number, n: number) { if (stock.otlkMagForecast > 50) { const expected = origForecastForecast - (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100)); return expected < 50 ? 50 : expected; @@ -576,24 +590,24 @@ describe("Stock Market Tests", function() { it("should do nothing on invalid 'stock' argument", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement({}, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement({} as Stock, mvmtShares); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should do nothing on invalid 'shares' arg", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement(stock, NaN, PositionTypes.Long); + processTransactionForecastMovement(stock, NaN); expect(stock.shareTxUntilMovement).to.equal(oldTracker); - processTransactionForecastMovement(stock, -1, PositionTypes.Long); + processTransactionForecastMovement(stock, -1); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should properly evaluate a LONG transaction that doesn't trigger a forecast movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -601,7 +615,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate a SHORT transaction that doesn't trigger a forecast movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -610,7 +624,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); @@ -620,7 +634,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); @@ -630,7 +644,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -640,9 +654,9 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -652,7 +666,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -662,7 +676,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -672,9 +686,9 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -684,7 +698,7 @@ describe("Stock Market Tests", function() { const oldForecast = stock.otlkMag; const oldForecastForecast = stock.otlkMagForecast; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.otlkMagForecast).to.equal(getNthForecastForecast(oldForecastForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); @@ -698,24 +712,24 @@ describe("Stock Market Tests", function() { it("should do nothing on invalid 'stock' argument", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement({}, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement({} as Stock, mvmtShares); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should do nothing on invalid 'shares' arg", function() { const oldTracker = stock.shareTxUntilMovement; - processTransactionForecastMovement(stock, NaN, PositionTypes.Long); + processTransactionForecastMovement(stock, NaN); expect(stock.shareTxUntilMovement).to.equal(oldTracker); - processTransactionForecastMovement(stock, -1, PositionTypes.Long); + processTransactionForecastMovement(stock, -1); expect(stock.shareTxUntilMovement).to.equal(oldTracker); }); it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -723,7 +737,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, noMvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, noMvmtShares); expect(stock.otlkMag).to.equal(oldForecast); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -731,7 +745,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that trigger price movements", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Long); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -739,7 +753,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions that trigger price movements", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, mvmtShares, PositionTypes.Short); + processTransactionForecastMovement(stock, mvmtShares); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement - noMvmtShares); }); @@ -747,7 +761,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -755,9 +769,9 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Long); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -765,7 +779,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Long); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -773,7 +787,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -781,9 +795,9 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2), PositionTypes.Short); + processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); expect(stock.shareTxUntilMovement).to.be.below(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 2)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -791,7 +805,7 @@ describe("Stock Market Tests", function() { it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() { const oldForecast = stock.otlkMag; - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement, PositionTypes.Short); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); expect(stock.otlkMag).to.equal(getNthForecast(oldForecast, 4)); expect(stock.shareTxUntilMovement).to.equal(stock.shareTxForMovement); }); @@ -803,7 +817,7 @@ describe("Stock Market Tests", function() { describe("buyStock()", function() { it("should fail for invalid arguments", function() { - expect(buyStock({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(buyStock({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(buyStock(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(buyStock(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(buyStock(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -822,7 +836,11 @@ describe("Stock Market Tests", function() { it("should return true and properly update stock properties for successful transactions", function() { const shares = 1e3; const cost = getBuyTransactionCost(stock, shares, PositionTypes.Long); - Player.setMoney(cost); + if (cost == null) { + expect.fail(); + } + + Player.setMoney(cost!); expect(buyStock(stock, shares, null, suppressDialogOpt)).to.equal(true); expect(stock.playerShares).to.equal(shares); @@ -833,7 +851,7 @@ describe("Stock Market Tests", function() { describe("sellStock()", function() { it("should fail for invalid arguments", function() { - expect(sellStock({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(sellStock({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(sellStock(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(sellStock(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(sellStock(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -893,7 +911,7 @@ describe("Stock Market Tests", function() { describe("shortStock()", function() { it("should fail for invalid arguments", function() { - expect(shortStock({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(shortStock({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(shortStock(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(shortStock(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(shortStock(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -912,7 +930,11 @@ describe("Stock Market Tests", function() { it("should return true and properly update stock properties for successful transactions", function() { const shares = 1e3; const cost = getBuyTransactionCost(stock, shares, PositionTypes.Short); - Player.setMoney(cost); + if (cost == null) { + expect.fail(); + } + + Player.setMoney(cost!); expect(shortStock(stock, shares, null, suppressDialogOpt)).to.equal(true); expect(stock.playerShortShares).to.equal(shares); @@ -923,7 +945,7 @@ describe("Stock Market Tests", function() { describe("sellShort()", function() { it("should fail for invalid arguments", function() { - expect(sellShort({}, 1, null, suppressDialogOpt)).to.equal(false); + expect(sellShort({} as Stock, 1, null, suppressDialogOpt)).to.equal(false); expect(sellShort(stock, 0, null, suppressDialogOpt)).to.equal(false); expect(sellShort(stock, -1, null, suppressDialogOpt)).to.equal(false); expect(sellShort(stock, NaN, null, suppressDialogOpt)).to.equal(false); @@ -985,13 +1007,13 @@ describe("Stock Market Tests", function() { describe("Order Class", function() { it("should throw on invalid arguments", function() { function invalid1() { - return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); + return new Order({} as string, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); } function invalid2() { - return new Order("FOO", "z", 0, OrderTypes.LimitBuy, PositionTypes.Short); + return new Order("FOO", "z" as any as number, 0, OrderTypes.LimitBuy, PositionTypes.Short); } function invalid3() { - return new Order("FOO", 1, {}, OrderTypes.LimitBuy, PositionTypes.Short); + return new Order("FOO", 1, {} as number, OrderTypes.LimitBuy, PositionTypes.Short); } function invalid4() { return new Order("FOO", 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short); @@ -1008,19 +1030,281 @@ describe("Stock Market Tests", function() { }); }); - describe("Order Processing", function() { - before(function() { + describe("Order Placing & Processing", function() { + beforeEach(function() { expect(initStockMarket).to.not.throw(); expect(initSymbolToStockMap).to.not.throw(); - }); - - describe() - describe("executeOrder()", function() { + // Create an order book for our mock stock + StockMarket["Orders"][stock.symbol] = []; + }); + + describe("placeOrder()", function() { + it("should return false when it's called with invalid arguments", function() { + const invalid1 = placeOrder({} as Stock, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); + const invalid2 = placeOrder(stock, "foo" as any as number, 2, OrderTypes.LimitBuy, PositionTypes.Long); + const invalid3 = placeOrder(stock, 1, "foo" as any as number, OrderTypes.LimitBuy, PositionTypes.Long); + + expect(invalid1).to.equal(false); + expect(invalid2).to.equal(false); + expect(invalid3).to.equal(false); + + expect(StockMarket["Orders"][stock.symbol]).to.be.empty; + }); + + it("should return true and update the order book for valid arguments", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + expect(res).to.equal(true); + + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + const order = StockMarket["Orders"][stock.symbol][0]; + expect(order).to.be.an.instanceof(Order); + expect(order.stockSymbol).to.equal(ctorParams.symbol); + expect(order.shares).to.equal(1e3); + expect(order.price).to.equal(9e3); + expect(order.type).to.equal(OrderTypes.LimitBuy); + expect(order.pos).to.equal(PositionTypes.Long); + }); + }); + + describe("cancelOrder()", function() { + beforeEach(function() { + StockMarket["Orders"][stock.symbol] = []; + + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + expect(res).to.equal(true); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + }); + + it("returns true & removes an Order from the order book", function() { + const order = StockMarket["Orders"][stock.symbol][0]; + const res = cancelOrder({ order }); + expect(res).to.equal(true); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(0); + }); + + it("should also work when passing in order parameters separately", function() { + const res = cancelOrder({ + stock, + shares: 1e3, + price: 9e3, + type: OrderTypes.LimitBuy, + pos: PositionTypes.Long + }); + expect(res).to.equal(true); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(0); + }); + + it("should return false and do nothing when the specified order doesn't exist", function() { + // Same parameters, but its a different object + const order = new Order(stock.symbol, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + const res = cancelOrder({ order }); + expect(res).to.equal(false); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + + const res2 = cancelOrder({ + stock, + shares: 999, + price: 9e3, + type: OrderTypes.LimitBuy, + pos: PositionTypes.Long + }); + expect(res2).to.equal(false); + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + }); + }); + + describe("processOrders()", function() { + let processOrdersRefs: IProcessOrderRefs; + + beforeEach(function() { + expect(initStockMarket).to.not.throw(); + expect(initSymbolToStockMap).to.not.throw(); + + StockMarket[stock.name] = stock; + SymbolToStockMap[stock.symbol] = stock; + StockMarket["Orders"][stock.symbol] = []; + + stock.playerShares = 1e3; + stock.playerShortShares = 1e3; + Player.setMoney(100e9); + + processOrdersRefs = { + rerenderFn: () => {}, + stockMarket: StockMarket as IStockMarket, + symbolToStockMap: SymbolToStockMap, + }; + }); + + function checkThatOrderExists(placeOrderRes?: boolean) { + if (typeof placeOrderRes === "boolean") { + expect(placeOrderRes).to.equal(true); + } + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(1); + } + + function checkThatOrderExecuted() { + expect(StockMarket["Orders"][stock.symbol]).to.have.lengthOf(0); + } + + it("should execute LONG Limit Buy orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(2e3); + }); + + it("should execute SHORT Limit Buy Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(2e3); + }); + + it("should execute LONG Limit Sell Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.LimitSell, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(0); + }); + + it("should execute SHORT Limit Sell Orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitSell, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(0); + }); + + it("should execute LONG Stop Buy Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.StopBuy, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(2e3); + }); + + it("should execute SHORT Stop Buy Orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.StopBuy, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(2e3); + }); + + it("should execute LONG Stop Sell Orders when price <= order price", function() { + const res = placeOrder(stock, 1e3, 9e3, OrderTypes.StopSell, PositionTypes.Long); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(0); + }); + + it("should execute SHORT Stop Sell Orders when price >= order price", function() { + const res = placeOrder(stock, 1e3, 11e3, OrderTypes.StopSell, PositionTypes.Short); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrdersRefs); + checkThatOrderExecuted(); + expect(stock.playerShortShares).to.equal(0); + }); + + it("should execute immediately if their conditions are satisfied", function() { + placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Long); + checkThatOrderExecuted(); + expect(stock.playerShares).to.equal(2e3); + }); }); }); + // TODO describe("Player Influencing", function() { - // TODO + const server = new Server({ + hostname: "mockserver", + moneyAvailable: 1e6, + organizationName: "MockStock", + }); + + const company = new Company({ + name: "MockStock", + info: "", + companyPositions: {}, + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 1, + }) + + beforeEach(function() { + expect(initStockMarket).to.not.throw(); + expect(initSymbolToStockMap).to.not.throw(); + + StockMarket[stock.name] = stock; + }); + + describe("influenceStockThroughServerHack()", function() { + it("should decrease a stock's second-order forecast when all of its money is hacked", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerHack(server, server.moneyMax); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast - forecastForecastChangeFromHack); + }); + + it("should not decrease the stock's second-order forecast when no money is stolen", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerHack(server, 0); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast); + }); + }); + + describe("influenceStockThroughServerGrow()", function() { + it("should increase a stock's second-order forecast when all of its money is grown", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerGrow(server, server.moneyMax); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + forecastForecastChangeFromHack); + }); + + it("should not increase the stock's second-order forecast when no money is grown", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerGrow(server, 0); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast); + }); + }); + + describe("influenceStockThroughCompanyWork()", function() { + it("should increase the server's second order forecast", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + + // Use 1e3 for numCycles to force a change + // (This may break later if numbers are rebalanced); + influenceStockThroughCompanyWork(company, 1, 1e3); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + forecastForecastChangeFromCompanyWork); + }); + + it("should be affected by performanceMult", function() { + const oldSecondOrderForecast = stock.otlkMagForecast; + + // Use 1e3 for numCycles to force a change + // (This may break later if numbers are rebalanced); + influenceStockThroughCompanyWork(company, 4, 1e3); + expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + 4 * forecastForecastChangeFromCompanyWork); + }); + }); }); }); From 931de230aeb9e1c19f2544190cc1613f4189bf50 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Tue, 11 Jun 2019 00:18:14 -0700 Subject: [PATCH 33/39] Minor rebalancing to stock market. Updated documentation and tests for recent changes --- doc/source/basicgameplay/stockmarket.rst | 56 +++++++++++++------- src/StockMarket/PlayerInfluencing.ts | 2 +- src/StockMarket/StockMarket.tsx | 4 +- src/StockMarket/ui/StockTickerHeaderText.tsx | 4 +- test/StockMarketTests.ts | 2 +- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/doc/source/basicgameplay/stockmarket.rst b/doc/source/basicgameplay/stockmarket.rst index 7e44d6c22..05df76d04 100644 --- a/doc/source/basicgameplay/stockmarket.rst +++ b/doc/source/basicgameplay/stockmarket.rst @@ -28,6 +28,27 @@ a stock. .. note:: Shorting stocks is not available immediately, and must be unlocked later in the game. +Forecast & Second-Order Forecast +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +A stock's forecast is its likelihood of increasing or decreasing in value. The +forecast is typically represented by its probability of increasing in either +a decimal or percentage form. For example, a forecast of 70% means the stock +has a 70% chance of increasing and a 30% chance of decreasing. + +A stock's second-order forecast is the target value that its forecast trends towards. +For example, if a stock has a forecast of 60% and a second-order forecast of 70%, +then the stock's forecast should slowly trend towards 70% over time. However, this is +determined by RNG so there is a chance that it may never reach 70%. + +Both the forecast and the second-order forecast change over time. + +A stock's forecast can be viewed after purchasing Four Sigma (4S) Market Data +access. This lets you see the forecast info on the Stock Market UI. If you also +purchase access to the 4S Market Data TIX API, then you can view a stock's forecast +using the :js:func:`getStockForecast` function. + +A stock's second-order forecast is always hidden. + .. _gameplay_stock_market_spread: Spread (Bid Price & Ask Price) @@ -53,11 +74,14 @@ in the short position will occur at the stock's ask price. Transactions Influencing Stock Forecast ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In addition to influencing stock price, buying or selling a large number of shares -of a stock will also influence that stock's "forecast". The forecast is the likelihood -that the stock will increase or decrease in price. The magnitude of this effect depends -on the number of shares being transacted. More shares will have a bigger effect on the -stock forecast. +Buying or selling a large number of shares +of a stock will influence that stock's forecast & second-order forecast. +The forecast is the likelihood that the stock will increase or decrease in price. +The magnitude of this effect depends on the number of shares being transacted. +More shares will have a bigger effect. + +The effect that transactions have on a stock's second-order forecast is +significantly smaller than the effect on its forecast. .. _gameplay_stock_market_order_types: @@ -109,16 +133,6 @@ A Stop Order to buy will execute if the stock's price <= order's price A Stop Order to sell will execute if the stock's price >= order's price. -.. note:: Stop Orders do **not** take into account the fact that transactions can - influence a stock's price. Limit Orders, however, do take this into account. - - For example, assume you have a Limit Order set to purchase a stock at - $5. Then, the stock's price drops to $5 and your Limit Order executes. - However, the transaction causes the stock's price to increase before - the order finishes executing all of the shares. Your Limit Order will - stop executing, and will continue only when the stock's price drops back to - $5 or below. - .. _gameplay_stock_market_player_actions_influencing_stock: Player Actions Influencing Stocks @@ -127,8 +141,8 @@ It is possible for your actions elsewhere in the game to influence the stock mar Hacking If a server has a corresponding stock (e.g. *foodnstuff* server -> FoodNStuff - stock), then hacking that server can - cause the corresponding stock's forecast to trend downwards in value + stock), then hacking that server can decrease the stock's second-order + forecast. This causes the corresponding stock's forecast to trend downwards in value over time. This effect only occurs if you set the *stock* option to @@ -142,7 +156,8 @@ Hacking Growing If a server has a corresponding stock (e.g. *foodnstuff* server -> FoodNStuff - stock), then growing that server's money can cause the corresponding stock's + stock), then growing that server's money can increase the stock's + second-order forecast. This causes the corresponding stock's forecast to trend upwards in value over time. This effect only occurs if you set the *stock* option to true when calling the @@ -155,11 +170,12 @@ Growing Working for a Company If a company has a corresponding stock, then working for that company will - cause the corresponding stock's forecast to (slowly) trend upwards in value + increase the corresponding stock's second-order forecast. This will + cause the stock's forecast to (slowly) trend upwards in value over time. The potency of this effect is based on how "effective" you are when you work - (i.e. its based on your stats). + (i.e. its based on your stats and multipliers). Automating the Stock Market --------------------------- diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts index 9d810e0ef..f3c9e49ff 100644 --- a/src/StockMarket/PlayerInfluencing.ts +++ b/src/StockMarket/PlayerInfluencing.ts @@ -71,7 +71,7 @@ export function influenceStockThroughCompanyWork(company: Company, performanceMu } if (!(stock instanceof Stock)) { return; } - if (Math.random() < 0.001 * cyclesOfWork) { + if (Math.random() < 0.002 * cyclesOfWork) { const change = forecastForecastChangeFromCompanyWork * performanceMult; stock.changeForecastForecast(stock.otlkMagForecast + change); } diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index e08cf440d..30d2b8b1b 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -185,9 +185,7 @@ export function stockMarketCycle() { if (!(stock instanceof Stock)) { continue; } const roll = Math.random(); - if (roll < 0.1) { - stock.flipForecastForecast(); - } else if (roll < 0.55) { + if (roll < 0.45) { stock.b = !stock.b; stock.flipForecastForecast(); } diff --git a/src/StockMarket/ui/StockTickerHeaderText.tsx b/src/StockMarket/ui/StockTickerHeaderText.tsx index 3227f17aa..387e0b96d 100644 --- a/src/StockMarket/ui/StockTickerHeaderText.tsx +++ b/src/StockMarket/ui/StockTickerHeaderText.tsx @@ -27,7 +27,9 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement { let plusOrMinus = stock.b; // True for "+", false for "-" if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus } hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1); - hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; + + // Debugging: + // hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; } let styleMarkup = { diff --git a/test/StockMarketTests.ts b/test/StockMarketTests.ts index 1034b754b..bd933478d 100644 --- a/test/StockMarketTests.ts +++ b/test/StockMarketTests.ts @@ -1293,7 +1293,7 @@ describe("Stock Market Tests", function() { // Use 1e3 for numCycles to force a change // (This may break later if numbers are rebalanced); - influenceStockThroughCompanyWork(company, 1, 1e3); + influenceStockThroughCompanyWork(company, 1, 500); expect(stock.otlkMagForecast).to.equal(oldSecondOrderForecast + forecastForecastChangeFromCompanyWork); }); From 821725cf4df65c00c36892e6a0ce1f5c5d51a910 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 19 Jun 2019 01:03:08 -0700 Subject: [PATCH 34/39] Initial commit for converting workerScripts pool to Map data structure --- src/Netscript/WorkerScript.ts | 15 ++++++++++- src/Netscript/WorkerScripts.ts | 2 +- src/NetscriptWorker.js | 47 +++++++++++++++++++++++++++++++++- src/Script/RunningScript.ts | 3 +++ 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts index 6b99c43d5..726af7c84 100644 --- a/src/Netscript/WorkerScript.ts +++ b/src/Netscript/WorkerScript.ts @@ -80,6 +80,12 @@ export class WorkerScript { */ output: string = ""; + /** + * Process ID. Must be an integer. Used for efficient script + * killing and removal. + */ + pid: number; + /** * Script's Static RAM usage. Equivalent to underlying script's RAM usage */ @@ -100,10 +106,17 @@ export class WorkerScript { */ serverIp: string; - constructor(runningScriptObj: RunningScript, nsFuncsGenerator?: (ws: WorkerScript) => object) { + constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => object) { this.name = runningScriptObj.filename; this.serverIp = runningScriptObj.server; + const sanitizedPid = Math.round(pid); + if (typeof sanitizedPid !== "number" || isNaN(sanitizedPid)) { + throw new Error(`Invalid PID when constructing WorkerScript: ${pid}`); + } + this.pid = sanitizedPid; + runningScriptObj.pid = sanitizedPid; + // Get the underlying script's code const server = AllServers[this.serverIp]; if (server == null) { diff --git a/src/Netscript/WorkerScripts.ts b/src/Netscript/WorkerScripts.ts index 50515f413..58c184314 100644 --- a/src/Netscript/WorkerScripts.ts +++ b/src/Netscript/WorkerScripts.ts @@ -3,4 +3,4 @@ */ import { WorkerScript } from "./WorkerScript"; -export const workerScripts: WorkerScript[] = []; +export const workerScripts: Map = new Map(); diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 095ba401d..cb8a355ae 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -407,6 +407,42 @@ function processNetscript1Imports(code, workerScript) { return res; } +/** + * + */ +let pidCounter = 1; +function generateNextPid() { + let tempCounter = pidCounter; + + // Cap the number of search iterations at some arbitrary value to avoid + // infinite loops. We'll assume that players wont have 1mil+ running scripts + let found = false; + for (let i = 0; i < 1e6;) { + if (!workerScripts.has(tempCounter + i)) { + found = true; + tempCounter = tempCounter + i; + break; + } + + if (i === Number.MAX_SAFE_INTEGER - 1) { + i = 1; + } else { + ++i; + } + } + + if (found) { + pidCounter = tempCounter + 1; + if (pidCounter >= Number.MAX_SAFE_INTEGER) { + pidCounter = 1; + } + + return tempCounter; + } else { + return -1; + } +} + /** * Start a script * @@ -438,8 +474,17 @@ export function addWorkerScript(runningScriptObj, server) { } server.ramUsed = roundToTwo(server.ramUsed + ramUsage); + // Get the pid + const pid = getNextPid(); + if (pid === -1) { + throw new Error( + `Failed to start script because could not find available PID. This is most ` + + `because you have too many scripts running.` + ); + } + // Create the WorkerScript - const s = new WorkerScript(runningScriptObj, NetscriptFunctions); + const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); s.ramUsage = ramUsage; // Start the script's execution diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index 59bde86b2..f3f9b617c 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -56,6 +56,9 @@ export class RunningScript { // Number of seconds that this script has been running online onlineRunningTime: number = 0.01; + // Process ID. Must be an integer and equals the PID of corresponding WorkerScript + pid: number = -1; + // How much RAM this script uses for ONE thread ramUsage: number = 0; From 4cc6437408c939c8e45b126b86004de2ca9c6d6e Mon Sep 17 00:00:00 2001 From: danielyxie Date: Wed, 19 Jun 2019 01:51:25 -0700 Subject: [PATCH 35/39] Updated WorkerScript-related code for the workerScripts array->map change --- src/Netscript/killWorkerScript.ts | 121 +++++++++++----------- src/NetscriptFunctions.js | 6 +- src/NetscriptWorker.js | 22 ++-- src/ui/ActiveScripts/Root.tsx | 2 +- src/ui/ActiveScripts/ScriptProduction.tsx | 6 +- src/ui/ActiveScripts/ServerAccordions.tsx | 4 +- 6 files changed, 83 insertions(+), 78 deletions(-) diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index 59abd4e93..d97775842 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -14,26 +14,31 @@ 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 { +export function killWorkerScript(pid: number): boolean; +export function killWorkerScript(script: RunningScript | WorkerScript | number, serverIp?: string): boolean { if (script instanceof WorkerScript) { - script.env.stopFlag = true; - killNetscriptDelay(script); - removeWorkerScript(script); + stopAndCleanUpWorkerScript(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]); + // Try to kill by PID + const res = killWorkerScriptByPid(script.pid); + if (res) { return res; } + + // If for some reason that doesn't work, we'll try the old way + for (const ws of workerScripts.values()) { + if (ws.name == script.filename && ws.serverIp == serverIp && + compareArrays(ws.args, script.args)) { + + stopAndCleanUpWorkerScript(ws); return true; } - } + } return false; + } else if (typeof script === "number") { + return killWorkerScriptByPid(script); } else { console.error(`killWorkerScript() called with invalid argument:`); console.error(script); @@ -41,6 +46,23 @@ export function killWorkerScript(script: RunningScript | WorkerScript, serverIp? } } +function stopAndCleanUpWorkerScript(workerScript: WorkerScript): void { + workerScript.env.stopFlag = true; + killNetscriptDelay(workerScript); + removeWorkerScript(workerScript); +} + +function killWorkerScriptByPid(pid: number): boolean { + const ws = workerScripts.get(pid); + if (ws instanceof WorkerScript) { + stopAndCleanUpWorkerScript(ws); + + return true; + } + + return false; +} + /** * Helper function that removes the script being killed from the global pool. * Also handles other cleanup-time operations @@ -48,66 +70,47 @@ export function killWorkerScript(script: RunningScript | WorkerScript, serverIp? * @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; +function removeWorkerScript(workerScript: WorkerScript): void { + if (workerScript instanceof WorkerScript) { + const ip = workerScript.serverIp; + const name = workerScript.name; - if (typeof id === "number") { - if (id < 0 || id >= workerScripts.length) { - console.error(`Too high of an index passed into removeWorkerScript(): ${id}`); + // 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; } - 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; + // 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; } } - if (index == null) { - console.error(`Could not find WorkerScript in global pool:`); - console.error(workerScript); + // Delete script from global pool (workerScripts) + const res = workerScripts.delete(workerScript.pid); + if (!res) { + console.warn(`removeWorkerScript() called with WorkerScript that wasn't in the global map:`); + console.warn(workerScript); } + + WorkerScriptStartStopEventEmitter.emitEvent(); } else { - console.error(`Invalid argument passed into removeWorkerScript(): ${id}`); + console.error(`Invalid argument passed into removeWorkerScript():`); + console.error(workerScript); 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); - WorkerScriptStartStopEventEmitter.emitEvent(); } /** diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index cb751f9ae..28bba6eb1 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -2178,7 +2178,7 @@ function NetscriptFunctions(workerScript) { // First element is total income of all currently running scripts let total = 0; - for (const script of workerScripts) { + for (const script of workerScripts.values()) { total += (script.scriptRef.onlineMoneyMade / script.scriptRef.onlineRunningTime); } res.push(total); @@ -2209,8 +2209,8 @@ function NetscriptFunctions(workerScript) { updateDynamicRam("getScriptExpGain", getRamCost("getScriptExpGain")); if (arguments.length === 0) { var total = 0; - for (var i = 0; i < workerScripts.length; ++i) { - total += (workerScripts[i].scriptRef.onlineExpGained / workerScripts[i].scriptRef.onlineRunningTime); + for (const ws of workerScripts.values()) { + total += (ws.scriptRef.onlineExpGained / ws.scriptRef.onlineRunningTime); } return total; } else { diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index cb8a355ae..0d69234d2 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -46,11 +46,12 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) { } export function prestigeWorkerScripts() { - for (var i = 0; i < workerScripts.length; ++i) { - // TODO Signal event emitter - workerScripts[i].env.stopFlag = true; + for (const ws of workerScripts.values()) { + ws.env.stopFlag = true; } - workerScripts.length = 0; + + WorkerScriptStartStopEventEmitter.emitEvent(); + workerScripts.clear(); } // JS script promises need a little massaging to have the same guarantees as netscript @@ -475,7 +476,7 @@ export function addWorkerScript(runningScriptObj, server) { server.ramUsed = roundToTwo(server.ramUsed + ramUsage); // Get the pid - const pid = getNextPid(); + const pid = generateNextPid(); if (pid === -1) { throw new Error( `Failed to start script because could not find available PID. This is most ` + @@ -483,7 +484,8 @@ export function addWorkerScript(runningScriptObj, server) { ); } - // Create the WorkerScript + // Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying + // RunningScript's PID as well const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); s.ramUsage = ramUsage; @@ -547,7 +549,7 @@ export function addWorkerScript(runningScriptObj, server) { }); // Add the WorkerScript to the global pool - workerScripts.push(s); + workerScripts.set(pid, s); WorkerScriptStartStopEventEmitter.emitEvent(); return; } @@ -557,9 +559,9 @@ export function addWorkerScript(runningScriptObj, server) { */ export function updateOnlineScriptTimes(numCycles = 1) { var time = (numCycles * Engine._idleSpeed) / 1000; //seconds - for (var i = 0; i < workerScripts.length; ++i) { - workerScripts[i].scriptRef.onlineRunningTime += time; - } + for (const ws of workerScripts.values()) { + ws.scriptRef.onlineRunningTime += time; + } } /** diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx index 2dd6e78d0..00ac8bbbb 100644 --- a/src/ui/ActiveScripts/Root.tsx +++ b/src/ui/ActiveScripts/Root.tsx @@ -12,7 +12,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; type IProps = { p: IPlayer; - workerScripts: WorkerScript[]; + workerScripts: Map; } export class ActiveScriptsRoot extends React.Component { diff --git a/src/ui/ActiveScripts/ScriptProduction.tsx b/src/ui/ActiveScripts/ScriptProduction.tsx index 66da02a01..faa6d0686 100644 --- a/src/ui/ActiveScripts/ScriptProduction.tsx +++ b/src/ui/ActiveScripts/ScriptProduction.tsx @@ -11,14 +11,14 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; type IProps = { p: IPlayer; - workerScripts: WorkerScript[]; + workerScripts: Map; } 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) { + for (const ws of props.workerScripts.values()) { onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime); } @@ -35,7 +35,7 @@ export function ScriptProduction(props: IProps): React.ReactElement { {numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)} - + ( {numeralWrapper.formatMoney(prodRateSinceLastAug)} diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx index 554d69317..acd279870 100644 --- a/src/ui/ActiveScripts/ServerAccordions.tsx +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -22,7 +22,7 @@ interface IServerToScriptsMap { } type IProps = { - workerScripts: WorkerScript[]; + workerScripts: Map; }; type IState = { @@ -61,7 +61,7 @@ export class ServerAccordions extends React.Component { updateServerToScriptsMap(): void { const map: IServerToScriptsMap = {}; - for (const ws of this.props.workerScripts) { + for (const ws of this.props.workerScripts.values()) { const server = getServer(ws.serverIp); if (server == null) { console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`); From 3a374de210948553df4197bd6a334da1134c268d Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sat, 22 Jun 2019 01:12:17 -0700 Subject: [PATCH 36/39] killWorkerScript() now takes an optional argument for whether to rerenderUI. This is used to batch UI updates on killall() --- src/Netscript/killWorkerScript.ts | 38 ++++++++++++++++++------------- src/NetscriptFunctions.js | 11 +++++---- utils/LogBox.ts | 2 +- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index d97775842..072bf5921 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -12,17 +12,21 @@ 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(runningScriptObj: RunningScript, serverIp: string, rerenderUi: boolean): boolean; export function killWorkerScript(workerScript: WorkerScript): boolean; export function killWorkerScript(pid: number): boolean; -export function killWorkerScript(script: RunningScript | WorkerScript | number, serverIp?: string): boolean { +export function killWorkerScript(script: RunningScript | WorkerScript | number, serverIp?: string, rerenderUi?: boolean): boolean { + if (rerenderUi == null || typeof rerenderUi !== "boolean") { + rerenderUi = true; + } + if (script instanceof WorkerScript) { stopAndCleanUpWorkerScript(script); return true; } else if (script instanceof RunningScript && typeof serverIp === "string") { // Try to kill by PID - const res = killWorkerScriptByPid(script.pid); + const res = killWorkerScriptByPid(script.pid, rerenderUi); if (res) { return res; } // If for some reason that doesn't work, we'll try the old way @@ -30,7 +34,7 @@ export function killWorkerScript(script: RunningScript | WorkerScript | number, if (ws.name == script.filename && ws.serverIp == serverIp && compareArrays(ws.args, script.args)) { - stopAndCleanUpWorkerScript(ws); + stopAndCleanUpWorkerScript(ws, rerenderUi); return true; } @@ -38,7 +42,7 @@ export function killWorkerScript(script: RunningScript | WorkerScript | number, return false; } else if (typeof script === "number") { - return killWorkerScriptByPid(script); + return killWorkerScriptByPid(script, rerenderUi); } else { console.error(`killWorkerScript() called with invalid argument:`); console.error(script); @@ -46,23 +50,23 @@ export function killWorkerScript(script: RunningScript | WorkerScript | number, } } -function stopAndCleanUpWorkerScript(workerScript: WorkerScript): void { - workerScript.env.stopFlag = true; - killNetscriptDelay(workerScript); - removeWorkerScript(workerScript); -} - -function killWorkerScriptByPid(pid: number): boolean { +function killWorkerScriptByPid(pid: number, rerenderUi: boolean=true): boolean { const ws = workerScripts.get(pid); if (ws instanceof WorkerScript) { - stopAndCleanUpWorkerScript(ws); - + stopAndCleanUpWorkerScript(ws, rerenderUi); + return true; } return false; } +function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true): void { + workerScript.env.stopFlag = true; + killNetscriptDelay(workerScript); + removeWorkerScript(workerScript, rerenderUi); +} + /** * Helper function that removes the script being killed from the global pool. * Also handles other cleanup-time operations @@ -70,7 +74,7 @@ function killWorkerScriptByPid(pid: number): boolean { * @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or * its index in the global workerScripts array */ -function removeWorkerScript(workerScript: WorkerScript): void { +function removeWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true): void { if (workerScript instanceof WorkerScript) { const ip = workerScript.serverIp; const name = workerScript.name; @@ -105,7 +109,9 @@ function removeWorkerScript(workerScript: WorkerScript): void { console.warn(workerScript); } - WorkerScriptStartStopEventEmitter.emitEvent(); + if (rerenderUi) { + WorkerScriptStartStopEventEmitter.emitEvent(); + } } else { console.error(`Invalid argument passed into removeWorkerScript():`); console.error(workerScript); diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 28bba6eb1..6d1b1ed0d 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -2,6 +2,7 @@ const sprintf = require("sprintf-js").sprintf; const vsprintf = require("sprintf-js").vsprintf; import { getRamCost } from "./Netscript/RamCostGenerator"; +import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { Augmentation } from "./Augmentation/Augmentation"; import { Augmentations } from "./Augmentation/Augmentations"; @@ -983,18 +984,20 @@ function NetscriptFunctions(workerScript) { if (ip === undefined) { throw makeRuntimeRejectMsg(workerScript, "killall() call has incorrect number of arguments. Takes 1 argument"); } - var server = getServer(ip); + const server = getServer(ip); if (server == null) { workerScript.scriptRef.log("killall() failed. Invalid IP or hostname passed in: " + ip); throw makeRuntimeRejectMsg(workerScript, "killall() failed. Invalid IP or hostname passed in: " + ip); } - var scriptsRunning = (server.runningScripts.length > 0); - for (var i = server.runningScripts.length-1; i >= 0; --i) { - killWorkerScript(server.runningScripts[i], server.ip); + const scriptsRunning = (server.runningScripts.length > 0); + for (let i = server.runningScripts.length-1; i >= 0; --i) { + killWorkerScript(server.runningScripts[i], server.ip, false); } + WorkerScriptStartStopEventEmitter.emitEvent(); if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.killall == null) { workerScript.scriptRef.log("killall(): Killing all scripts on " + server.hostname + ". May take a few minutes for the scripts to die"); } + return scriptsRunning; }, exit : function() { diff --git a/utils/LogBox.ts b/utils/LogBox.ts index 1a38244c6..10633e035 100644 --- a/utils/LogBox.ts +++ b/utils/LogBox.ts @@ -74,7 +74,7 @@ export function logBoxCreate(script: RunningScript) { } killScriptBtn.addEventListener("click", () => { - killWorkerScript(script, script.server); + killWorkerScript(script, script.server, true); return false; }); From e3a74f23a144fd0fd68a6bceb0998c8fb4ceb775 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Thu, 27 Jun 2019 00:01:06 -0700 Subject: [PATCH 37/39] ps and top Terminal commands now show script pid. Updated version and changelog --- src/Constants.ts | 46 ++----------------------------------- src/NetscriptWorker.js | 2 +- src/Terminal.js | 51 +++++++++++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/Constants.ts b/src/Constants.ts index babe87666..305101fcd 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -6,7 +6,7 @@ import { IMap } from "./types"; export let CONSTANTS: IMap = { - Version: "0.47.0", + Version: "0.47.1", /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then @@ -230,51 +230,9 @@ export let CONSTANTS: IMap = { * Scripts now start/stop instantly * Improved performance when starting up many copies of a new NetscriptJS script (by Ornedan) + * Improved performance when killing scripts * Dialog boxes can now be closed with the ESC key (by jaguilar) * NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar) * write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar) - - v0.47.0 - * Stock Market changes: - ** Implemented spread. Stock's now have bid and ask prices at which transactions occur - ** Large transactions will now influence a stock's price and forecast - ** This "influencing" can take effect in the middle of a transaction - ** See documentation for more details on these changes - ** Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API - ** Added getStockPurchaseCost(), getStockSaleGain() Netscript functions to the TIX API - - * Re-sleeves can no longer have the NeuroFlux Governor augmentation - ** This is just a temporary patch until the mechanic gets re-worked - - * hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD) - * codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward - * Adjusted RAM costs of Netscript Singularity functions (mostly increased) - * Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function - * Netscript Singularity functions no longer cost extra RAM outside of BitNode-4 - * Corporation employees no longer have an "age" stat - * Gang Wanted level gain rate capped at 100 (per employee) - * Script startup/kill is now processed every 3 seconds, instead of 6 seconds - * getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server - * Money/Income tracker now displays money lost from hospitalizations - * Exported saves now have a unique filename based on current BitNode and timestamp - * Maximum number of Hacknet Servers decreased from 25 to 20 - * Bug Fix: Corporation employees stats should no longer become negative - * Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios - * Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server - * Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang - * Bug Fix: getAugmentationsFromFaction() & purchaseAugmentation() functions should now work properly if you have a gang - * Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game - * Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes - * Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads - * Bug Fix: Faction UI should now automatically update reputation - * Bug Fix: Fixed purchase4SMarketData() - * Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from "script") - * Bug Fix: Terminal 'wget' command now correctly evaluates directory paths - * Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in - * Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions - * Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button - * Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy - * Bug Fix: Fixed getScriptIncome() returning an undefined value - * Bug Fix: Fixed an issue with Hacknet Server hash rate not always updating ` } diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 0d69234d2..66f709870 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -409,7 +409,7 @@ function processNetscript1Imports(code, workerScript) { } /** - * + * Find and return the next availble PID for a script */ let pidCounter = 1; function generateNextPid() { diff --git a/src/Terminal.js b/src/Terminal.js index 12e150742..ae05244bb 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -55,6 +55,7 @@ import { Message } from "./Message/Message"; import { showMessage } from "./Message/MessageHelpers"; import { addWorkerScript } from "./NetscriptWorker"; import { killWorkerScript } from "./Netscript/killWorkerScript"; +import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { Player } from "./Player"; import { hackWorldDaemon } from "./RedPill"; import { RunningScript } from "./Script/RunningScript"; @@ -1146,8 +1147,9 @@ let Terminal = { } case "killall": { for (let i = s.runningScripts.length - 1; i >= 0; --i) { - killWorkerScript(s.runningScripts[i], s.ip); + killWorkerScript(s.runningScripts[i], s.ip, false); } + WorkerScriptStartStopEventEmitter.emitEvent(); post("Killing all running scripts. May take up to a few minutes for the scripts to die..."); break; } @@ -1250,7 +1252,7 @@ let Terminal = { } for (let i = 0; i < s.runningScripts.length; i++) { let rsObj = s.runningScripts[i]; - let res = rsObj.filename; + let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`; for (let j = 0; j < rsObj.args.length; ++j) { res += (" " + rsObj.args[j].toString()); } @@ -1432,7 +1434,23 @@ let Terminal = { return; } - post("Script Threads RAM Usage"); + // Headers + const scriptWidth = 40; + const pidWidth = 10; + const threadsWidth = 16; + + const scriptTxt = "Script"; + const pidTxt = "PID"; + const threadsTxt = "Threads"; + const ramTxt = "RAM Usage"; + + const spacesAfterScriptTxt = " ".repeat(scriptWidth - scriptTxt.length); + const spacesAfterPidTxt = " ".repeat(pidWidth - pidTxt.length); + const spacesAfterThreadsTxt = " ".repeat(threadsWidth - threadsTxt.length); + + const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`; + + post(headers); let currRunningScripts = s.runningScripts; // Iterate through scripts on current server @@ -1440,19 +1458,30 @@ let Terminal = { let script = currRunningScripts[i]; // Calculate name padding - let numSpacesScript = 32 - script.filename.length; // 26 -> width of name column - if (numSpacesScript < 0) {numSpacesScript = 0;} - let spacesScript = Array(numSpacesScript+1).join(" "); + const numSpacesScript = Math.max(0, scriptWidth - script.filename.length); + const spacesScript = " ".repeat(numSpacesScript); + + // Calculate PID padding + const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length); + const spacesPid = " ".repeat(numSpacesPid); // Calculate thread padding - let numSpacesThread = 16 - (script.threads + "").length; // 16 -> width of thread column - let spacesThread = Array(numSpacesThread+1).join(" "); + const numSpacesThread = Math.max(0, threadsWidth - (script.threads + "").length); + const spacesThread = " ".repeat(numSpacesThread); // Calculate and transform RAM usage - let ramUsage = numeralWrapper.format(getRamUsageFromRunningScript(script) * script.threads, '0.00') + " GB"; + const ramUsage = numeralWrapper.format(getRamUsageFromRunningScript(script) * script.threads, '0.00') + " GB"; - var entry = [script.filename, spacesScript, script.threads, spacesThread, ramUsage]; - post(entry.join("")); + const entry = [ + script.filename, + spacesScript, + script.pid, + spacesPid, + script.threads, + spacesThread, + ramUsage + ].join(""); + post(entry); } break; } From 58d04c0cbbbe3ad9005e3ad02b27690136e149f9 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Thu, 27 Jun 2019 00:01:46 -0700 Subject: [PATCH 38/39] Updated documentation changelog --- doc/source/changelog.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 106136f69..0af1d4437 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -3,6 +3,21 @@ Changelog ========= +v0.47.1 - 6/27/2019 +------------------- +* Stock Market changes: +** Transactions no longer influence stock prices (but they still influence forecast) +** Changed the way stocks behave, particularly with regard to how the stock forecast occasionally "flips" +** Hacking & growing a server can potentially affect the way the corresponding stock's forecast changes +** Working for a company positively affects the way the corresponding stock's forecast changes + +* Scripts now start/stop instantly +* Improved performance when starting up many copies of a new NetscriptJS script (by Ornedan) +* Improved performance when killing scripts +* Dialog boxes can now be closed with the ESC key (by jaguilar) +* NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar) +* write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar) + v0.47.0 - 5/17/2019 ------------------- * Stock Market changes: From 433b399de9397b805181dc89c8e1a024e6490dbe Mon Sep 17 00:00:00 2001 From: danielyxie Date: Fri, 28 Jun 2019 09:28:08 -0700 Subject: [PATCH 39/39] v0.47.1 --- dist/engine.bundle.js | 2 +- dist/engineStyle.bundle.js | 2 +- dist/engineStyle.css | 259 +++++++++++++++++-------------------- dist/vendor.bundle.js | 26 ++-- doc/source/changelog.rst | 8 +- 5 files changed, 141 insertions(+), 156 deletions(-) diff --git a/dist/engine.bundle.js b/dist/engine.bundle.js index da6caf5f2..e7f321604 100644 --- a/dist/engine.bundle.js +++ b/dist/engine.bundle.js @@ -1,2 +1,2 @@ -!function(e){function t(t){for(var r,o,s=t[0],l=t[1],c=t[2],p=0,h=[];p0&&(s+=`${n} days `),a>0&&(s+=`${a} hours `),o>0&&(s+=`${o} minutes `),s+=`${i%60} seconds`},t.longestCommonStart=function(e){if(!a(e))return"";if(0===e.length)return"";const t=e.concat().sort(),n=t[0],r=t[t.length-1],i=n.length;let o=0;const s=(e,t)=>e.toUpperCase()===t.toUpperCase();for(;o=0;e--)if(1===n[e].nodeType)return!0;return!1},t.generateRandomString=function(e){let t="";const n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";for(let r=0;r{t.delay=null,n()},e),t.delayResolve=n})}function c(e,t,n=null){var r="";null!=n&&(r=" (Line "+function(e,t){var n=t.scriptRef.codeCode();try{return((n=n.substring(0,e.start)).match(/\n/g)||[]).length+1}catch(e){return-1}}(n,e)+")");return"|"+e.serverIp+"|"+e.name+"|"+t+r}function u(e,t,n){const r=e.scriptRef.threads;if(!n)return isNaN(r)||r<1?1:r;const a=0|n;if(isNaN(n)||a<1)throw c(e,`Invalid thread count passed to ${t}: ${n}. Threads must be a positive number.`);if(n>r)throw c(e,`Too many threads requested by ${t}. Requested: ${n}. Has: ${r}.`);return a}function p(e){if(!Object(o.isString)(e))return!1;let t=e.split("|");if(4!=t.length)return!1;var n=t[1];return!!Object(i.isValidIPAddress)(n)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(92);function a(e,t={}){const n=`color: ${null!=t.color?t.color:"var(--my-font-color)"}; background-color:var(--my-background-color);${void 0===t.id?" white-space:pre-wrap;":""}`,a=`${e}`;r.getElementById("terminal-input").insertAdjacentHTML("beforebegin",a),function(){const e=r.getElementById("terminal-container");e.scrollTop=e.scrollHeight}()}t.post=function(e){a(e)},t.postError=function(e){a(`ERROR: ${e}`,{color:"#ff2929"})},t.hackProgressBarPost=function(e){a(e,{id:"hack-progress-bar"})},t.hackProgressPost=function(e){a(e,{id:"hack-progress"})},t.postContent=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CONSTANTS={Version:"0.47.0",MaxSkillLevel:975,MilliPerCycle:200,CorpFactionRepRequirement:2e5,BaseCostFor1GBOfRamHome:32e3,BaseCostFor1GBOfRamServer:55e3,TravelCost:2e5,BaseFavorToDonate:150,DonateMoneyToRepDivisor:1e6,FactionReputationToFavorBase:500,FactionReputationToFavorMult:1.02,CompanyReputationToFavorBase:500,CompanyReputationToFavorMult:1.02,NeuroFluxGovernorLevelMult:1.14,NumNetscriptPorts:20,HomeComputerMaxRam:1073741824,ServerBaseGrowthRate:1.03,ServerMaxGrowthRate:1.0035,ServerFortifyAmount:.002,ServerWeakenAmount:.05,PurchasedServerLimit:25,PurchasedServerMaxRam:1048576,AugmentationCostMultiplier:5,AugmentationRepMultiplier:2.5,MultipleAugMultiplier:1.9,TorRouterCost:2e5,InfiltrationBribeBaseAmount:1e5,InfiltrationMoneyValue:5e3,InfiltrationRepValue:1.4,InfiltrationExpPow:.8,WSEAccountCost:2e8,TIXAPICost:5e9,MarketData4SCost:1e9,MarketDataTixApi4SCost:25e9,StockMarketCommission:1e5,HospitalCostPerHp:1e5,IntelligenceCrimeWeight:.05,IntelligenceInfiltrationWeight:.1,IntelligenceCrimeBaseExpGain:.001,IntelligenceProgramBaseExpGain:500,IntelligenceTerminalHackBaseExpGain:200,IntelligenceSingFnBaseExpGain:.002,IntelligenceClassBaseExpGain:1e-6,IntelligenceHackingMissionBaseExpGain:.03,HackingMissionRepToDiffConversion:1e4,HackingMissionRepToRewardConversion:7,HackingMissionSpamTimeIncrease:25e3,HackingMissionTransferAttackIncrease:1.05,HackingMissionMiscDefenseIncrease:1.05,HackingMissionDifficultyToHacking:135,HackingMissionHowToPlay:"Hacking missions are a minigame that, if won, will reward you with faction reputation.

    In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes are colored blue, while the enemy's are red. There are also other nodes on the map colored gray that initially belong to neither you nor the enemy. The goal of the game is to capture all of the enemy's Database nodes within the time limit. If you fail to do this, you will lose.

    Each Node has three stats: Attack, Defense, and HP. There are five different actions that a Node can take:

    Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

    Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

    Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's defense.

    Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.

    Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.

    Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the Attack/Defense of the individual Node that is performing the action.

    To capture a Node, you must lower its HP down to 0.

    There are six different types of Nodes:

    CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action

    Firewall - Nodes with high defense. These Nodes can 'Fortify'

    Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within the time limit. These Nodes cannot perform any actions

    Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete the mission. These Nodes cannot perform any actions

    Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. These Nodes are capable of performing every action except the 'Attack' action

    Shield - Nodes with high defense. These Nodes can 'Fortify'

    To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard shortcut.

    For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, or press 'd'.

    Other Notes:

    -Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that are not actively being targeted will increase by a fixed percentage.

    -Whenever a Node is conquered, its stats are significantly reduced

    -Miscellaneous Nodes slowly raise their defense over time

    -Nodes slowly regenerate health over time.",MillisecondsPer20Hours:72e6,GameCyclesPer20Hours:36e4,MillisecondsPer10Hours:36e6,GameCyclesPer10Hours:18e4,MillisecondsPer8Hours:288e5,GameCyclesPer8Hours:144e3,MillisecondsPer4Hours:144e5,GameCyclesPer4Hours:72e3,MillisecondsPer2Hours:72e5,GameCyclesPer2Hours:36e3,MillisecondsPerHour:36e5,GameCyclesPerHour:18e3,MillisecondsPerHalfHour:18e5,GameCyclesPerHalfHour:9e3,MillisecondsPerQuarterHour:9e5,GameCyclesPerQuarterHour:4500,MillisecondsPerFiveMinutes:3e5,GameCyclesPerFiveMinutes:1500,FactionWorkHacking:"Faction Hacking Work",FactionWorkField:"Faction Field Work",FactionWorkSecurity:"Faction Security Work",WorkTypeCompany:"Working for Company",WorkTypeCompanyPartTime:"Working for Company part-time",WorkTypeFaction:"Working for Faction",WorkTypeCreateProgram:"Working on Create a Program",WorkTypeStudyClass:"Studying or Taking a class at university",WorkTypeCrime:"Committing a crime",ClassStudyComputerScience:"studying Computer Science",ClassDataStructures:"taking a Data Structures course",ClassNetworks:"taking a Networks course",ClassAlgorithms:"taking an Algorithms course",ClassManagement:"taking a Management course",ClassLeadership:"taking a Leadership course",ClassGymStrength:"training your strength at a gym",ClassGymDefense:"training your defense at a gym",ClassGymDexterity:"training your dexterity at a gym",ClassGymAgility:"training your agility at a gym",ClassDataStructuresBaseCost:40,ClassNetworksBaseCost:80,ClassAlgorithmsBaseCost:320,ClassManagementBaseCost:160,ClassLeadershipBaseCost:320,ClassGymBaseCost:120,CrimeShoplift:"shoplift",CrimeRobStore:"rob a store",CrimeMug:"mug someone",CrimeLarceny:"commit larceny",CrimeDrugs:"deal drugs",CrimeBondForgery:"forge corporate bonds",CrimeTraffickArms:"traffick illegal arms",CrimeHomicide:"commit homicide",CrimeGrandTheftAuto:"commit grand theft auto",CrimeKidnap:"kidnap someone for ransom",CrimeAssassination:"assassinate a high-profile target",CrimeHeist:"pull off the ultimate heist",CodingContractBaseFactionRepGain:2500,CodingContractBaseCompanyRepGain:4e3,CodingContractBaseMoneyGain:75e6,TotalNumBitNodes:24,LatestUpdate:"\n v0.47.0\n * Stock Market changes:\n ** Implemented spread. Stock's now have bid and ask prices at which transactions occur\n ** Large transactions will now influence a stock's price and forecast\n ** This \"influencing\" can take effect in the middle of a transaction\n ** See documentation for more details on these changes\n ** Added getStockAskPrice(), getStockBidPrice() Netscript functions to the TIX API\n ** Added getStockPurchaseCost(), getStockSaleGain() Netscript functions to the TIX API\n\n * Re-sleeves can no longer have the NeuroFlux Governor augmentation\n ** This is just a temporary patch until the mechanic gets re-worked\n\n * hack(), grow(), and weaken() functions now take optional arguments for number of threads to use (by MasonD)\n * codingcontract.attempt() now takes an optional argument that allows you to configure the function to return a contract's reward\n * Adjusted RAM costs of Netscript Singularity functions (mostly increased)\n * Adjusted RAM cost of codingcontract.getNumTriesRemaining() Netscript function\n * Netscript Singularity functions no longer cost extra RAM outside of BitNode-4\n * Corporation employees no longer have an \"age\" stat\n * Gang Wanted level gain rate capped at 100 (per employee)\n * Script startup/kill is now processed every 3 seconds, instead of 6 seconds\n * getHackTime(), getGrowTime(), and getWeakenTime() now return Infinity if called on a Hacknet Server\n * Money/Income tracker now displays money lost from hospitalizations\n * Exported saves now have a unique filename based on current BitNode and timestamp\n * Maximum number of Hacknet Servers decreased from 25 to 20\n * Bug Fix: Corporation employees stats should no longer become negative\n * Bug Fix: Fixed sleeve.getInformation() throwing error in certain scenarios\n * Bug Fix: Coding contracts should no longer generate on the w0r1d_d43m0n server\n * Bug Fix: Duplicate Sleeves now properly have access to all Augmentations if you have a gang\n * Bug Fix: getAugmentationsFromFaction() & purchaseAugmentation() functions should now work properly if you have a gang\n * Bug Fix: Fixed issue that caused messages (.msg) to be sent when refreshing/reloading the game\n * Bug Fix: Purchasing hash upgrades for Bladeburner/Corporation when you don't actually have access to those mechanics no longer gives hashes\n * Bug Fix: run(), exec(), and spawn() Netscript functions now throw if called with 0 threads\n * Bug Fix: Faction UI should now automatically update reputation\n * Bug Fix: Fixed purchase4SMarketData()\n * Bug Fix: Netscript1.0 now works properly for multiple 'namespace' imports (import * as namespace from \"script\")\n * Bug Fix: Terminal 'wget' command now correctly evaluates directory paths\n * Bug Fix: wget(), write(), and scp() Netscript functions now fail if an invalid filepath is passed in\n * Bug Fix: Having Corporation warehouses at full capacity should no longer freeze game in certain conditions\n * Bug Fix: Prevented an exploit that allows you to buy multiple copies of an Augmentation by holding the 'Enter' button\n * Bug Fix: gang.getOtherGangInformation() now properly returns a deep copy\n * Bug Fix: Fixed getScriptIncome() returning an undefined value\n * Bug Fix: Fixed an issue with Hacknet Server hash rate not always updating\n "}},function(e,t,n){"use strict";n.r(t),function(e){n.d(t,"dialogBoxCreate",function(){return i}),n.d(t,"dialogBoxOpened",function(){return a});let r=[];e(document).click(function(t){a&&r.length>=1&&(e(t.target).closest(r[0]).length||(r[0].remove(),r.splice(0,1),0==r.length?a=!1:r[0].style.visibility="visible"))}),e(document).on("click",".dialog-box-close-button",function(e){a&&r.length>=1&&(r[0].remove(),r.splice(0,1),0==r.length?a=!1:r[0].style.visibility="visible")});let a=!1;function i(e,t=!1){console.log("dialogBoxCreate() called");var n=document.createElement("div");n.setAttribute("class","dialog-box-container");var i=document.createElement("div");i.setAttribute("class","dialog-box-content");var o,s=document.createElement("span");s.setAttribute("class","dialog-box-close-button"),s.innerHTML="×",t?(o=document.createElement("pre")).innerHTML=e:(o=document.createElement("p")).innerHTML=e.replace(/(?:\r\n|\r|\n)/g,"
    "),i.appendChild(s),i.appendChild(o),n.appendChild(i),document.body.appendChild(n),r.length>=1&&(n.style.visibility="hidden"),r.push(n),setTimeout(function(){a=!0},400)}}.call(this,n(85))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RamCostConstants={ScriptBaseRamCost:1.6,ScriptDomRamCost:25,ScriptHackRamCost:.1,ScriptHackAnalyzeRamCost:1,ScriptGrowRamCost:.15,ScriptGrowthAnalyzeRamCost:1,ScriptWeakenRamCost:.15,ScriptScanRamCost:.2,ScriptPortProgramRamCost:.05,ScriptRunRamCost:1,ScriptExecRamCost:1.3,ScriptSpawnRamCost:2,ScriptScpRamCost:.6,ScriptKillRamCost:.5,ScriptHasRootAccessRamCost:.05,ScriptGetHostnameRamCost:.05,ScriptGetHackingLevelRamCost:.05,ScriptGetMultipliersRamCost:4,ScriptGetServerRamCost:.1,ScriptFileExistsRamCost:.1,ScriptIsRunningRamCost:.1,ScriptHacknetNodesRamCost:4,ScriptHNUpgLevelRamCost:.4,ScriptHNUpgRamRamCost:.6,ScriptHNUpgCoreRamCost:.8,ScriptGetStockRamCost:2,ScriptBuySellStockRamCost:2.5,ScriptGetPurchaseServerRamCost:.25,ScriptPurchaseServerRamCost:2.25,ScriptGetPurchasedServerLimit:.05,ScriptGetPurchasedServerMaxRam:.05,ScriptRoundRamCost:.05,ScriptReadWriteRamCost:1,ScriptArbScriptRamCost:1,ScriptGetScriptRamCost:.1,ScriptGetHackTimeRamCost:.05,ScriptGetFavorToDonate:.1,ScriptCodingContractBaseRamCost:10,ScriptSleeveBaseRamCost:4,ScriptSingularityFn1RamCost:2,ScriptSingularityFn2RamCost:3,ScriptSingularityFn3RamCost:5,ScriptGangApiBaseRamCost:4,ScriptBladeburnerApiBaseRamCost:4},t.RamCosts={hacknet:{numNodes:()=>0,purchaseNode:()=>0,getPurchaseNodeCost:()=>0,getNodeStats:()=>0,upgradeLevel:()=>0,upgradeRam:()=>0,upgradeCore:()=>0,upgradeCache:()=>0,getLevelUpgradeCost:()=>0,getRamUpgradeCost:()=>0,getCoreUpgradeCost:()=>0,getCacheUpgradeCost:()=>0,numHashes:()=>0,hashCost:()=>0,spendHashes:()=>0},sprintf:()=>0,vsprintf:()=>0,scan:()=>t.RamCostConstants.ScriptScanRamCost,hack:()=>t.RamCostConstants.ScriptHackRamCost,hackAnalyzeThreads:()=>t.RamCostConstants.ScriptHackAnalyzeRamCost,hackAnalyzePercent:()=>t.RamCostConstants.ScriptHackAnalyzeRamCost,hackChance:()=>t.RamCostConstants.ScriptHackAnalyzeRamCost,sleep:()=>0,grow:()=>t.RamCostConstants.ScriptGrowRamCost,growthAnalyze:()=>t.RamCostConstants.ScriptGrowthAnalyzeRamCost,weaken:()=>t.RamCostConstants.ScriptWeakenRamCost,print:()=>0,tprint:()=>0,clearLog:()=>0,disableLog:()=>0,enableLog:()=>0,isLogEnabled:()=>0,getScriptLogs:()=>0,nuke:()=>t.RamCostConstants.ScriptPortProgramRamCost,brutessh:()=>t.RamCostConstants.ScriptPortProgramRamCost,ftpcrack:()=>t.RamCostConstants.ScriptPortProgramRamCost,relaysmtp:()=>t.RamCostConstants.ScriptPortProgramRamCost,httpworm:()=>t.RamCostConstants.ScriptPortProgramRamCost,sqlinject:()=>t.RamCostConstants.ScriptPortProgramRamCost,run:()=>t.RamCostConstants.ScriptRunRamCost,exec:()=>t.RamCostConstants.ScriptExecRamCost,spawn:()=>t.RamCostConstants.ScriptSpawnRamCost,kill:()=>t.RamCostConstants.ScriptKillRamCost,killall:()=>t.RamCostConstants.ScriptKillRamCost,exit:()=>0,scp:()=>t.RamCostConstants.ScriptScpRamCost,ls:()=>t.RamCostConstants.ScriptScanRamCost,ps:()=>t.RamCostConstants.ScriptScanRamCost,hasRootAccess:()=>t.RamCostConstants.ScriptHasRootAccessRamCost,getIp:()=>t.RamCostConstants.ScriptGetHostnameRamCost,getHostname:()=>t.RamCostConstants.ScriptGetHostnameRamCost,getHackingLevel:()=>t.RamCostConstants.ScriptGetHackingLevelRamCost,getHackingMultipliers:()=>t.RamCostConstants.ScriptGetMultipliersRamCost,getHacknetMultipliers:()=>t.RamCostConstants.ScriptGetMultipliersRamCost,getBitNodeMultipliers:()=>t.RamCostConstants.ScriptGetMultipliersRamCost,getServerMoneyAvailable:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerSecurityLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerBaseSecurityLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerMinSecurityLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerRequiredHackingLevel:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerMaxMoney:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerGrowth:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerNumPortsRequired:()=>t.RamCostConstants.ScriptGetServerRamCost,getServerRam:()=>t.RamCostConstants.ScriptGetServerRamCost,serverExists:()=>t.RamCostConstants.ScriptGetServerRamCost,fileExists:()=>t.RamCostConstants.ScriptFileExistsRamCost,isRunning:()=>t.RamCostConstants.ScriptIsRunningRamCost,getStockSymbols:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockPrice:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockAskPrice:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockBidPrice:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockPosition:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockMaxShares:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockPurchaseCost:()=>t.RamCostConstants.ScriptGetStockRamCost,getStockSaleGain:()=>t.RamCostConstants.ScriptGetStockRamCost,buyStock:()=>t.RamCostConstants.ScriptBuySellStockRamCost,sellStock:()=>t.RamCostConstants.ScriptBuySellStockRamCost,shortStock:()=>t.RamCostConstants.ScriptBuySellStockRamCost,sellShort:()=>t.RamCostConstants.ScriptBuySellStockRamCost,placeOrder:()=>t.RamCostConstants.ScriptBuySellStockRamCost,cancelOrder:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getOrders:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getStockVolatility:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getStockForecast:()=>t.RamCostConstants.ScriptBuySellStockRamCost,purchase4SMarketData:()=>t.RamCostConstants.ScriptBuySellStockRamCost,purchase4SMarketDataTixApi:()=>t.RamCostConstants.ScriptBuySellStockRamCost,getPurchasedServerLimit:()=>t.RamCostConstants.ScriptGetPurchasedServerLimit,getPurchasedServerMaxRam:()=>t.RamCostConstants.ScriptGetPurchasedServerMaxRam,getPurchasedServerCost:()=>t.RamCostConstants.ScriptGetPurchaseServerRamCost,purchaseServer:()=>t.RamCostConstants.ScriptPurchaseServerRamCost,deleteServer:()=>t.RamCostConstants.ScriptPurchaseServerRamCost,getPurchasedServers:()=>t.RamCostConstants.ScriptPurchaseServerRamCost,write:()=>t.RamCostConstants.ScriptReadWriteRamCost,tryWrite:()=>t.RamCostConstants.ScriptReadWriteRamCost,read:()=>t.RamCostConstants.ScriptReadWriteRamCost,peek:()=>t.RamCostConstants.ScriptReadWriteRamCost,clear:()=>t.RamCostConstants.ScriptReadWriteRamCost,getPortHandle:()=>10*t.RamCostConstants.ScriptReadWriteRamCost,rm:()=>t.RamCostConstants.ScriptReadWriteRamCost,scriptRunning:()=>t.RamCostConstants.ScriptArbScriptRamCost,scriptKill:()=>t.RamCostConstants.ScriptArbScriptRamCost,getScriptName:()=>0,getScriptRam:()=>t.RamCostConstants.ScriptGetScriptRamCost,getHackTime:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,getGrowTime:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,getWeakenTime:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,getScriptIncome:()=>t.RamCostConstants.ScriptGetScriptRamCost,getScriptExpGain:()=>t.RamCostConstants.ScriptGetScriptRamCost,nFormat:()=>0,getTimeSinceLastAug:()=>t.RamCostConstants.ScriptGetHackTimeRamCost,prompt:()=>0,wget:()=>0,getFavorToDonate:()=>t.RamCostConstants.ScriptGetFavorToDonate,universityCourse:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,gymWorkout:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,travelToCity:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,purchaseTor:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,purchaseProgram:()=>t.RamCostConstants.ScriptSingularityFn1RamCost,getStats:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,getCharacterInformation:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,isBusy:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/4,stopAction:()=>t.RamCostConstants.ScriptSingularityFn1RamCost/2,upgradeHomeRam:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,getUpgradeHomeRamCost:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/2,workForCompany:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,applyToCompany:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,getCompanyRep:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getCompanyFavor:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getCompanyFavorGain:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/4,checkFactionInvitations:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,joinFaction:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,workForFaction:()=>t.RamCostConstants.ScriptSingularityFn2RamCost,getFactionRep:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getFactionFavor:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/3,getFactionFavorGain:()=>t.RamCostConstants.ScriptSingularityFn2RamCost/4,donateToFaction:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,createProgram:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,commitCrime:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getCrimeChance:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getOwnedAugmentations:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getOwnedSourceFiles:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationsFromFaction:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationPrereq:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,getAugmentationCost:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,purchaseAugmentation:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,installAugmentations:()=>t.RamCostConstants.ScriptSingularityFn3RamCost,gang:{getMemberNames:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,getGangInformation:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getOtherGangInformation:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getMemberInformation:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,canRecruitMember:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,recruitMember:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getTaskNames:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,setMemberTask:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getEquipmentNames:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/4,getEquipmentCost:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getEquipmentType:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,purchaseEquipment:()=>t.RamCostConstants.ScriptGangApiBaseRamCost,ascendMember:()=>t.RamCostConstants.ScriptGangApiBaseRamCost,setTerritoryWarfare:()=>t.RamCostConstants.ScriptGangApiBaseRamCost/2,getChanceToWinClash:()=>t.RamCostConstants.ScriptGangApiBaseRamCost,getBonusTime:()=>0},bladeburner:{getContractNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getOperationNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getBlackOpNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getBlackOpRank:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/2,getGeneralActionNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,getSkillNames:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/10,startAction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,stopBladeburnerAction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/2,getCurrentAction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost/4,getActionTime:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionEstimatedSuccessChance:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionRepGain:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionCountRemaining:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionMaxLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionCurrentLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getActionAutolevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,setActionAutolevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,setActionLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getRank:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getSkillPoints:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getSkillLevel:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getSkillUpgradeCost:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,upgradeSkill:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getTeamSize:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,setTeamSize:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCityEstimatedPopulation:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCityEstimatedCommunities:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCityChaos:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getCity:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,switchCity:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getStamina:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,joinBladeburnerFaction:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,joinBladeburnerDivision:()=>t.RamCostConstants.ScriptBladeburnerApiBaseRamCost,getBonusTime:()=>0},codingcontract:{attempt:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost,getContractType:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/2,getData:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/2,getDescription:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/2,getNumTriesRemaining:()=>t.RamCostConstants.ScriptCodingContractBaseRamCost/5},sleeve:{getNumSleeves:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToShockRecovery:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToSynchronize:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToCommitCrime:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToUniversityCourse:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,travel:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToCompanyWork:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToFactionWork:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,setToGymWorkout:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getSleeveStats:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getTask:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getInformation:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getSleeveAugmentations:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,getSleevePurchasableAugs:()=>t.RamCostConstants.ScriptSleeveBaseRamCost,purchaseSleeveAug:()=>t.RamCostConstants.ScriptSleeveBaseRamCost},heart:{break:()=>0}},t.getRamCost=function(...e){if(0===e.length)return console.warn("No arguments passed to getRamCost()"),0;let n=t.RamCosts[e[0]];for(let t=1;t(ce.loadFactionContent(),Object(b.displayFactionContent)(n),!1)})),e.appendChild(Object(te.createElement)("br"))}();ce.Display.factionsContent.appendChild(e),ce.Display.factionsContent.appendChild(Object(te.createElement)("br")),ce.Display.factionsContent.appendChild(Object(te.createElement)("h1",{innerText:"Outstanding Faction Invitations"})),ce.Display.factionsContent.appendChild(Object(te.createElement)("p",{width:"70%",innerText:"Lists factions you have been invited to, as well as factions you have previously rejected. You can accept these faction invitations at any time."}));var n=Object(te.createElement)("ul");for(t=0;t{if(!t.isTrusted)return!1;Object(b.joinFaction)(f.Factions[e]);for(var n=0;n0&&(ce._lastUpdate=e-n,x.Player.lastUpdate=e-n,ce.updateGame(t)),window.requestAnimationFrame(ce.idleTimer)},updateGame:function(e=1){var t=e*ce._idleSpeed;null==x.Player.totalPlaytime&&(x.Player.totalPlaytime=0),null==x.Player.playtimeSinceLastAug&&(x.Player.playtimeSinceLastAug=0),null==x.Player.playtimeSinceLastBitnode&&(x.Player.playtimeSinceLastBitnode=0),x.Player.totalPlaytime+=t,x.Player.playtimeSinceLastAug+=t,x.Player.playtimeSinceLastBitnode+=t,!0===U.a.actionStarted&&(ce._totalActionTime=U.a.actionTime,ce._actionTimeLeft=U.a.actionTime,ce._actionInProgress=!0,ce._actionProgressBarCount=1,ce._actionProgressStr="[ ]",ce._actionTimeStr="Time left: ",U.a.actionStarted=!1),x.Player.isWorking&&(x.Player.workType==_.CONSTANTS.WorkTypeFaction?x.Player.workForFaction(e):x.Player.workType==_.CONSTANTS.WorkTypeCreateProgram?x.Player.createProgramWork(e):x.Player.workType==_.CONSTANTS.WorkTypeStudyClass?x.Player.takeClass(e):x.Player.workType==_.CONSTANTS.WorkTypeCrime?x.Player.commitCrime(e):x.Player.workType==_.CONSTANTS.WorkTypeCompanyPartTime?x.Player.workPartTime(e):x.Player.work(e)),x.Player.hasWseAccount&&Object(F.j)(e),x.Player.inGang()&&x.Player.gang.process(e,x.Player),S.c&&S.b&&S.b.process(e),x.Player.corporation instanceof g.c&&x.Player.corporation.storeCycles(e),x.Player.bladeburner instanceof u.a&&x.Player.bladeburner.storeCycles(e);for(let t=0;t0?(t.innerHTML=e,t.setAttribute("class","notification-on")):(t.innerHTML="",t.setAttribute("class","notification-off")),ce.Counters.createProgramNotifications=10}if(ce.Counters.checkFactionInvitations<=0){var n=x.Player.checkForFactionInvitations();if(n.length>0){!1===x.Player.firstFacInvRecvd&&(x.Player.firstFacInvRecvd=!0,document.getElementById("factions-tab").style.display="list-item",document.getElementById("character-menu-header").click(),document.getElementById("character-menu-header").click());var r=n[Math.floor(Math.random()*n.length)];Object(b.inviteToFaction)(r)}ce.Counters.checkFactionInvitations=100}if(ce.Counters.passiveFactionGrowth<=0){var s=Math.floor(600-ce.Counters.passiveFactionGrowth);Object(b.processPassiveFactionRepGain)(s),ce.Counters.passiveFactionGrowth=600}if(ce.Counters.messages<=0&&(Object(T.b)(),o.Augmentations[l.AugmentationNames.TheRedPill].owned?ce.Counters.messages=4500:ce.Counters.messages=150),ce.Counters.sCr<=0&&(x.Player.hasWseAccount&&Object(F.k)(),ce.Counters.sCr=1500),ce.Counters.mechanicProcess<=0){if(x.Player.corporation instanceof g.c&&x.Player.corporation.process(),x.Player.bladeburner instanceof u.a)try{x.Player.bladeburner.process()}catch(e){Object(ne.exceptionAlert)("Exception caught in Bladeburner.process(): "+e)}ce.Counters.mechanicProcess=5}ce.Counters.contractGeneration<=0&&(Math.random()<=.25&&Object(m.b)(),ce.Counters.contractGeneration=3e3)},_totalActionTime:0,_actionTimeLeft:0,_actionTimeStr:"Time left: ",_actionProgressStr:"[ ]",_actionProgressBarCount:1,_actionInProgress:!1,updateHackProgress:function(e=1){var t=e*ce._idleSpeed;ce._actionTimeLeft-=t/1e3,ce._actionTimeLeft=Math.max(ce._actionTimeLeft,0);for(var n=Math.round(100*(1-ce._actionTimeLeft/ce._totalActionTime));2*ce._actionProgressBarCount<=n;)ce._actionProgressStr=Object(r.replaceAt)(ce._actionProgressStr,ce._actionProgressBarCount,"|"),ce._actionProgressBarCount+=1;ce._actionTimeStr="Time left: "+Math.max(0,Math.round(ce._actionTimeLeft)).toString()+"s",document.getElementById("hack-progress").innerHTML=ce._actionTimeStr,document.getElementById("hack-progress-bar").innerHTML=ce._actionProgressStr.replace(/ /g," "),n>=100&&(ce._actionInProgress=!1,U.a.finishAction())},closeMainMenuHeader:function(e){for(var t=0;t"+z.numeralWrapper.formatMoney(q)+"
    and your Hacknet Nodes generated "+e+"");var J=[t,n,a,o,h,m,A,R,B];x.Player.firstFacInvRecvd?J.push(l):l.style.display="none",x.Player.firstAugPurchased?J.push(p):p.style.display="none",""!==x.Player.companyName?J.push(v):v.style.display="none",x.Player.firstTimeTraveled?J.push(y):y.style.display="none",x.Player.firstProgramAvailable?J.push(i):i.style.display="none",x.Player.hasWseAccount?J.push(P):P.style.display="none",x.Player.bladeburner instanceof u.a?J.push(O):O.style.display="none",x.Player.corporation instanceof g.c?J.push(S):S.style.display="none",x.Player.inGang()?J.push(w):w.style.display="none",ce.closeMainMenuHeader(J)}else{console.log("Initializing new game"),Object(c.initBitNodes)(),Object(c.initBitNodeMultipliers)(x.Player),Object(L.c)(),Object(j.initSpecialServerIps)(),ce.setDisplayElements(),ce.start(),x.Player.init(),Object(D.initForeignServers)(x.Player.getHomeComputer()),Object(d.initCompanies)(),Object(f.initFactions)(),Object(s.d)(),Object(T.c)(),Object(C.a)(),Object(W.updateSourceFileFlags)(x.Player),document.getElementById("hacking-menu-header").classList.toggle("opened"),document.getElementById("character-menu-header").classList.toggle("opened"),document.getElementById("world-menu-header").classList.toggle("opened"),document.getElementById("help-menu-header").classList.toggle("opened"),l.style.display="none",p.style.display="none",v.style.display="none",P.style.display="none",y.style.display="none",i.style.display="none",O.style.display="none",S.style.display="none",w.style.display="none",B.style.display="none",ce.openMainMenuHeader([t,n,a,o,h,m,A,R]),Object(k.c)(),Object(re.removeLoadingScreen)()}Object(V.a)(),Object(I.d)(),U.a.resetTerminalInput()},setDisplayElements:function(){if(ce.Display.terminalContent=document.getElementById("terminal-container"),Y.routing.navigateTo(Y.Page.Terminal),ce.Display.characterContent=document.getElementById("character-container"),ce.Display.characterContent.style.display="none",ce.Display.scriptEditorContent=document.getElementById("script-editor-container"),ce.Display.scriptEditorContent.style.display="none",ce.Display.activeScriptsContent=document.getElementById("active-scripts-container"),ce.Display.activeScriptsContent.style.display="none",ce.Display.hacknetNodesContent=document.getElementById("hacknet-nodes-container"),ce.Display.hacknetNodesContent.style.display="none",ce.Display.createProgramContent=document.getElementById("create-program-container"),ce.Display.createProgramContent.style.display="none",ce.Display.factionsContent=document.getElementById("factions-container"),ce.Display.factionsContent.style.display="none",ce.Display.factionContent=document.getElementById("faction-container"),ce.Display.factionContent.style.display="none",ce.Display.augmentationsContent=document.getElementById("augmentations-container"),ce.Display.augmentationsContent.style.display="none",ce.Display.tutorialContent=document.getElementById("tutorial-container"),ce.Display.tutorialContent.style.display="none",ce.Display.infiltrationContent=document.getElementById("infiltration-container"),ce.Display.infiltrationContent.style.display="none",ce.Display.stockMarketContent=document.getElementById("stock-market-container"),ce.Display.stockMarketContent.style.display="none",ce.Display.missionContent=document.getElementById("mission-container"),ce.Display.missionContent.style.display="none",ce.Display.characterInfo=document.getElementById("character-content"),ce.Display.locationContent=document.getElementById("location-container"),ce.Display.locationContent.style.display="none",ce.Display.workInProgressContent=document.getElementById("work-in-progress-container"),ce.Display.workInProgressContent.style.display="none",ce.Display.redPillContent=document.getElementById("red-pill-container"),ce.Display.redPillContent.style.display="none",ce.Display.cinematicTextContent=document.getElementById("cinematic-text-container"),ce.Display.cinematicTextContent.style.display="none",!Object(X.initializeMainMenuLinks)()){const e="Failed to initialize Main Menu Links. Please try refreshing the page. If that doesn't work, report the issue to the developer";return Object(ne.exceptionAlert)(new Error(e)),void console.error(e)}},init:function(){if(document.getElementById("import-game-link").onclick=function(){N.b.importGame()},!Object(J.initializeMainMenuHeaders)(x.Player,!1)){const e="Failed to initialize Main Menu Headers. Please try refreshing the page. If that doesn't work, report the issue to the developer";return Object(ne.exceptionAlert)(new Error(e)),void console.error(e)}(X.MainMenuLinks.Terminal.addEventListener("click",function(){return ce.loadTerminalContent(),!1}),X.MainMenuLinks.ScriptEditor.addEventListener("click",function(){return ce.loadScriptEditorContent(),!1}),X.MainMenuLinks.ActiveScripts.addEventListener("click",function(){return ce.loadActiveScriptsContent(),!1}),X.MainMenuLinks.CreateProgram.addEventListener("click",function(){return ce.loadCreateProgramContent(),!1}),X.MainMenuLinks.Stats.addEventListener("click",function(){return ce.loadCharacterContent(),!1}),X.MainMenuLinks.Factions.addEventListener("click",function(){return ce.loadFactionsContent(),!1}),X.MainMenuLinks.Augmentations.addEventListener("click",function(){return ce.loadAugmentationsContent(),!1}),X.MainMenuLinks.HacknetNodes.addEventListener("click",function(){return ce.loadHacknetNodesContent(),!1}),X.MainMenuLinks.Sleeves.addEventListener("click",function(){return ce.loadSleevesContent(),X.MainMenuLinks.Sleeves.classList.add("active"),!1}),X.MainMenuLinks.City.addEventListener("click",function(){return ce.loadLocationContent(),!1}),X.MainMenuLinks.Travel.addEventListener("click",function(){return ce.loadTravelContent(),!1}),X.MainMenuLinks.Job.addEventListener("click",function(){return ce.loadJobContent(),!1}),X.MainMenuLinks.StockMarket.addEventListener("click",function(){return ce.loadStockMarketContent(),X.MainMenuLinks.StockMarket.classList.add("active"),!1}),X.MainMenuLinks.Bladeburner.addEventListener("click",function(){return ce.loadBladeburnerContent(),!1}),X.MainMenuLinks.Corporation.addEventListener("click",function(){return ce.loadCorporationContent(),X.MainMenuLinks.Corporation.classList.add("active"),!1}),X.MainMenuLinks.Gang.addEventListener("click",function(){return ce.loadGangContent(),!1}),X.MainMenuLinks.Tutorial.addEventListener("click",function(){return ce.loadTutorialContent(),!1}),X.MainMenuLinks.DevMenu.addEventListener("click",function(){return!1}),ce.ActiveScriptsList=document.getElementById("active-scripts-list"),ce.Clickables.saveMainMenuButton=document.getElementById("save-game-link"),ce.Clickables.saveMainMenuButton.addEventListener("click",function(){return N.b.saveGame(ue),!1}),ce.Clickables.deleteMainMenuButton=document.getElementById("delete-game-link"),ce.Clickables.deleteMainMenuButton.addEventListener("click",function(){return N.b.deleteGame(ue),!1}),document.getElementById("export-game-link").addEventListener("click",function(){return N.b.exportGame(),!1}),document.getElementById("character-overview-save-button").addEventListener("click",function(){return N.b.saveGame(ue),!1}),document.getElementById("character-overview-options-button").addEventListener("click",function(){return Object(Z.b)(),!1}),Object(A.c)(),Object(U.b)(),x.Player.isWorking)&&(document.getElementById("work-in-progress-cancel-button").addEventListener("click",function(){if(x.Player.workType==_.CONSTANTS.WorkTypeFaction){f.Factions[x.Player.currentWorkFactionName];x.Player.finishFactionWork(!0)}else x.Player.workType==_.CONSTANTS.WorkTypeCreateProgram?x.Player.finishCreateProgramWork(!0):x.Player.workType==_.CONSTANTS.WorkTypeStudyClass?x.Player.finishClass():x.Player.workType==_.CONSTANTS.WorkTypeCrime?x.Player.finishCrime(!0):x.Player.workType==_.CONSTANTS.WorkTypeCompanyPartTime?x.Player.finishWorkPartTime():x.Player.finishWork(!0)}),ce.loadWorkInProgressContent());document.getElementById("character-overview-container").style.display="block",document.getElementById("terminal-menu-link").removeAttribute("class"),document.getElementById("stats-menu-link").removeAttribute("class"),document.getElementById("create-script-menu-link").removeAttribute("class"),document.getElementById("active-scripts-menu-link").removeAttribute("class"),document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"),document.getElementById("city-menu-link").removeAttribute("class"),document.getElementById("tutorial-menu-link").removeAttribute("class"),document.getElementById("copy-save-to-clipboard-link").addEventListener("click",function(){const e=N.b.getSaveString();if(navigator.clipboard)navigator.clipboard.writeText(e).then(function(){Object(q.createStatusText)("Copied save to clipboard")},function(e){console.error("Unable to copy save data to clipboard using Async API"),Object(q.createStatusText)("Failed to copy save")});else{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.left="-9999px",document.body.appendChild(t),t.focus(),t.select();try{document.execCommand("copy")?Object(q.createStatusText)("Copied save to clipboard"):Object(q.createStatusText)("Failed to copy save")}catch(e){console.error("Unable to copy save data to clipboard using document.execCommand('copy')"),Object(q.createStatusText)("Failed to copy save")}document.body.removeChild(t)}}),document.getElementById("debug-delete-scripts-link").addEventListener("click",function(){return console.log("Deleting running scripts on home computer"),x.Player.getHomeComputer().runningScripts=[],Object(Q.dialogBoxCreate)("Forcefully deleted all running scripts on home computer. Please save and refresh page"),Object(Z.a)(),!1}),document.getElementById("debug-soft-reset").addEventListener("click",function(){return Object(Q.dialogBoxCreate)("Soft Reset!"),Object(w.a)(),Object(Z.a)(),!1})},start:function(){ce.idleTimer(),Object(M.g)()}};var ue,pe;window.onload=function(){if(!window.indexedDB)return ce.load(null);(pe=window.indexedDB.open("bitburnerSave",1)).onerror=function(e){return console.log("Error opening indexedDB: "),console.log(e),ce.load(null)},pe.onsuccess=function(e){console.log("Opening bitburnerSave database successful!");var t=(ue=e.target.result).transaction(["savestring"]).objectStore("savestring").get("save");t.onerror=function(e){return console.log("Error in Database request to get savestring: "+e),ce.load(null)},t.onsuccess=function(e){ce.load(t.result)}},pe.onupgradeneeded=function(e){e.target.result.createObjectStore("savestring")}}}.call(this,n(85))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getRandomInt=function(e,t){const n=Math.min(e,t),r=Math.max(e,t);return Math.floor(Math.random()*(r-n+1))+n}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(8),a=n(20),i=n(13),o=n(18);class s{constructor(e={info:"",moneyCost:0,name:"",repCost:0}){this.baseCost=0,this.baseRepRequirement=0,this.info="",this.isSpecial=!1,this.level=0,this.name="",this.owned=!1,this.prereqs=[],this.mults={},this.startingCost=0,this.name=e.name,this.info=e.info,this.prereqs=e.prereqs?e.prereqs:[],this.baseRepRequirement=e.repCost*r.CONSTANTS.AugmentationRepMultiplier*a.BitNodeMultipliers.AugmentationRepCost,this.baseCost=e.moneyCost*r.CONSTANTS.AugmentationCostMultiplier*a.BitNodeMultipliers.AugmentationMoneyCost,this.startingCost=this.baseCost,e.isSpecial&&(this.isSpecial=!0),this.level=0,e.hacking_mult&&(this.mults.hacking_mult=e.hacking_mult),e.strength_mult&&(this.mults.strength_mult=e.strength_mult),e.defense_mult&&(this.mults.defense_mult=e.defense_mult),e.dexterity_mult&&(this.mults.dexterity_mult=e.dexterity_mult),e.agility_mult&&(this.mults.agility_mult=e.agility_mult),e.charisma_mult&&(this.mults.charisma_mult=e.charisma_mult),e.hacking_exp_mult&&(this.mults.hacking_exp_mult=e.hacking_exp_mult),e.strength_exp_mult&&(this.mults.strength_exp_mult=e.strength_exp_mult),e.defense_exp_mult&&(this.mults.defense_exp_mult=e.defense_exp_mult),e.dexterity_exp_mult&&(this.mults.dexterity_exp_mult=e.dexterity_exp_mult),e.agility_exp_mult&&(this.mults.agility_exp_mult=e.agility_exp_mult),e.charisma_exp_mult&&(this.mults.charisma_exp_mult=e.charisma_exp_mult),e.hacking_chance_mult&&(this.mults.hacking_chance_mult=e.hacking_chance_mult),e.hacking_speed_mult&&(this.mults.hacking_speed_mult=e.hacking_speed_mult),e.hacking_money_mult&&(this.mults.hacking_money_mult=e.hacking_money_mult),e.hacking_grow_mult&&(this.mults.hacking_grow_mult=e.hacking_grow_mult),e.company_rep_mult&&(this.mults.company_rep_mult=e.company_rep_mult),e.faction_rep_mult&&(this.mults.faction_rep_mult=e.faction_rep_mult),e.crime_money_mult&&(this.mults.crime_money_mult=e.crime_money_mult),e.crime_success_mult&&(this.mults.crime_success_mult=e.crime_success_mult),e.work_money_mult&&(this.mults.work_money_mult=e.work_money_mult),e.hacknet_node_money_mult&&(this.mults.hacknet_node_money_mult=e.hacknet_node_money_mult),e.hacknet_node_purchase_cost_mult&&(this.mults.hacknet_node_purchase_cost_mult=e.hacknet_node_purchase_cost_mult),e.hacknet_node_ram_cost_mult&&(this.mults.hacknet_node_ram_cost_mult=e.hacknet_node_ram_cost_mult),e.hacknet_node_core_cost_mult&&(this.mults.hacknet_node_core_cost_mult=e.hacknet_node_core_cost_mult),e.hacknet_node_level_cost_mult&&(this.mults.hacknet_node_level_cost_mult=e.hacknet_node_level_cost_mult),e.bladeburner_max_stamina_mult&&(this.mults.bladeburner_max_stamina_mult=e.bladeburner_max_stamina_mult),e.bladeburner_stamina_gain_mult&&(this.mults.bladeburner_stamina_gain_mult=e.bladeburner_stamina_gain_mult),e.bladeburner_analysis_mult&&(this.mults.bladeburner_analysis_mult=e.bladeburner_analysis_mult),e.bladeburner_success_chance_mult&&(this.mults.bladeburner_success_chance_mult=e.bladeburner_success_chance_mult)}static fromJSON(e){return o.Generic_fromJSON(s,e.data)}addToFactions(e){for(let t=0;t{switch(typeof e){case"number":return e;case"object":return s.getRandomInt(e.min,e.max);default:throw Error(`Do not know how to convert the type '${typeof e}' to a number`)}};for(const e of i.serverMetadata){const i={hostname:e.hostname,ip:u(),numOpenPortsRequired:e.numOpenPortsRequired,organizationName:e.organizationName};void 0!==e.maxRamExponent&&(i.maxRam=Math.pow(2,o(e.maxRamExponent)));for(const t of n)void 0!==e[t]&&(i[t]=o(e[t]));const s=new r.Server(i);for(const t of e.literature||[])s.messages.push(t);void 0!==e.specialName&&a.SpecialServerIps.addIp(e.specialName,s.ip),p(s),void 0!==e.networkLayer&&t[o(e.networkLayer)-1].push(s)}const l=(e,t)=>{e.serversOnNetwork.push(t.ip),t.serversOnNetwork.push(e.ip)},c=e=>e[Math.floor(Math.random()*e.length)],h=(e,t)=>{for(const n of e)l(n,t())};h(t[0],()=>e);for(let e=1;ec(t[e-1]))},t.prestigeAllServers=function(){for(var e in t.AllServers)delete t.AllServers[e];t.AllServers={}},t.loadAllServers=function(e){t.AllServers=JSON.parse(e,l.Reviver)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(21),a=n(106),i=n(20),o=n(8),s=n(30),l=n(149);function c(e,t,n){let r=1+(o.CONSTANTS.ServerBaseGrowthRate-1)/e.hackDifficulty;r>o.CONSTANTS.ServerMaxGrowthRate&&(r=o.CONSTANTS.ServerMaxGrowthRate);const a=e.serverGrowth/100;return Math.log(t)/(Math.log(r)*n.hacking_grow_mult*a*i.BitNodeMultipliers.ServerGrowthRate)}function u(e){for(var t in r.AllServers)if(r.AllServers.hasOwnProperty(t)&&r.AllServers[t].hostname==e)return r.AllServers[t];return null}t.safetlyCreateUniqueServer=function(e){if(null!=e.ip&&r.ipExists(e.ip)&&(e.ip=r.createUniqueRandomIp()),null!=u(e.hostname)){let t=e.hostname;for(let n=0;n<200&&null!=u(t=`${e.hostname}-${n}`);++n);e.hostname=t}return new a.Server(e)},t.numCycleForGrowth=c,t.processSingleServerGrowth=function(e,t,n){const r=Math.max(Math.floor(t/450),0);var a=1+(o.CONSTANTS.ServerBaseGrowthRate-1)/e.hackDifficulty;a>o.CONSTANTS.ServerMaxGrowthRate&&(a=o.CONSTANTS.ServerMaxGrowthRate);const s=r*(e.serverGrowth/100)*i.BitNodeMultipliers.ServerGrowthRate;let l=Math.pow(a,s*n.hacking_grow_mult);l<1&&(console.log("WARN: serverGrowth calculated to be less than 1"),l=1);const u=e.moneyAvailable;if(e.moneyAvailable*=l,e.moneyMax&&isNaN(e.moneyAvailable)&&(e.moneyAvailable=e.moneyMax),e.moneyMax&&e.moneyAvailable>e.moneyMax&&(e.moneyAvailable=e.moneyMax),u!==e.moneyAvailable){let t=c(e,e.moneyAvailable/u,n);t=Math.max(0,t),e.fortify(2*o.CONSTANTS.ServerFortifyAmount*Math.ceil(t))}return e.moneyAvailable/u},t.prestigeHomeComputer=function(e){const t=e.programs.includes(s.Programs.BitFlume.name);e.programs.length=0,e.runningScripts=[],e.serversOnNetwork=[],e.isConnectedTo=!0,e.ramUsed=0,e.programs.push(s.Programs.NukeProgram.name),t&&e.programs.push(s.Programs.BitFlume.name),e.scripts.forEach(function(t){t.updateRamUsage(e.scripts)}),e.messages.length=0,e.messages.push("hackers-starting-handbook.lit")},t.GetServerByHostname=u,t.getServer=function(e){return l.isValidIPAddress(e)?void 0!==r.AllServers[e]?r.AllServers[e]:null:u(e)},t.getServerOnNetwork=function(e,t){if(!(t>e.serversOnNetwork.length))return r.AllServers[e.serversOnNetwork[t]];console.error("Tried to get server on network that was out of range")}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.EmployeePositions={Operations:"Operations",Engineer:"Engineer",Business:"Business",Management:"Management",RandD:"Research & Development",Training:"Training",Unassigned:"Unassigned"}},function(e,t,n){"use strict";n.d(t,"h",function(){return v}),n.d(t,"m",function(){return E}),n.d(t,"i",function(){return k}),n.d(t,"b",function(){return C}),n.d(t,"c",function(){return P}),n.d(t,"f",function(){return O}),n.d(t,"g",function(){return T}),n.d(t,"e",function(){return S}),n.d(t,"d",function(){return M}),n.d(t,"o",function(){return x}),n.d(t,"p",function(){return w}),n.d(t,"l",function(){return A}),n.d(t,"k",function(){return R}),n.d(t,"q",function(){return N}),n.d(t,"a",function(){return I}),n.d(t,"j",function(){return D}),n.d(t,"r",function(){return L}),n.d(t,"n",function(){return W});var r=n(54),a=n(35),i=n(108),o=n(139),s=n(118),l=n(40),c=n(0),u=n(21),p=n(22),h=n(17),m=n(12),d=(n(92),n(1)),g=n.n(d),_=n(64),y=n.n(_),f=n(170);let b;function v(){return 9===c.Player.bitNodeN||h.SourceFileFlags[9]>0}function E(){if(l.a.isRunning){if(l.a.currStep!==l.d.HacknetNodesIntroduction)return;Object(l.b)()}const e=c.Player.hacknetNodes.length;if(v()){const t=P();if(isNaN(t))throw new Error("Calculated cost of purchasing HacknetServer is NaN");if(!c.Player.canAfford(t))return-1;c.Player.loseMoney(t);c.Player.createHacknetServer();return L(),e}{const t=C();if(isNaN(t))throw new Error("Calculated cost of purchasing HacknetNode is NaN");if(!c.Player.canAfford(t))return-1;const n="hacknet-node-"+e,a=new r.HacknetNode(n,c.Player.hacknet_node_money_mult);return c.Player.loseMoney(t),c.Player.hacknetNodes.push(a),e}}function k(){return v()&&c.Player.hacknetNodes.length>=a.MaxNumberHacknetServers}function C(){const e=c.Player.hacknetNodes.length,t=r.HacknetNodePurchaseNextMult;return r.BaseCostForHacknetNode*Math.pow(t,e)*c.Player.hacknet_node_purchase_cost_mult}function P(){const e=c.Player.hacknetNodes.length,t=a.HacknetServerPurchaseMult;return e>a.MaxNumberHacknetServers?1/0:a.BaseCostForHacknetServer*Math.pow(t,e)*c.Player.hacknet_node_purchase_cost_mult}function O(e,t){if(null==t)throw new Error("getMaxNumberLevelUpgrades() called without maxLevel arg");if(c.Player.money.lt(e.calculateLevelUpgradeCost(1,c.Player.hacknet_node_level_cost_mult)))return 0;let n=1,r=t-1,a=t-e.level;if(c.Player.money.gt(e.calculateLevelUpgradeCost(a,c.Player.hacknet_node_level_cost_mult)))return a;for(;n<=r;){var i=(n+r)/2|0;if(i!==t&&c.Player.money.gt(e.calculateLevelUpgradeCost(i,c.Player.hacknet_node_level_cost_mult))&&c.Player.money.lt(e.calculateLevelUpgradeCost(i+1,c.Player.hacknet_node_level_cost_mult)))return Math.min(a,i);if(c.Player.money.lt(e.calculateLevelUpgradeCost(i,c.Player.hacknet_node_level_cost_mult)))r=i-1;else{if(!c.Player.money.gt(e.calculateLevelUpgradeCost(i,c.Player.hacknet_node_level_cost_mult)))return Math.min(a,i);n=i+1}}return 0}function T(e,t){if(null==t)throw new Error("getMaxNumberRamUpgrades() called without maxLevel arg");if(c.Player.money.lt(e.calculateRamUpgradeCost(1,c.Player.hacknet_node_ram_cost_mult)))return 0;let n;if(n=e instanceof a.HacknetServer?Math.round(Math.log2(t/e.maxRam)):Math.round(Math.log2(t/e.ram)),c.Player.money.gt(e.calculateRamUpgradeCost(n,c.Player.hacknet_node_ram_cost_mult)))return n;for(let t=n-1;t>=0;--t)if(c.Player.money.gt(e.calculateRamUpgradeCost(t,c.Player.hacknet_node_ram_cost_mult)))return t;return 0}function S(e,t){if(null==t)throw new Error("getMaxNumberCoreUpgrades() called without maxLevel arg");if(c.Player.money.lt(e.calculateCoreUpgradeCost(1,c.Player.hacknet_node_core_cost_mult)))return 0;let n=1,r=t-1;const a=t-e.cores;if(c.Player.money.gt(e.calculateCoreUpgradeCost(a,c.Player.hacknet_node_core_cost_mult)))return a;for(;n<=r;){let i=(n+r)/2|0;if(i!=t&&c.Player.money.gt(e.calculateCoreUpgradeCost(i,c.Player.hacknet_node_core_cost_mult))&&c.Player.money.lt(e.calculateCoreUpgradeCost(i+1,c.Player.hacknet_node_core_cost_mult)))return Math.min(a,i);if(c.Player.money.lt(e.calculateCoreUpgradeCost(i,c.Player.hacknet_node_core_cost_mult)))r=i-1;else{if(!c.Player.money.gt(e.calculateCoreUpgradeCost(i,c.Player.hacknet_node_core_cost_mult)))return Math.min(a,i);n=i+1}}return 0}function M(e,t){if(null==t)throw new Error("getMaxNumberCacheUpgrades() called without maxLevel arg");if(!c.Player.canAfford(e.calculateCacheUpgradeCost(1)))return 0;let n=1,r=t-1;const a=t-e.cache;if(c.Player.canAfford(e.calculateCacheUpgradeCost(a)))return a;for(;n<=r;){let i=(n+r)/2|0;if(i!=t&&c.Player.canAfford(e.calculateCacheUpgradeCost(i))&&!c.Player.canAfford(e.calculateCacheUpgradeCost(i+1)))return Math.min(a,i);if(c.Player.canAfford(e.calculateCacheUpgradeCost(i))){if(!c.Player.canAfford(e.calculateCacheUpgradeCost(i)))return Math.min(a,i);n=i+1}else r=i-1}return 0}function x(e,t=1){const n=Math.round(t),i=e.calculateLevelUpgradeCost(n,c.Player.hacknet_node_level_cost_mult);if(isNaN(i)||i<=0||n<0)return!1;const o=e instanceof a.HacknetServer;if(e.level>=(o?a.HacknetServerMaxLevel:r.HacknetNodeMaxLevel))return!1;if(e.level+n>(o?a.HacknetServerMaxLevel:r.HacknetNodeMaxLevel)){return x(e,Math.max(0,(o?a.HacknetServerMaxLevel:r.HacknetNodeMaxLevel)-e.level))}return!!c.Player.canAfford(i)&&(c.Player.loseMoney(i),e.upgradeLevel(n,c.Player.hacknet_node_money_mult),!0)}function w(e,t=1){const n=Math.round(t),i=e.calculateRamUpgradeCost(n,c.Player.hacknet_node_ram_cost_mult);if(isNaN(i)||i<=0||n<0)return!1;const o=e instanceof a.HacknetServer;if(e.ram>=(o?a.HacknetServerMaxRam:r.HacknetNodeMaxRam))return!1;if(o){if(e.maxRam*Math.pow(2,n)>a.HacknetServerMaxRam){return w(e,Math.max(0,Math.log2(Math.round(a.HacknetServerMaxRam/e.maxRam))))}}else if(e.ram*Math.pow(2,n)>r.HacknetNodeMaxRam){return w(e,Math.max(0,Math.log2(Math.round(r.HacknetNodeMaxRam/e.ram))))}return!!c.Player.canAfford(i)&&(c.Player.loseMoney(i),e.upgradeRam(n,c.Player.hacknet_node_money_mult),!0)}function A(e,t=1){const n=Math.round(t),i=e.calculateCoreUpgradeCost(n,c.Player.hacknet_node_core_cost_mult);if(isNaN(i)||i<=0||n<0)return!1;const o=e instanceof a.HacknetServer;if(e.cores>=(o?a.HacknetServerMaxCores:r.HacknetNodeMaxCores))return!1;if(e.cores+n>(o?a.HacknetServerMaxCores:r.HacknetNodeMaxCores)){return A(e,Math.max(0,(o?a.HacknetServerMaxCores:r.HacknetNodeMaxCores)-e.cores))}return!!c.Player.canAfford(i)&&(c.Player.loseMoney(i),e.upgradeCore(n,c.Player.hacknet_node_money_mult),!0)}function R(e,t=1){const n=Math.round(t),r=e.calculateCacheUpgradeCost(n);if(isNaN(r)||r<=0||n<0)return!1;if(!(e instanceof a.HacknetServer))return console.warn("purchaseCacheUpgrade() called for a non-HacknetNode"),!1;if(e.cache+n>a.HacknetServerMaxCache){return R(e,Math.max(0,a.HacknetServerMaxCache-e.cache))}return!!c.Player.canAfford(r)&&(c.Player.loseMoney(r),e.upgradeCache(n),!0)}function N(){m.routing.isOn(m.Page.HacknetNodes)&&y.a.render(g.a.createElement(f.a,null),b)}function I(){b instanceof HTMLElement&&y.a.unmountComponentAtNode(b),b.style.display="none"}function D(e){return 0===c.Player.hacknetNodes.length?0:v()?function(e){if(!(c.Player.hashManager instanceof i.HashManager))throw new Error("Player does not have a HashManager (should be in 'hashManager' prop)");let t=0;for(let n=0;n{!function(e){null!=t.Companies[e.name]&&console.warn(`Duplicate Company Position being defined: ${e.name}`),t.Companies[e.name]=new a.Company(e)}(e)});for(const n in t.Companies){const r=t.Companies[n];e[n]instanceof a.Company?(r.favor=e[n].favor,isNaN(r.favor)&&(r.favor=0)):r.favor=0}},t.loadCompanies=function(e){t.Companies=JSON.parse(e,i.Reviver)},t.companyExists=function(e){return t.Companies.hasOwnProperty(e)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(667),a=n(2);t.Industries={Energy:"Energy",Utilities:"Water Utilities",Agriculture:"Agriculture",Fishing:"Fishing",Mining:"Mining",Food:"Food",Tobacco:"Tobacco",Chemical:"Chemical",Pharmaceutical:"Pharmaceutical",Computer:"Computer Hardware",Robotics:"Robotics",Software:"Software",Healthcare:"Healthcare",RealEstate:"RealEstate"},t.IndustryStartingCosts={Energy:225e9,Utilities:15e10,Agriculture:4e10,Fishing:8e10,Mining:3e11,Food:1e10,Tobacco:2e10,Chemical:7e10,Pharmaceutical:2e11,Computer:5e11,Robotics:1e12,Software:25e9,Healthcare:75e10,RealEstate:6e11},t.IndustryDescriptions={Energy:"Engage in the production and distribution of energy.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Energy,"$0.000a")+"
    Recommended starting Industry: NO",Utilities:"Distributes water and provides wastewater services.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Utilities,"$0.000a")+"
    Recommended starting Industry: NO",Agriculture:"Cultive crops and breed livestock to produce food.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Agriculture,"$0.000a")+"
    Recommended starting Industry: YES",Fishing:"Produce food through the breeding and processing of fish and fish products

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Fishing,"$0.000a")+"
    Recommended starting Industry: NO",Mining:"Extract and process metals from the earth.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Mining,"$0.000a")+"
    Recommended starting Industry: NO",Food:"Create your own restaurants all around the world.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Food,"$0.000a")+"
    Recommended starting Industry: YES",Tobacco:"Create and distribute tobacco and tobacco-related products.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Tobacco,"$0.000a")+"
    Recommended starting Industry: YES",Chemical:"Product industrial chemicals

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Chemical,"$0.000a")+"
    Recommended starting Industry: NO",Pharmaceutical:"Discover, develop, and create new pharmaceutical drugs.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Pharmaceutical,"$0.000a")+"
    Recommended starting Industry: NO",Computer:"Develop and manufacture new computer hardware and networking infrastructures.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Computer,"$0.000a")+"
    Recommended starting Industry: NO",Robotics:"Develop and create robots.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Robotics,"$0.000a")+"
    Recommended starting Industry: NO",Software:"Develop computer software and create AI Cores.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Software,"$0.000a")+"
    Recommended starting Industry: YES",Healthcare:"Create and manage hospitals.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.Healthcare,"$0.000a")+"
    Recommended starting Industry: NO",RealEstate:"Develop and manage real estate properties.

    Starting cost: "+a.numeralWrapper.format(t.IndustryStartingCosts.RealEstate,"$0.000a")+"
    Recommended starting Industry: NO"},t.IndustryResearchTrees={Energy:r.getBaseResearchTreeCopy(),Utilities:r.getBaseResearchTreeCopy(),Agriculture:r.getBaseResearchTreeCopy(),Fishing:r.getBaseResearchTreeCopy(),Mining:r.getBaseResearchTreeCopy(),Food:r.getProductIndustryResearchTreeCopy(),Tobacco:r.getProductIndustryResearchTreeCopy(),Chemical:r.getBaseResearchTreeCopy(),Pharmaceutical:r.getProductIndustryResearchTreeCopy(),Computer:r.getProductIndustryResearchTreeCopy(),Robotics:r.getProductIndustryResearchTreeCopy(),Software:r.getProductIndustryResearchTreeCopy(),Healthcare:r.getProductIndustryResearchTreeCopy(),RealEstate:r.getProductIndustryResearchTreeCopy()},t.resetIndustryResearchTrees=function(){t.IndustryResearchTrees.Energy=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Utilities=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Agriculture=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Fishing=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Mining=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Food=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Tobacco=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Chemical=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Pharmaceutical=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Computer=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Robotics=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Software=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.Healthcare=r.getBaseResearchTreeCopy(),t.IndustryResearchTrees.RealEstate=r.getBaseResearchTreeCopy()}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(92);t.clearEventListeners=function(e){try{let t;const n=(t="string"==typeof e?r.getElementById(e):e).cloneNode(!0);return null!==t.parentNode&&t.parentNode.replaceChild(n,t),n}catch(e){return console.error(e),null}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Aevum="Aevum",e.Chongqing="Chongqing",e.Ishima="Ishima",e.NewTokyo="New Tokyo",e.Sector12="Sector-12",e.Volhaven="Volhaven"}(t.CityName||(t.CityName={}))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(92),a=n(77);t.removeElementById=function(e){try{const t=r.getElementById(e);a.removeElement(t)}catch(e){}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(630),a=n(629);t.Programs={};for(const e of a.programsMetadata)t.Programs[e.key]=new r.Program(e.name,e.create)},function(module,__webpack_exports__,__webpack_require__){"use strict";(function($){__webpack_require__.d(__webpack_exports__,"f",function(){return IssueNewSharesCooldown}),__webpack_require__.d(__webpack_exports__,"k",function(){return SellSharesCooldown}),__webpack_require__.d(__webpack_exports__,"m",function(){return WarehouseInitialCost}),__webpack_require__.d(__webpack_exports__,"n",function(){return WarehouseInitialSize}),__webpack_require__.d(__webpack_exports__,"o",function(){return WarehouseUpgradeBaseCost}),__webpack_require__.d(__webpack_exports__,"g",function(){return OfficeInitialCost}),__webpack_require__.d(__webpack_exports__,"h",function(){return OfficeInitialSize}),__webpack_require__.d(__webpack_exports__,"a",function(){return BribeThreshold}),__webpack_require__.d(__webpack_exports__,"b",function(){return BribeToRepRatio}),__webpack_require__.d(__webpack_exports__,"j",function(){return ProductProductionCostRatio}),__webpack_require__.d(__webpack_exports__,"d",function(){return DividendMaxPercentage}),__webpack_require__.d(__webpack_exports__,"c",function(){return Corporation}),__webpack_require__.d(__webpack_exports__,"e",function(){return Industry}),__webpack_require__.d(__webpack_exports__,"i",function(){return OfficeSpace});var _CorporationState__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(204),_CorporationState__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(_CorporationState__WEBPACK_IMPORTED_MODULE_0__),_data_CorporationUnlockUpgrades__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(173),_data_CorporationUnlockUpgrades__WEBPACK_IMPORTED_MODULE_1___default=__webpack_require__.n(_data_CorporationUnlockUpgrades__WEBPACK_IMPORTED_MODULE_1__),_data_CorporationUpgrades__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(172),_data_CorporationUpgrades__WEBPACK_IMPORTED_MODULE_2___default=__webpack_require__.n(_data_CorporationUpgrades__WEBPACK_IMPORTED_MODULE_2__),_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(23),_EmployeePositions__WEBPACK_IMPORTED_MODULE_3___default=__webpack_require__.n(_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__),_IndustryData__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(26),_IndustryData__WEBPACK_IMPORTED_MODULE_4___default=__webpack_require__.n(_IndustryData__WEBPACK_IMPORTED_MODULE_4__),_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(153),_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5___default=__webpack_require__.n(_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5__),_Material__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(150),_Material__WEBPACK_IMPORTED_MODULE_6___default=__webpack_require__.n(_Material__WEBPACK_IMPORTED_MODULE_6__),_MaterialSizes__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__(94),_MaterialSizes__WEBPACK_IMPORTED_MODULE_7___default=__webpack_require__.n(_MaterialSizes__WEBPACK_IMPORTED_MODULE_7__),_Product__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__(131),_Product__WEBPACK_IMPORTED_MODULE_8___default=__webpack_require__.n(_Product__WEBPACK_IMPORTED_MODULE_8__),_ResearchMap__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__(165),_ResearchMap__WEBPACK_IMPORTED_MODULE_9___default=__webpack_require__.n(_ResearchMap__WEBPACK_IMPORTED_MODULE_9__),_Warehouse__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__(90),_Warehouse__WEBPACK_IMPORTED_MODULE_10___default=__webpack_require__.n(_Warehouse__WEBPACK_IMPORTED_MODULE_10__);__webpack_require__.d(__webpack_exports__,"l",function(){return _Warehouse__WEBPACK_IMPORTED_MODULE_10__.Warehouse});var _BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11__=__webpack_require__(20),_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11___default=__webpack_require__.n(_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11__),_Constants__WEBPACK_IMPORTED_MODULE_12__=__webpack_require__(8),_Constants__WEBPACK_IMPORTED_MODULE_12___default=__webpack_require__.n(_Constants__WEBPACK_IMPORTED_MODULE_12__),_Faction_Factions__WEBPACK_IMPORTED_MODULE_13__=__webpack_require__(13),_Faction_Factions__WEBPACK_IMPORTED_MODULE_13___default=__webpack_require__.n(_Faction_Factions__WEBPACK_IMPORTED_MODULE_13__),_Literature__WEBPACK_IMPORTED_MODULE_14__=__webpack_require__(130),_Locations_Cities__WEBPACK_IMPORTED_MODULE_15__=__webpack_require__(107),_Locations_Cities__WEBPACK_IMPORTED_MODULE_15___default=__webpack_require__.n(_Locations_Cities__WEBPACK_IMPORTED_MODULE_15__),_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__=__webpack_require__(28),_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16___default=__webpack_require__.n(_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__),_Player__WEBPACK_IMPORTED_MODULE_17__=__webpack_require__(0),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__=__webpack_require__(2),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18___default=__webpack_require__.n(_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_19__=__webpack_require__(12),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_19___default=__webpack_require__.n(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_19__),_utils_calculateEffectWithFactors__WEBPACK_IMPORTED_MODULE_20__=__webpack_require__(298),_utils_calculateEffectWithFactors__WEBPACK_IMPORTED_MODULE_20___default=__webpack_require__.n(_utils_calculateEffectWithFactors__WEBPACK_IMPORTED_MODULE_20__),_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__=__webpack_require__(9),_utils_uiHelpers_clearSelector__WEBPACK_IMPORTED_MODULE_22__=__webpack_require__(197),_utils_uiHelpers_clearSelector__WEBPACK_IMPORTED_MODULE_22___default=__webpack_require__.n(_utils_uiHelpers_clearSelector__WEBPACK_IMPORTED_MODULE_22__),_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_23__=__webpack_require__(18),_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_24__=__webpack_require__(81),_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_24___default=__webpack_require__.n(_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_24__),_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__=__webpack_require__(3),_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25___default=__webpack_require__.n(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__),_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_26__=__webpack_require__(36),_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_26___default=__webpack_require__.n(_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_26__),_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_27__=__webpack_require__(58),_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_27___default=__webpack_require__.n(_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_27__),_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__=__webpack_require__(5),_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28___default=__webpack_require__.n(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__),_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__=__webpack_require__(15),_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29___default=__webpack_require__.n(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__),_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_30__=__webpack_require__(51),_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_30___default=__webpack_require__.n(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_30__),_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_31__=__webpack_require__(32),_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_31___default=__webpack_require__.n(_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_31__),_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_32__=__webpack_require__(77),_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_32___default=__webpack_require__.n(_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_32__),_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33__=__webpack_require__(29),_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33___default=__webpack_require__.n(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33__),_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__=__webpack_require__(46),_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34___default=__webpack_require__.n(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__),react__WEBPACK_IMPORTED_MODULE_35__=__webpack_require__(1),react__WEBPACK_IMPORTED_MODULE_35___default=__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_35__),react_dom__WEBPACK_IMPORTED_MODULE_36__=__webpack_require__(64),react_dom__WEBPACK_IMPORTED_MODULE_36___default=__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_36__),_ui_CorporationUIEventHandler__WEBPACK_IMPORTED_MODULE_37__=__webpack_require__(297),_ui_Root__WEBPACK_IMPORTED_MODULE_38__=__webpack_require__(296),_ui_Routing__WEBPACK_IMPORTED_MODULE_39__=__webpack_require__(137),_ui_Routing__WEBPACK_IMPORTED_MODULE_39___default=__webpack_require__.n(_ui_Routing__WEBPACK_IMPORTED_MODULE_39__),decimal_js__WEBPACK_IMPORTED_MODULE_40__=__webpack_require__(45);const INITIALSHARES=1e9,SHARESPERPRICEUPDATE=1e6,IssueNewSharesCooldown=216e3,SellSharesCooldown=18e3,CyclesPerMarketCycle=50,CyclesPerIndustryStateCycle=CyclesPerMarketCycle/_CorporationState__WEBPACK_IMPORTED_MODULE_0__.AllCorporationStates.length,SecsPerMarketCycle=CyclesPerMarketCycle/5,Cities=["Aevum","Chongqing","Sector-12","New Tokyo","Ishima","Volhaven"],WarehouseInitialCost=5e9,WarehouseInitialSize=100,WarehouseUpgradeBaseCost=1e9,OfficeInitialCost=4e9,OfficeInitialSize=3,OfficeUpgradeBaseCost=1e9,BribeThreshold=1e14,BribeToRepRatio=1e9,ProductProductionCostRatio=5,DividendMaxPercentage=50,EmployeeSalaryMultiplier=3,CyclesPerEmployeeRaise=400,EmployeeRaiseAmount=50,BaseMaxProducts=3;let researchTreeBoxOpened=!1,researchTreeBox=null;$(document).mousedown(function(e){researchTreeBoxOpened&&null==$(e.target).closest("#corporation-research-popup-box-content").get(0)&&(Object(_utils_uiHelpers_removeElement__WEBPACK_IMPORTED_MODULE_32__.removeElement)(researchTreeBox),researchTreeBox=null,researchTreeBoxOpened=!1)});var empManualAssignmentModeActive=!1;function Industry(e={}){this.offices={[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Aevum]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Chongqing]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Sector12]:new OfficeSpace({loc:_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Sector12,size:OfficeInitialSize}),[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.NewTokyo]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Ishima]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Volhaven]:0},this.name=e.name?e.name:0,this.type=e.type?e.type:0,this.sciResearch=new _Material__WEBPACK_IMPORTED_MODULE_6__.Material({name:"Scientific Research"}),this.researched={},this.reqMats={},this.prodMats=[],this.products={},this.makesProducts=!1,this.awareness=0,this.popularity=0,this.startingCost=0,this.reFac=0,this.sciFac=0,this.hwFac=0,this.robFac=0,this.aiFac=0,this.advFac=0,this.prodMult=0,this.lastCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.lastCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.thisCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.thisCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0);var t=Object.keys(_IndustryUpgrades__WEBPACK_IMPORTED_MODULE_5__.IndustryUpgrades).length;this.upgrades=Array(t).fill(0),this.state="START",this.newInd=!0,this.warehouses={[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Aevum]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Chonqing]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Sector12]:new _Warehouse__WEBPACK_IMPORTED_MODULE_10__.Warehouse({corp:e.corp,industry:this,loc:_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Sector12,size:WarehouseInitialSize}),[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.NewTokyo]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Ishima]:0,[_Locations_data_CityNames__WEBPACK_IMPORTED_MODULE_16__.CityName.Volhaven]:0},this.init()}function Employee(e={}){if(!(this instanceof Employee))return new Employee(e);this.name=e.name?e.name:"Bobby",this.mor=e.morale?e.morale:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),this.hap=e.happiness?e.happiness:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),this.ene=e.energy?e.energy:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),this.int=e.intelligence?e.intelligence:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(10,50),this.cha=e.charisma?e.charisma:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(10,50),this.exp=e.experience?e.experience:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(10,50),this.cre=e.creativity?e.creativity:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(10,50),this.eff=e.efficiency?e.efficiency:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(10,50),this.sal=e.salary?e.salary:Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(.1,5),this.pro=0,this.cyclesUntilRaise=CyclesPerEmployeeRaise,this.loc=e.loc?e.loc:"",this.pos=_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Unassigned}Industry.prototype.init=function(){switch(this.startingCost=_IndustryData__WEBPACK_IMPORTED_MODULE_4__.IndustryStartingCosts[this.type],this.type){case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Energy:this.reFac=.65,this.sciFac=.7,this.robFac=.05,this.aiFac=.3,this.advFac=.08,this.reqMats={Hardware:.1,Metal:.2},this.prodMats=["Energy"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Utilities:case"Utilities":this.reFac=.5,this.sciFac=.6,this.robFac=.4,this.aiFac=.4,this.advFac=.08,this.reqMats={Hardware:.1,Metal:.1},this.prodMats=["Water"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Agriculture:this.reFac=.72,this.sciFac=.5,this.hwFac=.2,this.robFac=.3,this.aiFac=.3,this.advFac=.04,this.reqMats={Water:.5,Energy:.5},this.prodMats=["Plants","Food"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Fishing:this.reFac=.15,this.sciFac=.35,this.hwFac=.35,this.robFac=.5,this.aiFac=.2,this.advFac=.08,this.reqMats={Energy:.5},this.prodMats=["Food"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Mining:this.reFac=.3,this.sciFac=.26,this.hwFac=.4,this.robFac=.45,this.aiFac=.45,this.advFac=.06,this.reqMats={Energy:.8},this.prodMats=["Metal"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Food:this.sciFac=.12,this.hwFac=.15,this.robFac=.3,this.aiFac=.25,this.advFac=.25,this.reFac=.05,this.reqMats={Food:.5,Water:.5,Energy:.2},this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Tobacco:this.reFac=.15,this.sciFac=.75,this.hwFac=.15,this.robFac=.2,this.aiFac=.15,this.advFac=.2,this.reqMats={Plants:1,Water:.2},this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Chemical:this.reFac=.25,this.sciFac=.75,this.hwFac=.2,this.robFac=.25,this.aiFac=.2,this.advFac=.07,this.reqMats={Plants:1,Energy:.5,Water:.5},this.prodMats=["Chemicals"];break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Pharmaceutical:this.reFac=.05,this.sciFac=.8,this.hwFac=.15,this.robFac=.25,this.aiFac=.2,this.advFac=.16,this.reqMats={Chemicals:2,Energy:1,Water:.5},this.prodMats=["Drugs"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Computer:case"Computer":this.reFac=.2,this.sciFac=.62,this.robFac=.36,this.aiFac=.19,this.advFac=.17,this.reqMats={Metal:2,Energy:1},this.prodMats=["Hardware"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Robotics:this.reFac=.32,this.sciFac=.65,this.aiFac=.36,this.advFac=.18,this.hwFac=.19,this.reqMats={Hardware:5,Energy:3},this.prodMats=["Robots"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Software:this.sciFac=.62,this.advFac=.16,this.hwFac=.25,this.reFac=.15,this.aiFac=.18,this.robFac=.05,this.reqMats={Hardware:.5,Energy:.5},this.prodMats=["AICores"],this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Healthcare:this.reFac=.1,this.sciFac=.75,this.advFac=.11,this.hwFac=.1,this.robFac=.1,this.aiFac=.1,this.reqMats={Robots:10,AICores:5,Energy:5,Water:5},this.makesProducts=!0;break;case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.RealEstate:this.robFac=.6,this.aiFac=.6,this.advFac=.25,this.sciFac=.05,this.hwFac=.05,this.reqMats={Metal:5,Energy:5,Water:2,Hardware:4},this.prodMats=["RealEstate"],this.makesProducts=!0;break;default:return void console.log("ERR: Invalid Industry Type passed into Industry.init(): "+this.type)}},Industry.prototype.getProductDescriptionText=function(){if(this.makesProducts)switch(this.type){case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Food:return"create and manage restaurants";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Tobacco:return"create tobacco and tobacco-related products";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Pharmaceutical:return"develop new pharmaceutical drugs";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Computer:case"Computer":return"create new computer hardware and networking infrastructures";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Robotics:return"build specialized robots and robot-related products";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Software:return"develop computer software";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.Healthcare:return"build and manage hospitals";case _IndustryData__WEBPACK_IMPORTED_MODULE_4__.Industries.RealEstate:return"develop and manage real estate properties";default:return console.log("ERROR: Invalid industry type in Industry.getProductDescriptionText"),""}},Industry.prototype.getMaximumNumberProducts=function(){if(!this.makesProducts)return 0;let e=0;return this.hasResearch("uPgrade: Capacity.I")&&++e,this.hasResearch("uPgrade: Capacity.II")&&++e,BaseMaxProducts+e},Industry.prototype.hasMaximumNumberProducts=function(){return Object.keys(this.products).length>=this.getMaximumNumberProducts()},Industry.prototype.calculateProductionFactors=function(){for(var e=0,t=0;t0&&(e.breakdown+=t+": "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(n.data[e.loc][0]*n.siz,0)+"
    ")}},Industry.prototype.process=function(e=1,t,n){if(this.state=t,"START"===t){(isNaN(this.thisCycleRevenue)||isNaN(this.thisCycleExpenses))&&(console.log("ERROR: NaN in Corporation's computed revenue/expenses"),console.log(this.thisCycleRevenue.toString()),console.log(this.thisCycleExpenses.toString()),Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"),this.thisCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.thisCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0)),this.lastCycleRevenue=this.thisCycleRevenue.dividedBy(e*SecsPerMarketCycle),this.lastCycleExpenses=this.thisCycleExpenses.dividedBy(e*SecsPerMarketCycle),this.thisCycleRevenue=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.thisCycleExpenses=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.lastCycleRevenue.gt(0)&&(this.newInd=!1);var r=0;for(var a in this.offices)this.offices[a]instanceof OfficeSpace&&(r+=this.offices[a].process(e,{industry:this,corporation:n}));this.thisCycleExpenses=this.thisCycleExpenses.plus(r),this.processMaterialMarket(e),this.processProductMarket(e),this.popularity-=1e-4*e,this.popularity=Math.max(0,this.popularity);var i=n.getDreamSenseGain(),o=4*i;return void(i>0&&(this.popularity+=i*e,this.awareness+=o*e))}let s=this.processMaterials(e,n);Array.isArray(s)&&(this.thisCycleRevenue=this.thisCycleRevenue.plus(s[0]),this.thisCycleExpenses=this.thisCycleExpenses.plus(s[1])),s=this.processProducts(e,n),Array.isArray(s)&&(this.thisCycleRevenue=this.thisCycleRevenue.plus(s[0]),this.thisCycleExpenses=this.thisCycleExpenses.plus(s[1]))},Industry.prototype.processMaterialMarket=function(e=1){for(var t=this.reqMats,n=this.prodMats,r=0;r0&&(a.qty+=r,expenses+=r*a.bCost)}(matName,industry),this.updateWarehouseSizeUsed(warehouse));break;case"PRODUCTION":if(warehouse.smartSupplyStore=0,this.prodMats.length>0){var mat=warehouse.materials[this.prodMats[0]],maxProd=this.getOfficeProductivity(office)*this.prodMult*company.getProductionMultiplier()*this.getProductionMultiplier();let e;e=mat.prdman[0]?Math.min(maxProd,mat.prdman[1]):maxProd,e*=SecsPerMarketCycle*marketCycles;var totalMatSize=0;for(let e=0;e0){var maxAmt=Math.floor((warehouse.size-warehouse.sizeUsed)/totalMatSize);e=Math.min(maxAmt,e)}e<0&&(e=0),warehouse.smartSupplyStore+=e/(SecsPerMarketCycle*marketCycles);var producableFrac=1;for(var reqMatName in this.reqMats)if(this.reqMats.hasOwnProperty(reqMatName)){var req=this.reqMats[reqMatName]*e;warehouse.materials[reqMatName].qty0&&e>0){for(const t in this.reqMats){var reqMatQtyNeeded=this.reqMats[t]*e*producableFrac;warehouse.materials[t].qty-=reqMatQtyNeeded,warehouse.materials[t].prd=0,warehouse.materials[t].prd-=reqMatQtyNeeded/(SecsPerMarketCycle*marketCycles)}for(let t=0;tmat.bCost?sCost-mat.bCost>markupLimit&&(markup=Math.pow(markupLimit/(sCost-mat.bCost),2)):sCost=0?(mat.qty-=sellAmt,revenue+=sellAmt*sCost,mat.sll=sellAmt/(SecsPerMarketCycle*marketCycles)):mat.sll=0}break;case"EXPORT":for(var matName in warehouse.materials)if(warehouse.materials.hasOwnProperty(matName)){var mat=warehouse.materials[matName];mat.totalExp=0;for(var expI=0;expI=expWarehouse.size)return[0,0];var maxAmt=Math.floor((expWarehouse.size-expWarehouse.sizeUsed)/_MaterialSizes__WEBPACK_IMPORTED_MODULE_7__.MaterialSizes[matName]);amt=Math.min(maxAmt,amt),expWarehouse.materials[matName].imp+=amt/(SecsPerMarketCycle*marketCycles),expWarehouse.materials[matName].qty+=amt,expWarehouse.materials[matName].qlt=mat.qlt,mat.qty-=amt,mat.totalExp+=amt,expIndustry.updateWarehouseSizeUsed(expWarehouse);break}}}mat.totalExp/=SecsPerMarketCycle*marketCycles}break;case"START":break;default:console.log("ERROR: Invalid state: "+this.state)}this.updateWarehouseSizeUsed(warehouse)}office instanceof OfficeSpace&&(this.sciResearch.qty+=.004*Math.pow(office.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.RandD],.5)*company.getScientificResearchMultiplier()*this.getScientificResearchMultiplier())}return[revenue,expenses]},Industry.prototype.processProducts=function(e=1,t){var n=0;if("PRODUCTION"===this.state)for(const t in this.products){const n=this.products[t];if(!n.fin){const t=n.createCity,r=this.offices[t],a=r.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Engineer],i=r.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Management],o=r.employeeProd[_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Operations],s=a+i+o;if(s<=0)break;const l=1+i/(1.2*s),c=(Math.pow(a,.34)+Math.pow(o,.2))*l;n.createProduct(e,c),n.prog>=100&&n.finishProduct(r.employeeProd,this);break}}for(var r in this.products)if(this.products.hasOwnProperty(r)){var a=this.products[r];a instanceof _Product__WEBPACK_IMPORTED_MODULE_8__.Product&&a.fin&&(n+=this.processProduct(e,a,t))}return[n,0]},Industry.prototype.processProduct=function(marketCycles=1,product,corporation){let totalProfit=0;for(let i=0;i0){var maxAmt=Math.floor((warehouse.size-warehouse.sizeUsed)/netStorageSize);prod=Math.min(maxAmt,prod)}warehouse.smartSupplyStore+=prod/(SecsPerMarketCycle*marketCycles);var producableFrac=1;for(var reqMatName in product.reqMats)if(product.reqMats.hasOwnProperty(reqMatName)){var req=product.reqMats[reqMatName]*prod;warehouse.materials[reqMatName].qty0&&prod>0){for(var reqMatName in product.reqMats)if(product.reqMats.hasOwnProperty(reqMatName)){var reqMatQtyNeeded=product.reqMats[reqMatName]*prod*producableFrac;warehouse.materials[reqMatName].qty-=reqMatQtyNeeded,warehouse.materials[reqMatName].prd-=reqMatQtyNeeded/(SecsPerMarketCycle*marketCycles)}product.data[city][0]+=prod*producableFrac}product.data[city][1]=prod*producableFrac/(SecsPerMarketCycle*marketCycles);break;case"SALE":for(var reqMatName in product.pCost=0,product.reqMats)product.reqMats.hasOwnProperty(reqMatName)&&(product.pCost+=product.reqMats[reqMatName]*warehouse.materials[reqMatName].bCost);product.pCost*=ProductProductionCostRatio;const businessFactor=this.getBusinessFactor(office),advertisingFactor=this.getAdvertisingFactors()[0],marketFactor=this.getMarketFactor(product),markupLimit=product.rat/product.mku;var sCost;if(product.marketTa2){const e=product.data[city][1],t=markupLimit,n=e,r=.5*Math.pow(product.rat,.65)*marketFactor*corporation.getSalesMultiplier()*businessFactor*advertisingFactor*this.getSalesMultiplier(),a=Math.sqrt(n/r);let i;0===r||0===a?0===n?i=0:(i=product.pCost+markupLimit,console.warn("In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost")):i=t/a+product.pCost,product.marketTa2Price[city]=i,sCost=i}else product.marketTa1?sCost=product.pCost+markupLimit:Object(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_30__.isString)(product.sCost)?(sCost=product.sCost.replace(/MP/g,product.pCost+product.rat/product.mku),sCost=eval(sCost)):sCost=product.sCost;var markup=1;sCost>product.pCost&&sCost-product.pCost>markupLimit&&(markup=markupLimit/(sCost-product.pCost));var maxSell=.5*Math.pow(product.rat,.65)*marketFactor*corporation.getSalesMultiplier()*Math.pow(markup,2)*businessFactor*advertisingFactor*this.getSalesMultiplier(),sellAmt;if(product.sllman[city][0]&&Object(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_30__.isString)(product.sllman[city][1])){var tmp=product.sllman[city][1].replace(/MAX/g,maxSell);tmp=tmp.replace(/PROD/g,product.data[city][1]);try{tmp=eval(tmp)}catch(e){Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("Error evaluating your sell price expression for "+product.name+" in "+this.name+"'s "+city+" office. Sell price is being set to MAX"),tmp=maxSell}sellAmt=Math.min(maxSell,tmp)}else sellAmt=product.sllman[city][0]&&product.sllman[city][1]>0?Math.min(maxSell,product.sllman[city][1]):!1===product.sllman[city][0]?0:maxSell;sellAmt<0&&(sellAmt=0),sellAmt=sellAmt*SecsPerMarketCycle*marketCycles,sellAmt=Math.min(product.data[city][0],sellAmt),sellAmt&&sCost?(product.data[city][0]-=sellAmt,totalProfit+=sellAmt*sCost,product.data[city][2]=sellAmt/(SecsPerMarketCycle*marketCycles)):product.data[city][2]=0;break;case"START":case"PURCHASE":case"EXPORT":break;default:console.log("ERROR: Invalid State: "+this.state)}}return totalProfit},Industry.prototype.discontinueProduct=function(e){for(var t in this.products)this.products.hasOwnProperty(t)&&e===this.products[t]&&delete this.products[t]},Industry.prototype.upgrade=function(e,t){var n=t.corporation,r=(t.division,t.office),a=e[0];for(e[1],e[2],e[3];this.upgrades.length<=a;)this.upgrades.push(0);switch(++this.upgrades[a],a){case 0:for(let e=0;e{if(this.sciResearch.qty>=n.cost)return this.sciResearch.qty-=n.cost,t.research(r[e]),this.researched[r[e]]=!0,Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)(`Researched ${r[e]}. It may take a market cycle `+`(~${SecsPerMarketCycle} seconds) before the effects of `+"the Research apply."),this.createResearchBox();Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)(`You do not have enough Scientific Research for ${n.name}`)}):console.warn(`Could not find Research Tree div for ${a}`)}const a=document.getElementById(`${e}-content`);null!=a&&(Object(_utils_uiHelpers_appendLineBreaks__WEBPACK_IMPORTED_MODULE_24__.appendLineBreaks)(a,2),a.appendChild(Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("pre",{display:"block",innerText:"Multipliers from research:\n"+` * Advertising Multiplier: x${t.getAdvertisingMultiplier()}\n`+` * Employee Charisma Multiplier: x${t.getEmployeeChaMultiplier()}\n`+` * Employee Creativity Multiplier: x${t.getEmployeeCreMultiplier()}\n`+` * Employee Efficiency Multiplier: x${t.getEmployeeEffMultiplier()}\n`+` * Employee Intelligence Multiplier: x${t.getEmployeeIntMultiplier()}\n`+` * Production Multiplier: x${t.getProductionMultiplier()}\n`+` * Sales Multiplier: x${t.getSalesMultiplier()}\n`+` * Scientific Research Multiplier: x${t.getScientificResearchMultiplier()}\n`+` * Storage Multiplier: x${t.getStorageMultiplier()}`})),a.appendChild(Object(_utils_uiHelpers_createPopupCloseButton__WEBPACK_IMPORTED_MODULE_27__.createPopupCloseButton)(researchTreeBox,{class:"std-button",display:"block",innerText:"Close"}))),researchTreeBoxOpened=!0},Industry.prototype.toJSON=function(){return Object(_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_23__.Generic_toJSON)("Industry",this)},Industry.fromJSON=function(e){return Object(_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_23__.Generic_fromJSON)(Industry,e.data)},_utils_JSONReviver__WEBPACK_IMPORTED_MODULE_23__.Reviver.constructors.Industry=Industry,Employee.prototype.process=function(e=1,t){var n=.003*e,r=n*Math.random();this.exp+=n,this.cyclesUntilRaise-=e,this.cyclesUntilRaise<=0&&(this.salary+=EmployeeRaiseAmount,this.cyclesUntilRaise+=CyclesPerEmployeeRaise);var a=n*Math.random();return this.pos===_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Training&&(this.cha+=a,this.exp+=a,this.eff+=a),this.ene-=r,this.hap-=r,this.eneHappiness: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(this.hap,3)+"
    Energy: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(this.ene,3)+"
    Intelligence: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(i,3)+"
    Charisma: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(a,3)+"
    Experience: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(this.exp,3)+"
    Creativity: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(r,3)+"
    Efficiency: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(o,3)+"
    Salary: "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(this.sal,"$0.000a")+"/ s
    "}));var s=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("select",{});for(var l in _EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions)_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.hasOwnProperty(l)&&s.add(Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("option",{text:_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions[l],value:_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions[l]}));s.addEventListener("change",()=>{this.pos=s.options[s.selectedIndex].value});for(var c=0;c=this.size},OfficeSpace.prototype.process=function(e=1,t){t.corporation;var n=t.industry;if(n.hasResearch("HRBuddy-Recruitment")&&!this.atCapacity()){const e=this.hireRandomEmployee();n.hasResearch("HRBuddy-Training")&&(e.pos=_EmployeePositions__WEBPACK_IMPORTED_MODULE_3__.EmployeePositions.Training)}this.maxEne=100,this.maxHap=100,this.maxMor=100,n.hasResearch("Go-Juice")&&(this.maxEne+=10),n.hasResearch("JoyWire")&&(this.maxHap+=10),n.hasResearch("Sti.mu")&&(this.maxMor+=10);var r=1;n.funds<0&&n.lastCycleRevenue<0?r=Math.pow(.99,e):n.funds>0&&n.lastCycleRevenue>0&&(r=Math.pow(1.01,e));const a=n.hasResearch("AutoBrew"),i=n.hasResearch("AutoPartyManager");var o=0;for(let t=0;tCharisma: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(t.cha,1)+"
    Experience: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(t.exp,1)+"
    Creativity: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(t.cre,1)+"
    Efficiency: "+Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.formatNumber)(t.eff,1)+"
    Salary: "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(t.sal,"$0.000a")+" s
    ",clickListener:()=>(n.hireEmployee(t,e),Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33__.removeElementById)("cmpy-mgmt-hire-employee-popup"),!1)})},g=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("a",{class:"a-link-button",innerText:"Cancel",float:"right",clickListener:()=>(Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33__.removeElementById)("cmpy-mgmt-hire-employee-popup"),!1)}),_=[m,d(u,this),d(p,this),d(h,this),g];Object(_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_26__.createPopup)("cmpy-mgmt-hire-employee-popup",_)}},OfficeSpace.prototype.hireEmployee=function(e,t){var n=t.corporation,r=(t.industry,Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoTxtInpBoxGetYesButton)()),a=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoTxtInpBoxGetNoButton)();r.innerHTML="Hire",a.innerHTML="Cancel",r.addEventListener("click",()=>{for(var t=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoTxtInpBoxGetInput)(),r=0;rObject(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoTxtInpBoxClose)()),Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoTxtInpBoxCreate)("Give your employee a nickname!")},OfficeSpace.prototype.hireRandomEmployee=function(){if(!this.atCapacity()&&null==document.getElementById("cmpy-mgmt-hire-employee-popup")){var e=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(76,100)/100,t=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),n=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),r=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),a=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),i=Object(_utils_helpers_getRandomInt__WEBPACK_IMPORTED_MODULE_29__.getRandomInt)(50,100),o=new Employee({intelligence:t*e,charisma:n*e,experience:r*e,creativity:a*e,efficiency:i*e,salary:EmployeeSalaryMultiplier*(t+n+r+a+i)*e}),s=Object(_utils_StringHelperFunctions__WEBPACK_IMPORTED_MODULE_28__.generateRandomString)(7);for(let e=0;e=CyclesPerIndustryStateCycle){const t=this.getState(),n=1,r=n*CyclesPerIndustryStateCycle;if(this.storedCycles-=r,this.divisions.forEach(function(r){r.process(n,t,e)}),this.shareSaleCooldown>0&&(this.shareSaleCooldown-=r),this.issueNewSharesCooldown>0&&(this.issueNewSharesCooldown-=r),"START"===t){this.revenue=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.expenses=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(0),this.divisions.forEach(e=>{e.lastCycleRevenue!==-1/0&&e.lastCycleRevenue!==1/0&&e.lastCycleExpenses!==-1/0&&e.lastCycleExpenses!==1/0&&(this.revenue=this.revenue.plus(e.lastCycleRevenue),this.expenses=this.expenses.plus(e.lastCycleExpenses))});const e=this.revenue.minus(this.expenses).times(n*SecsPerMarketCycle);if(isNaN(this.funds)&&(Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("There was an error calculating your Corporations funds and they got reset to 0. This is a bug. Please report to game developer.

    (Your funds have been set to $150b for the inconvenience)"),this.funds=new decimal_js__WEBPACK_IMPORTED_MODULE_40__.a(15e10)),this.dividendPercentage>0&&e>0)if(isNaN(this.dividendPercentage)||this.dividendPercentage<0||this.dividendPercentage>DividendMaxPercentage)console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`);else{const t=this.dividendPercentage/100*e,n=e-t,r=t/this.totalShares,a=this.numShares*r*(1-this.dividendTaxPercentage/100);_Player__WEBPACK_IMPORTED_MODULE_17__.Player.gainMoney(a),_Player__WEBPACK_IMPORTED_MODULE_17__.Player.recordMoneySource(a,"corporation"),this.funds=this.funds.plus(n)}else this.funds=this.funds.plus(e);this.updateSharePrice()}this.state.nextState(),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_19__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_19__.Page.Corporation)&&this.rerender()}},Corporation.prototype.determineValuation=function(){var e,t=this.revenue.minus(this.expenses).toNumber();return this.public?(this.dividendPercentage>0&&(t*=(100-this.dividendPercentage)/100),e=this.funds.toNumber()+85e3*t,e*=Math.pow(1.1,this.divisions.length),e=Math.max(e,0)):(e=1e10+Math.max(this.funds.toNumber(),0)/3,t>0?(e+=315e3*t,e*=Math.pow(1.1,this.divisions.length)):e=1e10*Math.pow(1.1,this.divisions.length),e-=e%1e6),e*_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_11__.BitNodeMultipliers.CorporationValuation},Corporation.prototype.getInvestment=function(){var e,t=this.determineValuation();let n=4;switch(this.fundingRound){case 0:e=.1,n=4;break;case 1:e=.35,n=3;break;case 2:e=.25,n=3;break;case 3:e=.2,n=2.5;break;case 4:return}var r=t*e*n,a=Math.floor(INITIALSHARES*e),i=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoBoxGetYesButton)(),o=Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoBoxGetNoButton)();i.innerHTML="Accept",o.innerHML="Reject",i.addEventListener("click",()=>(++this.fundingRound,this.funds=this.funds.plus(r),this.numShares-=a,this.rerender(),Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoBoxClose)())),o.addEventListener("click",()=>Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoBoxClose)()),Object(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_34__.yesNoBoxCreate)("An investment firm has offered you "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(r,"$0.000a")+" in funding in exchange for a "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(100*e,"0.000a")+"% stake in the company ("+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(a,"0.000a")+" shares).

    Do you accept or reject this offer?

    Hint: Investment firms will offer more money if your corporation is turning a profit")},Corporation.prototype.goPublic=function(){var e,t=this.determineValuation()/this.totalShares,n=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("p",{innerHTML:"Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will no longer own them. Your Corporation will receive "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(t,"$0.000a")+" per share (the IPO money will be deposited directly into your Corporation's funds).

    You have a total of "+_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.format(this.numShares,"0.000a")+" of shares that you can issue."}),r=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("input",{type:"number",placeholder:"Shares to issue",onkeyup:t=>{t.preventDefault(),t.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_31__.KEY.ENTER&&e.click()}}),a=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("br",{});e=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("a",{class:"a-link-button",innerText:"Go Public",clickListener:()=>{var e=Math.round(r.value),t=this.determineValuation()/this.totalShares;return isNaN(e)?(Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("Invalid value for number of issued shares"),!1):e>this.numShares?(Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("Error: You don't have that many shares to issue!"),!1):(this.public=!0,this.sharePrice=t,this.issuedShares=e,this.numShares-=e,this.funds=this.funds.plus(e*t),this.rerender(),Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33__.removeElementById)("cmpy-mgmt-go-public-popup"),Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)(`You took your ${this.name} public and earned `+`${_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_18__.numeralWrapper.formatMoney(e*t)} in your IPO`),!1)}});var i=Object(_utils_uiHelpers_createElement__WEBPACK_IMPORTED_MODULE_25__.createElement)("a",{class:"a-link-button",innerText:"Cancel",clickListener:()=>(Object(_utils_uiHelpers_removeElementById__WEBPACK_IMPORTED_MODULE_33__.removeElementById)("cmpy-mgmt-go-public-popup"),!1)});Object(_utils_uiHelpers_createPopup__WEBPACK_IMPORTED_MODULE_26__.createPopup)("cmpy-mgmt-go-public-popup",[n,a,r,e,i])},Corporation.prototype.getTargetSharePrice=function(){return this.determineValuation()/(2*(this.totalShares-this.numShares)+1)},Corporation.prototype.updateSharePrice=function(){const e=this.getTargetSharePrice();this.sharePrice<=e?this.sharePrice*=1+.01*Math.random():this.sharePrice*=1-.01*Math.random(),this.sharePrice<=.01&&(this.sharePrice=.01)},Corporation.prototype.immediatelyUpdateSharePrice=function(){this.sharePrice=this.getTargetSharePrice()},Corporation.prototype.calculateShareSale=function(e){let t=e,n=this.shareSalesUntilPriceUpdate,r=this.sharePrice,a=0,i=0;const o=Math.ceil(e/SHARESPERPRICEUPDATE);if(!(isNaN(o)||o>1e7)){for(let e=0;e3600?`${Math.floor(t/3600)} hour(s)`:t>60?`${Math.floor(t/60)} minute(s)`:`${Math.floor(t)} second(s)`},Corporation.prototype.unlock=function(e){const t=e[0],n=e[1];for(;this.unlockUpgrades.length<=t;)this.unlockUpgrades.push(0);this.funds.lt(n)?Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("You don't have enough funds to unlock this!"):(this.unlockUpgrades[t]=1,this.funds=this.funds.minus(n),5===t?this.dividendTaxPercentage-=5:6===t&&(this.dividendTaxPercentage-=10))},Corporation.prototype.upgrade=function(e){for(var t=e[0],n=e[1],r=e[2],a=e[3];this.upgrades.length<=t;)this.upgrades.push(0);for(;this.upgradeMultipliers.length<=t;)this.upgradeMultipliers.push(1);var i=n*Math.pow(r,this.upgrades[t]);if(this.funds.lt(i))Object(_utils_DialogBox__WEBPACK_IMPORTED_MODULE_21__.dialogBoxCreate)("You don't have enough funds to purchase this!");else if(++this.upgrades[t],this.funds=this.funds.minus(i),this.upgradeMultipliers[t]=1+this.upgrades[t]*a,1===t)for(var o=0;o=t.HacknetServerMaxCache)return 1/0;const r=t.HacknetServerUpgradeCacheMult;let a=0,i=this.cache;for(let e=0;e=t.HacknetServerMaxCores)return 1/0;const a=t.HacknetServerUpgradeCoreMult;let i=0,o=this.cores;for(let e=0;e=t.HacknetServerMaxLevel)return 1/0;const a=t.HacknetServerUpgradeLevelMult;let i=0,o=this.level;for(let e=0;e=t.HacknetServerMaxRam)return 1/0;let a=0,i=Math.round(Math.log2(this.maxRam)),o=this.maxRam;for(let e=0;e
    automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks
    clear/cls Clear the console
    help [cmd] Display this help text, or help text for a specific command
    log [en/dis] [type] Enable or disable logging for events and actions
    skill [action] [name] Level or display info about your Bladeburner skills
    start [type] [name] Start a Bladeburner action/task
    stop Stops your current Bladeburner action/task
    ",automate:'automate [var] [val] [hi/low]

    A simple way to automate your Bladeburner actions. This console command can be used to automatically start an action when your stamina rises above a certain threshold, and automatically switch to another action when your stamina drops below another threshold.

    automate status - Check the current status of your automation and get a brief description of what it\'ll do
    automate en - Enable the automation feature
    automate dis - Disable the automation feature

    There are four properties that must be set for this automation to work properly. Here is how to set them:

    automate stamina 100 high
    automate contract Tracking high
    automate stamina 50 low
    automate general "Field Analysis" low

    Using the four console commands above will set the automation to perform Tracking contracts if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below 50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must exactly match whatever the name is in the UI.',clear:"clear

    Clears the console",cls:"cls

    Clears the console",help:"help [command]

    Running 'help' with no arguments displays the general help text, which lists all console commands and a brief description of what they do. A command can be specified to get more specific help text about that particular command. For example:

    help automate

    will display specific information about using the automate console command",log:"log [en/dis] [type]

    Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged in the console. There are also random events that are logged in the console as well. The five categories of things that get logged are:

    [general, contracts, ops, blackops, events]

    The logging for these categories can be enabled or disabled like so:

    log dis contracts - Disables logging that occurs when contracts are completed
    log en contracts - Enables logging that occurs when contracts are completed
    log dis events - Disables logging for Bladeburner random events

    Logging can be universally enabled/disabled using the 'all' keyword:

    log dis all
    log en all",skill:'skill [action] [name]

    Level or display information about your skills.

    To display information about all of your skills and your multipliers, use:

    skill list

    To display information about a specific skill, specify the name of the skill afterwards. Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If the name of the skill has whitespace, enclose the name of the skill in double quotation marks:

    skill list Reaper
    skill list "Digital Observer"

    This console command can also be used to level up skills:

    skill level [skill name]',start:'start [type] [name]

    Start an action. An action is specified by its type and its name. The name is case-sensitive. It must appear exactly as it does in the UI. If the name of the action has whitespace, enclose it in double quotation marks. Valid action types include:

    [general, contract, op, blackop]

    Examples:

    start contract Tracking
    start op "Undercover Operation"
    ',stop:"stop

    Stop your current action and go idle"};function j(e={}){this.name=e.name?e.name:I[2],this.pop=e.pop?e.pop:Object(C.getRandomInt)(D,1.5*D),this.popEst=this.pop*(Math.random()+.5),this.comms=e.comms?e.comms:Object(C.getRandomInt)(5,150),this.commsEst=this.comms+Object(C.getRandomInt)(-5,5),this.commsEst<0&&(this.commsEst=0),this.chaos=0}function F(e={name:"foo",desc:"foo"}){if(!e.name)throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor");if(this.name=e.name,!e.desc)throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor");this.desc=e.desc,this.baseCost=e.baseCost?e.baseCost:1,this.costInc=e.costInc?e.costInc:1,e.maxLvl&&(this.maxLvl=e.maxLvl),e.successChanceAll&&(this.successChanceAll=e.successChanceAll),e.successChanceStealth&&(this.successChanceStealth=e.successChanceStealth),e.successChanceKill&&(this.successChanceKill=e.successChanceKill),e.successChanceContract&&(this.successChanceContract=e.successChanceContract),e.successChanceOperation&&(this.successChanceOperation=e.successChanceOperation),e.successChanceEstimate&&(this.successChanceEstimate=e.successChanceEstimate),e.actionTime&&(this.actionTime=e.actionTime),e.effHack&&(this.effHack=e.effHack),e.effStr&&(this.effStr=e.effStr),e.effDef&&(this.effDef=e.effDef),e.effDex&&(this.effDex=e.effDex),e.effAgi&&(this.effAgi=e.effAgi),e.effCha&&(this.effCha=e.effCha),e.stamina&&(this.stamina=e.stamina),e.money&&(this.money=e.money),e.expGain&&(this.expGain=e.expGain),e.weaponAbility&&(this.weaponAbility=e.weaponAbility),e.gunAbility&&(this.gunAbility=e.gunAbility)}e(document).keydown(function(e){if(m.routing.isOn(m.Page.Bladeburner)){if(!(p.Player.bladeburner instanceof X))return;let i=p.Player.bladeburner.consoleHistory;if(e.keyCode===O.KEY.ENTER){e.preventDefault();var t=Q.consoleInput.value;t.length>0&&(p.Player.bladeburner.postToConsole("> "+t),p.Player.bladeburner.resetConsoleInput(),p.Player.bladeburner.executeConsoleCommands(t))}if(e.keyCode===O.KEY.UPARROW){if(null==Q.consoleInput)return;var n=L;if(0===(a=i.length))return;(n<0||n>a)&&(L=a),0!==n&&--L;var r=i[L];Q.consoleInput.value=r,Object(y.setTimeoutRef)(function(){Q.consoleInput.selectionStart=Q.consoleInput.selectionEnd=1e4},0)}if(e.keyCode===O.KEY.DOWNARROW){if(null==Q.consoleInput)return;var a;n=L;if(0==(a=i.length))return;if((n<0||n>a)&&(L=a),n==a||n==a-1)L=a,Q.consoleInput.value="";else{r=i[++L];Q.consoleInput.value=r}}}}),j.prototype.improvePopulationEstimateByCount=function(e){if(isNaN(e))throw new Error("NaN passeed into City.improvePopulationEstimateByCount()");this.popEstthis.pop&&(this.popEst=this.pop)):this.popEst>this.pop&&(this.popEst-=e,this.popEstthis.pop&&(this.popEst=this.pop)):this.popEst>this.pop&&(this.popEst*=1-e/100,this.popEstthis.comms&&(this.commsEst=this.comms)):this.commsEst>this.comms&&(this.commsEst-=e,this.commsEst0?1:-1),this.pop+=n,t.changeEstEqually&&(this.popEst+=n,this.popEst<0&&(this.popEst=0)),n}},j.prototype.changeChaosByCount=function(e){if(isNaN(e))throw new Error("NaN passed into City.changeChaosByCount()");0!==e&&(this.chaos+=e,this.chaos<0&&(this.chaos=0))},j.prototype.changeChaosByPercentage=function(e){if(isNaN(e))throw new Error("NaN passed into City.chaosChaosByPercentage()");if(0!==e){var t=this.chaos*(e/100);this.chaos+=t,this.chaos<0&&(this.chaos=0)}},j.prototype.toJSON=function(){return Object(_.Generic_toJSON)("City",this)},j.fromJSON=function(e){return Object(_.Generic_fromJSON)(j,e.data)},_.Reviver.constructors.City=j,F.prototype.calculateCost=function(e){return Math.floor((this.baseCost+e*this.costInc)*i.BitNodeMultipliers.BladeburnerSkillCost)};const U={},H={BladesIntuition:"Blade's Intuition",Cloak:"Cloak",Marksman:"Marksman",WeaponProficiency:"Weapon Proficiency",ShortCircuit:"Short-Circuit",DigitalObserver:"Digital Observer",Tracer:"Tracer",Overclock:"Overclock",Reaper:"Reaper",EvasiveSystem:"Evasive System",Datamancer:"Datamancer",CybersEdge:"Cyber's Edge",HandsOfMidas:"Hands of Midas",Hyperdrive:"Hyperdrive"};function G(e={}){this.name=e.name?e.name:"",this.desc=e.desc?e.desc:"",this.level=1,this.maxLevel=1,this.autoLevel=!0,this.baseDifficulty=e.baseDifficulty?Object(b.addOffset)(e.baseDifficulty,10):100,this.difficultyFac=e.difficultyFac?e.difficultyFac:1.01,this.rewardFac=e.rewardFac?e.rewardFac:1.02,this.successes=0,this.failures=0,this.rankGain=e.rankGain?e.rankGain:0,e.rankLoss&&(this.rankLoss=e.rankLoss),e.hpLoss&&(this.hpLoss=e.hpLoss,this.hpLost=0),this.isStealth=!!e.isStealth,this.isKill=!!e.isKill,this.count=e.count?e.count:Object(C.getRandomInt)(1e3,25e3),this.countGrowth=e.countGrowth?e.countGrowth:Object(C.getRandomInt)(1,5);this.weights=e.weights?e.weights:{hack:1/7,str:1/7,def:1/7,dex:1/7,agi:1/7,cha:1/7,int:1/7};let t=0;for(const e in this.weights)this.weights.hasOwnProperty(e)&&(t+=this.weights[e]);if(t-1>=10*Number.EPSILON)throw new Error("Invalid weights when constructing Action "+this.name+". The weights should sum up to 1. They sum up to :1");this.decays=e.decays?e.decays:{hack:.9,str:.9,def:.9,dex:.9,agi:.9,cha:.9,int:.9};for(const e in this.decays)if(this.decays.hasOwnProperty(e)&&this.decays[e]>1)throw new Error("Invalid decays when constructing Action "+this.name+". Decay value cannot be greater than 1")}G.prototype.getDifficulty=function(){var e=this.baseDifficulty*Math.pow(this.difficultyFac,this.level-1);if(isNaN(e))throw new Error("Calculated NaN in Action.getDifficulty()");return e},G.prototype.getSuccessChance=function(e,t={}){if(null==e)throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");var n=this.getDifficulty(),r=0;for(var a in this.weights)if(this.weights.hasOwnProperty(a)){var i=p.Player.queryStatFromString(a),o="eff"+a.charAt(0).toUpperCase()+a.slice(1),s=e.skillMultipliers[o];null==s&&(console.log("ERROR: Failed to find Bladeburner Skill multiplier for: "+a),s=1),r+=this.weights[a]*Math.pow(s*i,this.decays[a])}(r*=e.calculateStaminaPenalty(),this instanceof z||this instanceof V)&&(this.teamCount&&this.teamCount>0&&(this.teamCount=Math.min(this.teamCount,e.teamSize),r*=Math.pow(this.teamCount,.05)));if(!(this instanceof V)){var l=e.getCurrentCity();if(t.est?r*=Math.pow(l.popEst/D,.7):r*=Math.pow(l.pop/D,.7),l.chaos>50){var c=l.chaos-50+1;n*=Math.pow(c,.1)}if(this instanceof z&&"Raid"===this.name&&l.comms<=0)return 0}if(r*=e.skillMultipliers.successChanceAll,(this instanceof z||this instanceof V)&&(r*=e.skillMultipliers.successChanceOperation),this instanceof Y&&(r*=e.skillMultipliers.successChanceContract),this.isStealth&&(r*=e.skillMultipliers.successChanceStealth),this.isKill&&(r*=e.skillMultipliers.successChanceKill),r*=p.Player.bladeburner_success_chance_mult,isNaN(r))throw new Error("Competence calculated as NaN in Action.getSuccessChance()");return Math.min(1,r/n)},G.prototype.attempt=function(e){return Math.random()=this.getSuccessesNeededForNextLevel(e)&&++this.maxLevel},G.prototype.toJSON=function(){return Object(_.Generic_toJSON)("Action",this)},G.fromJSON=function(e){return Object(_.Generic_fromJSON)(G,e.data)},_.Reviver.constructors.Action=G;const K={},q=Object.freeze({Idle:1,Contract:2,Operation:3,BlackOp:4,BlackOperation:4,Training:5,Recruitment:6,FieldAnalysis:7,"Field Analysis":7,Diplomacy:8,"Hyperbolic Regeneration Chamber":9});function $(e={}){e.name&&(this.name=e.name),e.type&&(this.type=e.type)}function Y(e={}){G.call(this,e)}function z(e={}){G.call(this,e),this.reqdRank=e.reqdRank?e.reqdRank:100,this.teamCount=e.teamCount?e.teamCount:0}function V(e={}){z.call(this,e),this.count=1,this.countGrowth=0}$.prototype.toJSON=function(){return Object(_.Generic_toJSON)("ActionIdentifier",this)},$.fromJSON=function(e){return Object(_.Generic_fromJSON)($,e.data)},_.Reviver.constructors.ActionIdentifier=$,Y.prototype=Object.create(G.prototype),Y.prototype.toJSON=function(){return Object(_.Generic_toJSON)("Contract",this)},Y.fromJSON=function(e){return Object(_.Generic_fromJSON)(Y,e.data)},_.Reviver.constructors.Contract=Y,z.prototype=Object.create(G.prototype),z.prototype.toJSON=function(){return Object(_.Generic_toJSON)("Operation",this)},z.fromJSON=function(e){return Object(_.Generic_fromJSON)(z,e.data)},_.Reviver.constructors.Operation=z,V.prototype=Object.create(G.prototype),V.prototype.toJSON=function(){return Object(_.Generic_toJSON)("BlackOperation",this)},V.fromJSON=function(e){return Object(_.Generic_fromJSON)(V,e.data)},_.Reviver.constructors.BlackOperation=V;const J={};function X(e={}){this.numHosp=0,this.moneyLost=0,this.rank=0,this.maxRank=0,this.skillPoints=0,this.totalSkillPoints=0,this.teamSize=0,this.teamLost=0,this.storedCycles=0,this.randomEventCounter=Object(C.getRandomInt)(240,600),this.actionTimeToComplete=0,this.actionTimeCurrent=0;var t=q.Idle;this.action=new $({type:t}),this.cities={};for(var n=0;n
    Does NOT require stamina."}),K[e="Recruitment"]=new G({name:e,desc:"Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.

    Does NOT require stamina."}),K[e="Diplomacy"]=new G({name:e,desc:"Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in your current city.

    Does NOT require stamina."}),K[e="Hyperbolic Regeneration Chamber"]=new G({name:e,desc:"Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your wounds and slightly increase your stamina.

    "}),J["Operation Typhoon"]=new V({name:"Operation Typhoon",desc:"Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that Zenyatta, along with the rest of the PMC, is a Synthoid.

    The goal of Operation Typhoon is to find and eliminate Zenyatta and RedWater by any means necessary. After the task is completed, the actions must be covered up from the general public.",baseDifficulty:2e3,reqdRank:2500,rankGain:50,rankLoss:10,hpLoss:100,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Zero"]=new V({name:"Operation Zero",desc:"AeroCorp is one of the world's largest defense contractors. It's leader, Steve Watataki, is thought to be a supporter of Synthoid rights. He must be removed.

    The goal of Operation Zero is to covertly infiltrate AeroCorp and uncover any incriminating evidence or information against Watataki that will cause him to be removed from his position at AeroCorp. Incriminating evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced security measures in the world.",baseDifficulty:2500,reqdRank:5e3,rankGain:60,rankLoss:15,hpLoss:50,weights:{hack:.2,str:.15,def:.15,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isStealth:!0}),J["Operation X"]=new V({name:"Operation X",desc:"We have recently discovered an underground publication group called Samizdat. Even though most of their publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and other Eastern countries.

    Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that their base of operations is in Ishima's underground sewer systems. Your task is to investigate the sewer systems, and eliminate Samizdat. They must never publish anything again.",baseDifficulty:3e3,reqdRank:7500,rankGain:75,rankLoss:15,hpLoss:100,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Titan"]=new V({name:"Operation Titan",desc:"Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to, but the research that they could be conducting using Titan Laboraties' vast resources is potentially very dangerous.

    Your goal is to enter and destroy the Bioengineering department's facility in Aevum. The task is not just to retire the Synthoids there, but also to destroy any information or research at the facility that is relevant to the Synthoids and their goals.",baseDifficulty:4e3,reqdRank:1e4,rankGain:100,rankLoss:20,hpLoss:100,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Ares"]=new V({name:"Operation Ares",desc:"One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy weapons. It is critical for the safety of humanity that this deal does not happen.

    Your task is to intercept the deal. Leave no survivors.",baseDifficulty:5e3,reqdRank:12500,rankGain:125,rankLoss:20,hpLoss:200,weights:{hack:0,str:.25,def:.25,dex:.25,agi:.25,cha:0,int:0},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Archangel"]=new V({name:"Operation Archangel",desc:"Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms trafficking operation.

    The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the number of other casualties, but do what you must to complete the mission.",baseDifficulty:7500,reqdRank:15e3,rankGain:200,rankLoss:20,hpLoss:25,weights:{hack:0,str:.2,def:.2,dex:.3,agi:.3,cha:0,int:0},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Juggernaut"]=new V({name:"Operation Juggernaut",desc:"The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into Sector-12. We also have reason to believe the tried to break into one of Universal Energy's facilities in order to cause a city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented Synthoid, and have thus enlisted our help.

    Your mission is to eradicate Juggernaut and his followers.",baseDifficulty:1e4,reqdRank:2e4,rankGain:300,rankLoss:40,hpLoss:300,weights:{hack:0,str:.25,def:.25,dex:.25,agi:.25,cha:0,int:0},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Red Dragon"]=new V({name:"Operation Red Dragon",desc:"The Tetrads criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. We believe they altered and possibly improved the design and began manufacturing their own Synthoid models in order to bolster their criminal activities.

    Your task is to infiltrate and destroy the Tetrads' base of operations in Los Angeles. Intelligence tells us that their base houses one of their Synthoid manufacturing units.",baseDifficulty:12500,reqdRank:25e3,rankGain:500,rankLoss:50,hpLoss:500,weights:{hack:.05,str:.2,def:.2,dex:.25,agi:.25,cha:0,int:.05},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation K"]=new V({name:"Operation K",desc:"CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology. This technology is supposedly capable of cloning Synthoid, not only physically but also their advanced AI modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would be catastrophic.

    We do not have the power or jurisdiction to shutdown this down through legal or political means, so we must resort to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in its creation.",baseDifficulty:15e3,reqdRank:3e4,rankGain:750,rankLoss:60,hpLoss:1e3,weights:{hack:.05,str:.2,def:.2,dex:.25,agi:.25,cha:0,int:.05},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Deckard"]=new V({name:"Operation Deckard",desc:"Despite your success in eliminating VitaLife's new android-replicating technology in Operation K, we've discovered that a small group of MK-VI Synthoids were able to make off with the schematics and design of the technology before the Operation. It is almost a certainty that these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.The goal of Operation Deckard is to hunt down these Synthoids and retire them. I don't need to tell you how critical this mission is.",baseDifficulty:2e4,reqdRank:4e4,rankGain:1e3,rankLoss:75,hpLoss:200,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:0,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Tyrell"]=new V({name:"Operation Tyrell",desc:"A week ago Blade Industries reported a small break-in at one of their Aevum Augmentation storage facitilities. We figured out that The Dark Army was behind the heist, and didn't think any more of it. However, we've just discovered that several known MK-VI Synthoids were part of that break-in group.

    We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt down the associated Dark Army members and eliminate them.",baseDifficulty:25e3,reqdRank:5e4,rankGain:1500,rankLoss:100,hpLoss:500,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Wallace"]=new V({name:"Operation Wallace",desc:"Based on information gathered from Operation Tyrell, we've discovered that The Dark Army was well aware that there were Synthoids amongst their ranks. Even worse, we believe that The Dark Army is working together with other criminal organizations such as The Syndicate and that they are planning some sort of large-scale takeover of multiple major cities, most notably Aevum. We suspect that Synthoids have infiltrated the ranks of these criminal factions and are trying to stage another Synthoid uprising.

    The best way to deal with this is to prevent it before it even happens. The goal of Operation Wallace is to destroy the Dark Army and Syndicate factions in Aevum immediately. Leave no survivors.",baseDifficulty:3e4,reqdRank:75e3,rankGain:2e3,rankLoss:150,hpLoss:1500,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Shoulder of Orion"]=new V({name:"Operation Shoulder of Orion",desc:"China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using Synthoids. We believe China is trying to establish the first off-world colonies.

    The mission is to prevent this launch without instigating an international conflict. When you accept this mission you will be officially disavowed by the NSA and the national government until after you successfully return. In the event of failure, all of the operation's team members must not let themselves be captured alive.",baseDifficulty:35e3,reqdRank:1e5,rankGain:2500,rankLoss:500,hpLoss:1500,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isStealth:!0}),J["Operation Hyron"]=new V({name:"Operation Hyron",desc:"Our intelligence tells us that Fulcrum Technologies is developing a quantum supercomputer using human brains as core processors. This supercomputer is rumored to be able to store vast amounts of data and perform computations unmatched by any other supercomputer on the planet. But more importantly, the use of organic human brains means that the supercomputer may be able to reason abstractly and become self-aware.

    I do not need to remind you why sentient-level AIs pose a serious thread to all of mankind.

    The research for this project is being conducted at one of Fulcrum Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, and then find and kill the project lead.",baseDifficulty:4e4,reqdRank:125e3,rankGain:3e3,rankLoss:1e3,hpLoss:500,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Morpheus"]=new V({name:"Operation Morpheus",desc:"DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the peoples dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our agents and informants in Chonqging, we have reason to believe that one of the broadcast towers there has been compromised by Synthoids and is being used to spread pro-Synthoid propaganda.

    The mission is to destroy this broadcast tower. Speed and stealth are of the upmost important for this.",baseDifficulty:45e3,reqdRank:15e4,rankGain:4e3,rankLoss:1e3,hpLoss:100,weights:{hack:.05,str:.15,def:.15,dex:.3,agi:.3,cha:0,int:.05},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isStealth:!0}),J["Operation Ion Storm"]=new V({name:"Operation Ion Storm",desc:"Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the Sector-12 Slums. We don't know if they are rogue Synthoids from the Uprising, but we do know that they have been stockpiling weapons, money, and other resources. This makes them dangerous.

    This is a full-scale assault operation to find and retire all of these Synthoids in the Sector-12 Slums.",baseDifficulty:5e4,reqdRank:175e3,rankGain:5e3,rankLoss:1e3,hpLoss:5e3,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Annihilus"]=new V({name:"Operation Annihilus",desc:"Our superiors have ordered us to eradicate everything and everyone in an underground facility located in Aevum. They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist organization called 'The Covenant'. We have no prior intelligence about this organization, so you are going in blind.",baseDifficulty:55e3,reqdRank:2e5,rankGain:7500,rankLoss:1e3,hpLoss:1e4,weights:{hack:0,str:.24,def:.24,dex:.24,agi:.24,cha:0,int:.04},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Ultron"]=new V({name:"Operation Ultron",desc:"OmniTek Incorporated, the original designer and manufacturer of Synthoids, has notified us of a malfunction in their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized and seek out the destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue ones from the Uprising.

    OmniTek has also told us they they believe someone has triggered this malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids are now amassing in Volhaven to form a terrorist group called Ultron.

    Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).

    Your task is to find and destroy Ultron.",baseDifficulty:6e4,reqdRank:25e4,rankGain:1e4,rankLoss:2e3,hpLoss:1e4,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75},isKill:!0}),J["Operation Centurion"]=new V({name:"Operation Centurion",desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

    Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its advancement became our primary goal. And at the peak of human civilization technology turned into power. Global, absolute power.

    It seems that the universe is not without a sense of irony.

    D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",baseDifficulty:7e4,reqdRank:3e5,rankGain:15e3,rankLoss:5e3,hpLoss:1e4,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75}}),J["Operation Vindictus"]=new V({name:"Operation Vindictus",desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

    The bits are all around us. The daemons that hold the Node together can manifest themselves in many different ways.

    D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",baseDifficulty:75e3,reqdRank:35e4,rankGain:2e4,rankLoss:2e4,hpLoss:2e4,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75}}),J["Operation Daedalus"]=new V({name:"Operation Daedalus",desc:"Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.",baseDifficulty:8e4,reqdRank:4e5,rankGain:4e4,rankLoss:1e4,hpLoss:1e5,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.75}})}(),this.initializeDomElementRefs(),e.new&&this.create()}X.prototype.prestige=function(){this.resetAction();var e=c.Factions.Bladeburners;this.rank>=25&&Object(u.joinFaction)(e)},X.prototype.create=function(){this.contracts.Tracking=new Y({name:"Tracking",desc:"Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

    Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever city you are currently in.",baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041,rankGain:.3,hpLoss:.5,count:Object(C.getRandomInt)(25,150),countGrowth:Object(C.getRandomInt)(5,75)/10,weights:{hack:0,str:.05,def:.05,dex:.35,agi:.35,cha:.1,int:.05},decays:{hack:0,str:.91,def:.91,dex:.91,agi:.91,cha:.9,int:1},isStealth:!0}),this.contracts["Bounty Hunter"]=new Y({name:"Bounty Hunter",desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

    Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also increase its chaos level.",baseDifficulty:250,difficultyFac:1.04,rewardFac:1.085,rankGain:.9,hpLoss:1,count:Object(C.getRandomInt)(5,150),countGrowth:Object(C.getRandomInt)(5,75)/10,weights:{hack:0,str:.15,def:.15,dex:.25,agi:.25,cha:.1,int:.1},decays:{hack:0,str:.91,def:.91,dex:.91,agi:.91,cha:.8,int:.9},isKill:!0}),this.contracts.Retirement=new Y({name:"Retirement",desc:"Hunt down and retire (kill) rogue Synthoids.

    Successfully completing a Retirement contract will lower the population in your current city, and will also increase its chaos level.",baseDifficulty:200,difficultyFac:1.03,rewardFac:1.065,rankGain:.6,hpLoss:1,count:Object(C.getRandomInt)(5,150),countGrowth:Object(C.getRandomInt)(5,75)/10,weights:{hack:0,str:.2,def:.2,dex:.2,agi:.2,cha:.1,int:.1},decays:{hack:0,str:.91,def:.91,dex:.91,agi:.91,cha:.8,int:.9},isKill:!0}),this.operations.Investigation=new z({name:"Investigation",desc:"As a field agent, investigate and identify Synthoid populations, movements, and operations.

    Successful Investigation ops will increase the accuracy of your synthoid data.

    You will NOT lose HP from failed Investigation ops.",baseDifficulty:400,difficultyFac:1.03,rewardFac:1.07,reqdRank:25,rankGain:2.2,rankLoss:.2,count:Object(C.getRandomInt)(1,100),countGrowth:Object(C.getRandomInt)(10,40)/10,weights:{hack:.25,str:.05,def:.05,dex:.2,agi:.1,cha:.25,int:.1},decays:{hack:.85,str:.9,def:.9,dex:.9,agi:.9,cha:.7,int:.9},isStealth:!0}),this.operations["Undercover Operation"]=new z({name:"Undercover Operation",desc:"Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.

    Successful Undercover ops will increase the accuracy of your synthoid data.",baseDifficulty:500,difficultyFac:1.04,rewardFac:1.09,reqdRank:100,rankGain:4.4,rankLoss:.4,hpLoss:2,count:Object(C.getRandomInt)(1,100),countGrowth:Object(C.getRandomInt)(10,40)/10,weights:{hack:.2,str:.05,def:.05,dex:.2,agi:.2,cha:.2,int:.1},decays:{hack:.8,str:.9,def:.9,dex:.9,agi:.9,cha:.7,int:.9},isStealth:!0}),this.operations["Sting Operation"]=new z({name:"Sting Operation",desc:"Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.",baseDifficulty:650,difficultyFac:1.04,rewardFac:1.095,reqdRank:500,rankGain:5.5,rankLoss:.5,hpLoss:2.5,count:Object(C.getRandomInt)(1,150),countGrowth:Object(C.getRandomInt)(3,40)/10,weights:{hack:.25,str:.05,def:.05,dex:.25,agi:.1,cha:.2,int:.1},decays:{hack:.8,str:.85,def:.85,dex:.85,agi:.85,cha:.7,int:.9},isStealth:!0}),this.operations.Raid=new z({name:"Raid",desc:"Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your current city in order for this Operation to be successful",baseDifficulty:800,difficultyFac:1.045,rewardFac:1.1,reqdRank:3e3,rankGain:55,rankLoss:2.5,hpLoss:50,count:Object(C.getRandomInt)(1,150),countGrowth:Object(C.getRandomInt)(2,40)/10,weights:{hack:.1,str:.2,def:.2,dex:.2,agi:.2,cha:0,int:.1},decays:{hack:.7,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.9},isKill:!0}),this.operations["Stealth Retirement Operation"]=new z({name:"Stealth Retirement Operation",desc:"Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any attention. Stealth and discretion are key.",baseDifficulty:1e3,difficultyFac:1.05,rewardFac:1.11,reqdRank:2e4,rankGain:22,rankLoss:2,hpLoss:10,count:Object(C.getRandomInt)(1,150),countGrowth:Object(C.getRandomInt)(1,20)/10,weights:{hack:.1,str:.1,def:.1,dex:.3,agi:.3,cha:0,int:.1},decays:{hack:.7,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.9},isStealth:!0,isKill:!0}),this.operations.Assassination=new z({name:"Assassination",desc:"Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the Synthoid communities.",baseDifficulty:1500,difficultyFac:1.06,rewardFac:1.14,reqdRank:5e4,rankGain:44,rankLoss:4,hpLoss:5,count:Object(C.getRandomInt)(1,150),countGrowth:Object(C.getRandomInt)(1,20)/10,weights:{hack:.1,str:.1,def:.1,dex:.3,agi:.3,cha:0,int:.1},decays:{hack:.6,str:.8,def:.8,dex:.8,agi:.8,cha:0,int:.8},isStealth:!0,isKill:!0})},X.prototype.storeCycles=function(e=1){this.storedCycles+=e},X.prototype.process=function(){if(!1===h.b&&this.blackops.hasOwnProperty("Operation Daedalus"))return Object(h.a)(p.Player.bitNodeN);if(!1===r.Augmentations[a.AugmentationNames.BladesSimulacrum].owned&&p.Player.isWorking){if(this.action.type!==q.Idle){let e="Your Bladeburner action was cancelled because you started doing something else.";this.automateEnabled&&(e+="

    Your automation was disabled as well. You will have to re-enable it through the Bladeburner console",this.automateEnabled=!1),Object(g.dialogBoxCreate)(e)}this.resetAction()}if(this.stamina<=0&&(this.log("Your Bladeburner action was cancelled because your stamina hit 0"),this.resetAction()),this.storedCycles>=5){var e=Math.floor(this.storedCycles/5);for(var t in e=Math.min(e,5),this.storedCycles-=5*e,this.calculateMaxStamina(),this.stamina+=this.calculateStaminaGainPerSecond()*e,this.stamina=Math.min(this.maxStamina,this.stamina),this.contracts)if(this.contracts.hasOwnProperty(t)){var n=this.contracts[t];n.count+=e*n.countGrowth/480}for(var i in this.operations)if(this.operations.hasOwnProperty(i)){var o=this.operations[i];o.count+=e*o.countGrowth/480}for(var s=0;s=this.automateThreshHigh&&(this.action.name===this.automateActionHigh.name&&this.action.type===this.automateActionHigh.type||(this.action=new $({type:this.automateActionHigh.type,name:this.automateActionHigh.name}),this.startAction(this.action)))),m.routing.isOn(m.Page.Bladeburner)&&this.updateContent()}},X.prototype.calculateMaxStamina=function(){var e=p.Player.agility*this.skillMultipliers.effAgi,t=Math.pow(e,.8)+this.staminaBonus;if(t*=this.skillMultipliers.stamina,t*=p.Player.bladeburner_max_stamina_mult,isNaN(t))throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");this.maxStamina=t},X.prototype.calculateStaminaGainPerSecond=function(){var e=p.Player.agility*this.skillMultipliers.effAgi;return(.0085+this.maxStamina/7e4)*Math.pow(e,.17)*(this.skillMultipliers.stamina*p.Player.bladeburner_stamina_gain_mult)},X.prototype.calculateStaminaPenalty=function(){return Math.min(1,this.stamina/(.5*this.maxStamina))},X.prototype.changeRank=function(e){if(isNaN(e))throw new Error("NaN passed into Bladeburner.changeRank()");this.rank+=e,this.rank<0&&(this.rank=0),this.maxRank=Math.max(this.rank,this.maxRank);if(Object(c.factionExists)("Bladeburners")){var t=c.Factions.Bladeburners;if(!(t instanceof l.Faction))throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");if(t.isMember){var n=1+t.favor/100;t.playerReputation+=2*e*p.Player.faction_rep_mult*n}}var r=3*(this.totalSkillPoints+1);if(this.maxRank>=r){var a=Math.floor((this.maxRank-r)/3+1);this.skillPoints+=a,this.totalSkillPoints+=a}},X.prototype.getCurrentCity=function(){var e=this.cities[this.city];if(!(e instanceof j))throw new Error("Bladeburner.getCurrentCity() did not properly return a City object");return e},X.prototype.resetSkillMultipliers=function(){this.skillMultipliers={successChanceAll:1,successChanceStealth:1,successChanceKill:1,successChanceContract:1,successChanceOperation:1,successChanceEstimate:1,actionTime:1,effHack:1,effStr:1,effDef:1,effDex:1,effAgi:1,effCha:1,effInt:1,stamina:1,money:1,expGain:1,weaponAbility:1,gunAbility:1}},X.prototype.updateSkillMultipliers=function(){for(var e in this.resetSkillMultipliers(),this.skills)if(this.skills.hasOwnProperty(e)){var t=U[e];if(null==t)throw new Error("Could not find Skill Object for: "+e);var n=this.skills[e];if(null==n||n<=0)continue;for(var r=Object.keys(this.skillMultipliers),a=0;a=this.actionTimeToComplete?this.completeAction():void 0}},X.prototype.completeAction=function(){switch(this.action.type){case q.Contract:case q.Operation:try{var e=this.action.type===q.Operation;if(null==(g=this.getActionObject(this.action)))throw new Error("Failed to get Contract/Operation Object for: "+this.action.name);var t=g.getDifficulty(),n=Math.pow(t,.28)+t/650,r=Math.pow(g.rewardFac,g.level-1);if(this.stamina-=.285*n,this.stamina<0&&(this.stamina=0),g.attempt(this)){this.gainActionStats(g,!0),++g.successes,--g.count;var a=0;if(e||(a=25e4*r*this.skillMultipliers.money,p.Player.gainMoney(a),p.Player.recordMoneySource(a,"bladeburner")),e?g.setMaxLevel(2.5):g.setMaxLevel(3),g.rankGain){var s=Object(b.addOffset)(g.rankGain*r*i.BitNodeMultipliers.BladeburnerRank,10);this.changeRank(s),e&&this.logging.ops?this.log(g.name+" successfully completed! Gained "+Object(f.formatNumber)(s,3)+" rank"):!e&&this.logging.contracts&&this.log(g.name+" contract successfully completed! Gained "+Object(f.formatNumber)(s,3)+" rank and "+d.numeralWrapper.format(a,"$0.000a"))}e?this.completeOperation(!0):this.completeContract(!0)}else{this.gainActionStats(g,!1),++g.failures;var l=0,c=0;g.rankLoss&&(l=Object(b.addOffset)(g.rankLoss*r,10),this.changeRank(-1*l)),g.hpLoss&&(c=g.hpLoss*n,c=Math.ceil(Object(b.addOffset)(c,10)),this.hpLost+=c,p.Player.takeDamage(c)&&(++this.numHosp,this.moneyLost+=o.CONSTANTS.HospitalCostPerHp*p.Player.max_hp));var u="";l>0&&(u+="Lost "+Object(f.formatNumber)(l,3)+" rank."),c>0&&(u+="Took "+Object(f.formatNumber)(c,0)+" damage."),e&&this.logging.ops?this.log(g.name+" failed! "+u):!e&&this.logging.contracts&&this.log(g.name+" contract failed! "+u),e?this.completeOperation(!1):this.completeContract(!1)}g.autoLevel&&(g.level=g.maxLevel),this.startAction(this.action)}catch(e){Object(k.exceptionAlert)(e)}break;case q.BlackOp:case q.BlackOperation:try{var g;if(null==(g=this.getActionObject(this.action))||!(g instanceof V))throw new Error("Failed to get BlackOperation Object for: "+this.action.name);t=g.getDifficulty(),n=Math.pow(t,.28)+t/650;this.stamina-=.285*n,this.stamina<0&&(this.stamina=0);var _,y=g.teamCount;if(g.attempt(this)){this.gainActionStats(g,!0),g.count=0,this.blackops[g.name]=!0;var v=0;if(g.rankGain&&(v=Object(b.addOffset)(g.rankGain*i.BitNodeMultipliers.BladeburnerRank,10),this.changeRank(v)),_=Math.ceil(y/2),"Operation Daedalus"===g.name)return this.resetAction(),Object(h.a)(p.Player.bitNodeN);m.routing.isOn(m.Page.Bladeburner)&&this.createActionAndSkillsContent(),this.logging.blackops&&this.log(g.name+" successful! Gained "+Object(f.formatNumber)(v,1)+" rank")}else{this.gainActionStats(g,!1);var E=0;c=0;g.rankLoss&&(E=Object(b.addOffset)(g.rankLoss,10),this.changeRank(-1*E)),g.hpLoss&&(c=g.hpLoss*n,c=Math.ceil(Object(b.addOffset)(c,10)),p.Player.takeDamage(c)&&(++this.numHosp,this.moneyLost+=o.CONSTANTS.HospitalCostPerHp*p.Player.max_hp)),_=Math.floor(y),this.logging.blackops&&this.log(g.name+" failed! Lost "+Object(f.formatNumber)(E,1)+" rank and took "+Object(f.formatNumber)(c,0)+" damage")}if(this.resetAction(),y>=1){var P=Object(C.getRandomInt)(1,_);this.teamSize-=P,this.teamLost+=P,this.logging.blackops&&this.log("You lost "+Object(f.formatNumber)(P,0)+" team members during "+g.name)}}catch(e){Object(k.exceptionAlert)(e)}break;case q.Training:this.stamina-=.1425;var O=30*p.Player.strength_exp_mult,T=30*p.Player.defense_exp_mult,S=30*p.Player.dexterity_exp_mult,M=30*p.Player.agility_exp_mult,x=.04*this.skillMultipliers.stamina;p.Player.gainStrengthExp(O),p.Player.gainDefenseExp(T),p.Player.gainDexterityExp(S),p.Player.gainAgilityExp(M),this.staminaBonus+=x,this.logging.general&&this.log("Training completed. Gained: "+Object(f.formatNumber)(O,1)+" str exp, "+Object(f.formatNumber)(T,1)+" def exp, "+Object(f.formatNumber)(S,1)+" dex exp, "+Object(f.formatNumber)(M,1)+" agi exp, "+Object(f.formatNumber)(x,3)+" max stamina"),this.startAction(this.action);break;case q.FieldAnalysis:case q["Field Analysis"]:var w=.04*Math.pow(p.Player.hacking_skill,.3)+.04*Math.pow(p.Player.intelligence,.9)+.02*Math.pow(p.Player.charisma,.3);if(w*=p.Player.bladeburner_analysis_mult,isNaN(w)||w<0)throw new Error("Field Analysis Effectiveness calculated to be NaN or negative");var A=20*p.Player.hacking_exp_mult,R=20*p.Player.charisma_exp_mult;p.Player.gainHackingExp(A),p.Player.gainIntelligenceExp(.001),p.Player.gainCharismaExp(R),this.changeRank(.1*i.BitNodeMultipliers.BladeburnerRank),this.getCurrentCity().improvePopulationEstimateByPercentage(w*this.skillMultipliers.successChanceEstimate),this.logging.general&&this.log("Field analysis completed. Gained 0.1 rank, "+Object(f.formatNumber)(A,1)+" hacking exp, and "+Object(f.formatNumber)(R,1)+" charisma exp"),this.startAction(this.action);break;case q.Recruitment:var N=this.getRecruitmentSuccessChance();if(Math.random()=1){n=e?Math.ceil(r/2):Math.floor(r);var a=Object(C.getRandomInt)(0,n);this.teamSize-=a,this.teamLost+=a,this.logging.ops&&a>0&&this.log("Lost "+Object(f.formatNumber)(a,0)+" team members during this "+t.name)}var i=this.getCurrentCity();switch(t.name){case"Investigation":e?(i.improvePopulationEstimateByPercentage(.4*this.skillMultipliers.successChanceEstimate),Math.random()<.02*this.skillMultipliers.successChanceEstimate&&i.improveCommunityEstimate(1)):this.triggerPotentialMigration(this.city,.1);break;case"Undercover Operation":e?(i.improvePopulationEstimateByPercentage(.8*this.skillMultipliers.successChanceEstimate),Math.random()<.02*this.skillMultipliers.successChanceEstimate&&i.improveCommunityEstimate(1)):this.triggerPotentialMigration(this.city,.15);break;case"Sting Operation":e&&i.changePopulationByPercentage(-.1,{changeEstEqually:!0,nonZero:!0}),i.changeChaosByCount(.1);break;case"Raid":if(e)i.changePopulationByPercentage(-1,{changeEstEqually:!0,nonZero:!0}),--i.comms,--i.commsEst;else{var o=Object(C.getRandomInt)(-10,-5)/10;i.changePopulationByPercentage(o,{nonZero:!0})}i.changeChaosByPercentage(Object(C.getRandomInt)(1,5));break;case"Stealth Retirement Operation":e&&i.changePopulationByPercentage(-.5,{changeEstEqually:!0,nonZero:!0}),i.changeChaosByPercentage(Object(C.getRandomInt)(-3,-1));break;case"Assassination":e&&i.changePopulationByCount(-1,{estChange:-1}),i.changeChaosByPercentage(Object(C.getRandomInt)(-5,5));break;default:throw new Error("Invalid Action name in completeOperation: "+this.action.name)}},X.prototype.getRecruitmentTime=function(){var e=p.Player.charisma*this.skillMultipliers.effCha,t=Math.pow(e,.81)+e/90;return Math.max(10,Math.round(300-t))},X.prototype.getRecruitmentSuccessChance=function(){return Math.pow(p.Player.charisma,.45)/(this.teamSize+1)},X.prototype.getDiplomacyEffectiveness=function(){return(100-(Math.pow(p.Player.charisma,.045)+p.Player.charisma/1e3))/100},X.prototype.gainActionStats=function(e,t){var n=e.getDifficulty(),r=Math.pow(n,.28)+n/650,a=this.actionTimeToComplete,i=t?1:.5,o=1*a*i*r,s=.001*a*i*r;const l=this.skillMultipliers.expGain;p.Player.gainHackingExp(o*e.weights.hack*p.Player.hacking_exp_mult*l),p.Player.gainStrengthExp(o*e.weights.str*p.Player.strength_exp_mult*l),p.Player.gainDefenseExp(o*e.weights.def*p.Player.defense_exp_mult*l),p.Player.gainDexterityExp(o*e.weights.dex*p.Player.dexterity_exp_mult*l),p.Player.gainAgilityExp(o*e.weights.agi*p.Player.agility_exp_mult*l),p.Player.gainCharismaExp(o*e.weights.cha*p.Player.charisma_exp_mult*l),p.Player.gainIntelligenceExp(s*e.weights.int*l)},X.prototype.randomEvent=function(){var e=Math.random(),t=I[Object(C.getRandomInt)(0,5)],n=this.cities[t];if(!(n instanceof j))throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()");for(var r=I[Object(C.getRandomInt)(0,5)];r===t;)r=I[Object(C.getRandomInt)(0,5)];var a=this.cities[r];if(!(n instanceof j&&a instanceof j))throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()");if(e<=.05){++n.comms;var i=Object(C.getRandomInt)(10,20)/100,o=Math.round(n.pop*i);n.pop+=o,this.logging.events&&this.log("Intelligence indicates that a new Synthoid community was formed in a city")}else if(e<=.1)if(n.comms<=0){++n.comms;i=Object(C.getRandomInt)(10,20)/100,o=Math.round(n.pop*i);n.pop+=o,this.logging.events&&this.log("Intelligence indicates that a new Synthoid community was formed in a city")}else{--n.comms,++a.comms;i=Object(C.getRandomInt)(10,20)/100,o=Math.round(n.pop*i);n.pop-=o,a.pop+=o,this.logging.events&&this.log("Intelligence indicates that a Synthoid community migrated from "+t+" to some other city")}else if(e<=.3){i=Object(C.getRandomInt)(8,24)/100,o=Math.round(n.pop*i);n.pop+=o,this.logging.events&&this.log("Intelligence indicates that the Synthoid population of "+t+" just changed significantly")}else if(e<=.5)this.triggerMigration(t),this.logging.events&&this.log("Intelligence indicates that a large number of Synthoids migrated from "+t+" to some other city");else if(e<=.7)n.chaos+=1,n.chaos*=1+Object(C.getRandomInt)(5,20)/100,this.logging.events&&this.log("Tensions between Synthoids and humans lead to riots in "+t+"! Chaos increased");else if(e<=.9){i=Object(C.getRandomInt)(8,20)/100,o=Math.round(n.pop*i);n.pop-=o,this.logging.events&&this.log("Intelligence indicates that the Synthoid population of "+t+" just changed significantly")}},X.prototype.triggerPotentialMigration=function(e,t){(null==t||isNaN(t))&&console.log("ERROR: Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"),t>1&&(t/=100),Math.random()0&&(i*=Object(C.getRandomInt)(2,4),--r.comms,++n.comms);var o=Math.round(r.pop*i);r.pop-=o,n.pop+=o};let Q={};X.prototype.initializeDomElementRefs=function(){Q={bladeburnerDiv:null,overviewConsoleParentDiv:null,overviewDiv:null,actionAndSkillsDiv:null,currentTab:null,consoleDiv:null,consoleTable:null,consoleInputRow:null,consoleInputCell:null,consoleInputHeader:null,consoleInput:null,overviewRank:null,overviewStamina:null,overviewStaminaHelpTip:null,overviewGen1:null,overviewEstPop:null,overviewEstPopHelpTip:null,overviewEstComms:null,overviewChaos:null,overviewSkillPoints:null,overviewBonusTime:null,overviewAugSuccessMult:null,overviewAugMaxStaminaMult:null,overviewAugStaminaGainMult:null,overviewAugAnalysisMult:null,actionsAndSkillsDesc:null,actionsAndSkillsList:null,generalActions:{},contracts:{},operations:{},blackops:{},skills:{},skillPointsDisplay:null}},X.prototype.createContent=function(){Q.bladeburnerDiv=Object(M.createElement)("div",{id:"bladeburner-container",position:"fixed",class:"generic-menupage-container"}),Q.overviewConsoleParentDiv=Object(M.createElement)("div",{height:"60%",display:"block",position:"relative"}),Q.overviewDiv=Object(M.createElement)("div",{width:"30%",display:"inline-block",border:"1px solid white"}),Q.actionAndSkillsDiv=Object(M.createElement)("div",{height:"60%",width:"70%",display:"block",border:"1px solid white",margin:"6px",padding:"6px"}),Q.currentTab="general",this.createOverviewContent(),this.createActionAndSkillsContent(),Q.consoleDiv=Object(M.createElement)("div",{class:"bladeburner-console-div",clickListener:()=>(Q.consoleInput instanceof Element&&Q.consoleInput.focus(),!1)}),Q.consoleTable=Object(M.createElement)("table",{class:"bladeburner-console-table"}),Q.consoleInputRow=Object(M.createElement)("tr",{class:"bladeburner-console-input-row",id:"bladeubrner-console-input-row"}),Q.consoleInputCell=Object(M.createElement)("td",{class:"bladeburner-console-input-cell"}),Q.consoleInputHeader=Object(M.createElement)("pre",{innerText:"> "}),Q.consoleInput=Object(M.createElement)("input",{type:"text",class:"bladeburner-console-input",tabIndex:1,onfocus:()=>{Q.consoleInput.value=Q.consoleInput.value}}),Q.consoleInputCell.appendChild(Q.consoleInputHeader),Q.consoleInputCell.appendChild(Q.consoleInput),Q.consoleInputRow.appendChild(Q.consoleInputCell),Q.consoleTable.appendChild(Q.consoleInputRow),Q.consoleDiv.appendChild(Q.consoleTable),Q.overviewConsoleParentDiv.appendChild(Q.overviewDiv),Q.overviewConsoleParentDiv.appendChild(Q.consoleDiv),Q.bladeburnerDiv.appendChild(Q.overviewConsoleParentDiv),Q.bladeburnerDiv.appendChild(Q.actionAndSkillsDiv);const e=Object(M.createElement)("div");if(e.innerHTML=`${R}= This action requires stealth, ${N} = This action involves retirement`,Q.bladeburnerDiv.appendChild(e),document.getElementById("entire-game-container").appendChild(Q.bladeburnerDiv),0===this.consoleLogs.length)this.postToConsole("Bladeburner Console BETA"),this.postToConsole("Type 'help' to see console commands");else for(let e=0;e{Object(g.dialogBoxCreate)("Performing actions will use up your stamina.

    Your max stamina is determined primarily by your agility stat.

    Your stamina gain rate is determined by both your agility and your max stamina. Higher max stamina leads to a higher gain rate.

    Once your stamina falls below 50% of its max value, it begins to negatively affect the success rate of your contracts/operations. This penalty is shown in the overview panel. If the penalty is 15%, then this means your success rate would be multipled by 85% (100 - 15).

    Your max stamina and stamina gain rate can also be increased by training, or through skills and Augmentation upgrades.")}}),Q.overviewGen1=Object(M.createElement)("p",{display:"block"}),Q.overviewEstPop=Object(M.createElement)("p",{innerText:"Est. Synthoid Population: ",display:"inline-block",tooltip:"This is your Bladeburner division's estimate of how many Synthoids exist in your current city."}),Q.overviewEstPopHelpTip=Object(M.createElement)("div",{innerText:"?",class:"help-tip",clickListener:()=>{Object(g.dialogBoxCreate)("The success rate of your contracts/operations depends on the population of Synthoids in your current city. The success rate that is shown to you is only an estimate, and it is based on your Synthoid population estimate.

    Therefore, it is important that this Synthoid population estimate is accurate so that you have a better idea of your success rate for contracts/operations. Certain actions will increase the accuracy of your population estimate.

    The Synthoid populations of cities can change due to your actions or random events. If random events occur, they will be logged in the Bladeburner Console.")}}),Q.overviewEstComms=Object(M.createElement)("p",{innerText:"Est. Synthoid Communities: ",display:"inline-block",tooltip:"This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city."}),Q.overviewChaos=Object(M.createElement)("p",{innerText:"City Chaos: ",display:"inline-block",tooltip:"The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder."}),Q.overviewBonusTime=Object(M.createElement)("p",{innerText:"Bonus time: ",display:"inline-block",tooltip:"You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed."}),Q.overviewSkillPoints=Object(M.createElement)("p",{display:"block"}),Q.overviewAugSuccessMult=Object(M.createElement)("p",{display:"block"}),Q.overviewAugMaxStaminaMult=Object(M.createElement)("p",{display:"block"}),Q.overviewAugStaminaGainMult=Object(M.createElement)("p",{display:"block"}),Q.overviewAugAnalysisMult=Object(M.createElement)("p",{display:"block"}),Q.overviewDiv.appendChild(Q.overviewRank),Object(S.appendLineBreaks)(Q.overviewDiv,1),Q.overviewDiv.appendChild(Q.overviewStamina),Q.overviewDiv.appendChild(Q.overviewStaminaHelpTip),Q.overviewDiv.appendChild(Q.overviewGen1),Q.overviewDiv.appendChild(Q.overviewEstPop),Q.overviewDiv.appendChild(Q.overviewEstPopHelpTip),Object(S.appendLineBreaks)(Q.overviewDiv,1),Q.overviewDiv.appendChild(Q.overviewEstComms),Object(S.appendLineBreaks)(Q.overviewDiv,1),Q.overviewDiv.appendChild(Q.overviewChaos),Object(S.appendLineBreaks)(Q.overviewDiv,2),Q.overviewDiv.appendChild(Q.overviewBonusTime),Q.overviewDiv.appendChild(Q.overviewSkillPoints),Object(S.appendLineBreaks)(Q.overviewDiv,1),Q.overviewDiv.appendChild(Q.overviewAugSuccessMult),Q.overviewDiv.appendChild(Q.overviewAugMaxStaminaMult),Q.overviewDiv.appendChild(Q.overviewAugStaminaGainMult),Q.overviewDiv.appendChild(Q.overviewAugAnalysisMult),Object(S.appendLineBreaks)(Q.overviewDiv,1),Q.overviewDiv.appendChild(Object(M.createElement)("a",{innerHTML:"Travel",class:"a-link-button",display:"inline-block",clickListener:()=>{var e="bladeburner-travel-popup-cancel-btn",t=[];t.push(Object(M.createElement)("a",{innerText:"Cancel",class:"a-link-button",clickListener:()=>(Object(A.removeElementById)(e),!1)})),t.push(Object(M.createElement)("p",{innerText:"Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in for your Bladeburner duties does not affect your location in the game otherwise"}));for(var n=0;n(n.city=I[r],Object(A.removeElementById)(e),n.updateOverviewContent(),!1)}))}(this,n);Object(x.createPopup)(e,t)}}));if(Object(c.factionExists)("Bladeburners")){var e=c.Factions.Bladeburners;if(!(e instanceof l.Faction))throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button");Q.overviewDiv.appendChild(Object(M.createElement)("a",{innerText:"Faction",class:"a-link-button",display:"inline-block",tooltip:"Apply to the Bladeburner Faction, or go to the faction page if you are already a member",clickListener:()=>(e.isMember?(s.Engine.loadFactionContent(),Object(u.displayFactionContent)("Bladeburners")):this.rank>=25?(Object(u.joinFaction)(e),Object(g.dialogBoxCreate)("Congratulations! You were accepted into the Bladeburners faction"),Object(T.removeChildrenFromElement)(Q.overviewDiv),this.createOverviewContent()):Object(g.dialogBoxCreate)("You need a rank of 25 to join the Bladeburners Faction!"),!1)}))}Q.overviewDiv.appendChild(Object(M.createElement)("br")),Q.overviewDiv.appendChild(Object(M.createElement)("br")),this.updateOverviewContent()},X.prototype.createActionAndSkillsContent=function(){null==Q.currentTab&&(Q.currentTab="general"),Object(T.removeChildrenFromElement)(Q.actionAndSkillsDiv),Object(v.clearObject)(Q.generalActions),Object(v.clearObject)(Q.contracts),Object(v.clearObject)(Q.operations),Object(v.clearObject)(Q.blackops),Object(v.clearObject)(Q.skills);for(var e=Q.currentTab.toLowerCase(),t=["General","Contracts","Operations","BlackOps","Skills"],n=0;n(Q.currentTab=e[t].toLowerCase(),n.createActionAndSkillsContent(),!1)}))}(t,n,this,e);switch(Q.actionsAndSkillsDesc=Object(M.createElement)("p",{display:"block",margin:"4px",padding:"4px"}),Object(T.removeChildrenFromElement)(Q.actionsAndSkillsList),Q.actionsAndSkillsList=Object(M.createElement)("ul"),e){case"general":this.createGeneralActionsContent();break;case"contracts":this.createContractsContent();break;case"operations":this.createOperationsContent();break;case"blackops":this.createBlackOpsContent();break;case"skills":this.createSkillsContent();break;default:throw new Error("Invalid value for DomElems.currentTab in Bladeburner.createActionAndSkillsContent")}this.updateContent(),Q.actionAndSkillsDiv.appendChild(Q.actionsAndSkillsDesc),Q.actionAndSkillsDiv.appendChild(Q.actionsAndSkillsList)},X.prototype.createGeneralActionsContent=function(){if(null==Q.actionsAndSkillsList||null==Q.actionsAndSkillsDesc)throw new Error("Bladeburner.createGeneralActionsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");for(var e in Q.actionsAndSkillsDesc.innerText="These are generic actions that will assist you in your Bladeburner duties. They will not affect your Bladeburner rank in any way.",K)K.hasOwnProperty(e)&&(Q.generalActions[e]=Object(M.createElement)("div",{class:"bladeburner-action",name:e}),Q.actionsAndSkillsList.appendChild(Q.generalActions[e]))},X.prototype.createContractsContent=function(){if(null==Q.actionsAndSkillsList||null==Q.actionsAndSkillsDesc)throw new Error("Bladeburner.createContractsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");for(var e in Q.actionsAndSkillsDesc.innerHTML="Complete contracts in order to increase your Bladeburner rank and earn money. Failing a contract will cause you to lose HP, which can lead to hospitalization.

    You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more difficult, but grant more rank, experience, and money.",this.contracts)this.contracts.hasOwnProperty(e)&&(Q.contracts[e]=Object(M.createElement)("div",{class:"bladeburner-action",name:e}),Q.actionsAndSkillsList.appendChild(Q.contracts[e]))},X.prototype.createOperationsContent=function(){if(null==Q.actionsAndSkillsList||null==Q.actionsAndSkillsDesc)throw new Error("Bladeburner.createOperationsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");for(var e in Q.actionsAndSkillsDesc.innerHTML="Carry out operations for the Bladeburner division. Failing an operation will reduce your Bladeburner rank. It will also cause you to lose HP, which can lead to hospitalization. In general, operations are harder and more punishing than contracts, but are also more rewarding.

    Operations can affect the chaos level and Synthoid population of your current city. The exact effects vary between different Operations.

    For operations, you can use a team. You must first recruit team members. Having a larger team will improves your chances of success.

    You can unlock higher-level operations by successfully completing them. Higher-level operations are more difficult, but grant more rank and experience.",this.operations)this.operations.hasOwnProperty(e)&&(Q.operations[e]=Object(M.createElement)("div",{class:"bladeburner-action",name:e}),Q.actionsAndSkillsList.appendChild(Q.operations[e]))},X.prototype.createBlackOpsContent=function(){if(null==Q.actionsAndSkillsList||null==Q.actionsAndSkillsDesc)throw new Error("Bladeburner.createBlackOpsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");Q.actionsAndSkillsDesc.innerHTML="Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.

    Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.

    Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.";var e=[];for(var t in J)J.hasOwnProperty(t)&&e.push(J[t]);e.sort(function(e,t){return e.reqdRank-t.reqdRank});for(var n=e.length-1;n>=0;--n)null==this.blackops[[e[n].name]]&&0!==n&&null==this.blackops[[e[n-1].name]]||(Q.blackops[e[n].name]=Object(M.createElement)("div",{class:"bladeburner-action",name:e[n].name}),Q.actionsAndSkillsList.appendChild(Q.blackops[e[n].name]))},X.prototype.createSkillsContent=function(){if(null==Q.actionsAndSkillsList||null==Q.actionsAndSkillsDesc)throw new Error("Bladeburner.createSkillsContent called with either DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");Q.actionsAndSkillsDesc.innerHTML="You will gain one skill point every 3 ranks.

    Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different skills with each other is multiplicative.

    ";for(var e=Object.keys(this.skillMultipliers),t=0;t";break;case"successChanceStealth":Q.actionsAndSkillsDesc.innerHTML+="Stealth Success Chance: x"+n+"
    ";break;case"successChanceKill":Q.actionsAndSkillsDesc.innerHTML+="Retirement Success Chance: x"+n+"
    ";break;case"successChanceContract":Q.actionsAndSkillsDesc.innerHTML+="Contract Success Chance: x"+n+"
    ";break;case"successChanceOperation":Q.actionsAndSkillsDesc.innerHTML+="Operation Success Chance: x"+n+"
    ";break;case"successChanceEstimate":Q.actionsAndSkillsDesc.innerHTML+="Synthoid Data Estimate: x"+n+"
    ";break;case"actionTime":Q.actionsAndSkillsDesc.innerHTML+="Action Time: x"+n+"
    ";break;case"effHack":Q.actionsAndSkillsDesc.innerHTML+="Hacking Skill: x"+n+"
    ";break;case"effStr":Q.actionsAndSkillsDesc.innerHTML+="Strength: x"+n+"
    ";break;case"effDef":Q.actionsAndSkillsDesc.innerHTML+="Defense: x"+n+"
    ";break;case"effDex":Q.actionsAndSkillsDesc.innerHTML+="Dexterity: x"+n+"
    ";break;case"effAgi":Q.actionsAndSkillsDesc.innerHTML+="Agility: x"+n+"
    ";break;case"effCha":Q.actionsAndSkillsDesc.innerHTML+="Charisma: x"+n+"
    ";break;case"effInt":Q.actionsAndSkillsDesc.innerHTML+="Intelligence: x"+n+"
    ";break;case"stamina":Q.actionsAndSkillsDesc.innerHTML+="Stamina: x"+n+"
    ";break;case"money":Q.actionsAndSkillsDesc.innerHTML+="Contract Money: x"+n+"
    ";break;case"expGain":Q.actionsAndSkillsDesc.innerHTML+="Exp Gain: x"+n+"
    ";break;case"weaponAbility":case"gunAbility":break;default:console.log("Warning: Unrecognized SkillMult Key: "+e[t])}}for(var r in Q.skillPointsDisplay=Object(M.createElement)("p",{innerHTML:"
    Skill Points: "+Object(f.formatNumber)(this.skillPoints,0)+""}),Q.actionAndSkillsDiv.appendChild(Q.skillPointsDisplay),U)U.hasOwnProperty(r)&&(Q.skills[r]=Object(M.createElement)("div",{class:"bladeburner-action",name:r}),Q.actionsAndSkillsList.appendChild(Q.skills[r]))},X.prototype.updateContent=function(){this.updateOverviewContent(),this.updateActionAndSkillsContent()},X.prototype.updateOverviewContent=function(){m.routing.isOn(m.Page.Bladeburner)&&(Q.overviewRank.childNodes[0].nodeValue="Rank: "+Object(f.formatNumber)(this.rank,2),Q.overviewStamina.innerText="Stamina: "+Object(f.formatNumber)(this.stamina,3)+" / "+Object(f.formatNumber)(this.maxStamina,3),Q.overviewGen1.innerHTML="Stamina Penalty: "+Object(f.formatNumber)(100*(1-this.calculateStaminaPenalty()),1)+"%

    Team Size: "+Object(f.formatNumber)(this.teamSize,0)+"
    Team Members Lost: "+Object(f.formatNumber)(this.teamLost,0)+"

    Num Times Hospitalized: "+this.numHosp+"
    Money Lost From Hospitalizations: "+d.numeralWrapper.format(this.moneyLost,"$0.000a")+"

    Current City: "+this.city+"
    ",Q.overviewEstPop.childNodes[0].nodeValue="Est. Synthoid Population: "+d.numeralWrapper.format(this.getCurrentCity().popEst,"0.000a"),Q.overviewEstComms.childNodes[0].nodeValue="Est. Synthoid Communities: "+Object(f.formatNumber)(this.getCurrentCity().comms,0),Q.overviewChaos.childNodes[0].nodeValue="City Chaos: "+Object(f.formatNumber)(this.getCurrentCity().chaos),Q.overviewSkillPoints.innerText="Skill Points: "+Object(f.formatNumber)(this.skillPoints,0),Q.overviewBonusTime.childNodes[0].nodeValue="Bonus time: "+this.storedCycles/5,Q.overviewAugSuccessMult.innerText="Aug. Success Chance Mult: "+Object(f.formatNumber)(100*p.Player.bladeburner_success_chance_mult,1)+"%",Q.overviewAugMaxStaminaMult.innerText="Aug. Max Stamina Mult: "+Object(f.formatNumber)(100*p.Player.bladeburner_max_stamina_mult,1)+"%",Q.overviewAugStaminaGainMult.innerText="Aug. Stamina Gain Mult: "+Object(f.formatNumber)(100*p.Player.bladeburner_stamina_gain_mult,1)+"%",Q.overviewAugAnalysisMult.innerText="Aug. Field Analysis Mult: "+Object(f.formatNumber)(100*p.Player.bladeburner_analysis_mult,1)+"%")},X.prototype.updateActionAndSkillsContent=function(){switch(null==Q.currentTab&&(Q.currentTab="general"),Q.currentTab.toLowerCase()){case"general":for(var e=Object.keys(Q.generalActions),t=0;t";var d=Object.keys(Q.skills);for(t=0;t(this.action.type=q[t.name],this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)}));Object(S.appendLineBreaks)(e,2),e.appendChild(Object(M.createElement)("pre",{innerHTML:t.desc,display:"inline-block"}))},X.prototype.updateContractsUIElement=function(e,t){Object(T.removeChildrenFromElement)(e);var n=e.classList.contains(B),r=t.getSuccessChance(this,{est:!0});if(e.appendChild(Object(M.createElement)("h2",{innerText:n?t.name+" (IN PROGRESS - "+Object(f.formatNumber)(this.actionTimeCurrent,0)+" / "+Object(f.formatNumber)(this.actionTimeToComplete,0)+")":t.name,display:"inline-block"})),n){var a=this.actionTimeCurrent/this.actionTimeToComplete;e.appendChild(Object(M.createElement)("p",{display:"block",innerText:Object(E.createProgressBarText)({progress:a})}))}else e.appendChild(Object(M.createElement)("a",{innerText:"Start",class:"a-link-button",padding:"3px",margin:"3px",clickListener:()=>(this.action.type=q.Contract,this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)}));var i=t.level>=t.maxLevel;Object(S.appendLineBreaks)(e,2),e.appendChild(Object(M.createElement)("pre",{display:"inline-block",innerText:"Level: "+t.level+" / "+t.maxLevel,tooltip:t.getSuccessesNeededForNextLevel(3)+" successes needed for next level"})),e.appendChild(Object(M.createElement)("a",{class:i?"a-link-button-inactive":"a-link-button",innerHTML:"↑",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the contract":"",display:"inline",clickListener:()=>(++t.level,n&&this.startAction(this.action),this.updateContractsUIElement(e,t),!1)})),e.appendChild(Object(M.createElement)("a",{class:t.level<=1?"a-link-button-inactive":"a-link-button",innerHTML:"↓",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the contract":"",display:"inline",clickListener:()=>(--t.level,n&&this.startAction(this.action),this.updateContractsUIElement(e,t),!1)}));var o=t.getActionTime(this);Object(S.appendLineBreaks)(e,2),e.appendChild(Object(M.createElement)("pre",{display:"inline-block",innerHTML:t.desc+"\n\n"+`Estimated success chance: ${Object(f.formatNumber)(100*r,1)}% ${t.isStealth?R:""}${t.isKill?N:""}\n`+"Time Required (s): "+Object(f.formatNumber)(o,0)+"\nContracts remaining: "+Math.floor(t.count)+"\nSuccesses: "+t.successes+"\nFailures: "+t.failures})),e.appendChild(Object(M.createElement)("br"));var s="bladeburner-"+t.name+"-autolevel-checkbox";e.appendChild(Object(M.createElement)("label",{for:s,innerText:"Autolevel",color:"white",tooltip:"Automatically increase contract level when possible"}));const l=Object(M.createElement)("div",{class:"bbcheckbox"}),c=Object(M.createElement)("input",{type:"checkbox",id:s,checked:t.autoLevel,changeListener:()=>{t.autoLevel=c.checked}}),u=Object(M.createElement)("label",{for:s});l.appendChild(c),l.appendChild(u),e.appendChild(l)},X.prototype.updateOperationsUIElement=function(e,t){Object(T.removeChildrenFromElement)(e);var n=e.classList.contains(B),r=t.getSuccessChance(this,{est:!0});if(e.appendChild(Object(M.createElement)("h2",{innerText:n?t.name+" (IN PROGRESS - "+Object(f.formatNumber)(this.actionTimeCurrent,0)+" / "+Object(f.formatNumber)(this.actionTimeToComplete,0)+")":t.name,display:"inline-block"})),n){var a=this.actionTimeCurrent/this.actionTimeToComplete;e.appendChild(Object(M.createElement)("p",{display:"block",innerText:Object(E.createProgressBarText)({progress:a})}))}else e.appendChild(Object(M.createElement)("a",{innerText:"Start",class:"a-link-button",margin:"3px",padding:"3px",clickListener:()=>(this.action.type=q.Operation,this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)})),e.appendChild(Object(M.createElement)("a",{innerText:"Set Team Size (Curr Size: "+Object(f.formatNumber)(t.teamCount,0)+")",class:"a-link-button",margin:"3px",padding:"3px",clickListener:()=>{var n="bladeburner-operation-set-team-size-popup",r=Object(M.createElement)("p",{innerText:"Enter the amount of team members you would like to take on these operations. If you do not have the specified number of team members, then as many as possible will be used. Note that team members may be lost during operations."}),a=Object(M.createElement)("input",{type:"number",placeholder:"Team Members"}),i=Object(M.createElement)("a",{innerText:"Confirm",class:"a-link-button",clickListener:()=>{var r=Math.round(parseFloat(a.value));return isNaN(r)?Object(g.dialogBoxCreate)("Invalid value entered for number of Team Members (must be numeric)"):(t.teamCount=r,this.updateOperationsUIElement(e,t)),Object(A.removeElementById)(n),!1}}),o=Object(M.createElement)("a",{innerText:"Cancel",class:"a-link-button",clickListener:()=>(Object(A.removeElementById)(n),!1)});Object(x.createPopup)(n,[r,a,i,o])}}));var i=t.level>=t.maxLevel;Object(S.appendLineBreaks)(e,2),e.appendChild(Object(M.createElement)("pre",{display:"inline-block",innerText:"Level: "+t.level+" / "+t.maxLevel,tooltip:t.getSuccessesNeededForNextLevel(2.5)+" successes needed for next level"})),e.appendChild(Object(M.createElement)("a",{class:i?"a-link-button-inactive":"a-link-button",innerHTML:"↑",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the Operation":"",display:"inline",clickListener:()=>(++t.level,n&&this.startAction(this.action),this.updateOperationsUIElement(e,t),!1)})),e.appendChild(Object(M.createElement)("a",{class:t.level<=1?"a-link-button-inactive":"a-link-button",innerHTML:"↓",padding:"2px",margin:"2px",tooltip:n?"WARNING: changing the level will restart the Operation":"",display:"inline",clickListener:()=>(--t.level,n&&this.startAction(this.action),this.updateOperationsUIElement(e,t),!1)}));t.getDifficulty();var o=t.getActionTime(this);Object(S.appendLineBreaks)(e,2),e.appendChild(Object(M.createElement)("pre",{display:"inline-block",innerHTML:t.desc+"\n\n"+`Estimated success chance: ${Object(f.formatNumber)(100*r,1)}% ${t.isStealth?R:""}${t.isKill?N:""}\n`+"Time Required(s): "+Object(f.formatNumber)(o,0)+"\nOperations remaining: "+Math.floor(t.count)+"\nSuccesses: "+t.successes+"\nFailures: "+t.failures})),e.appendChild(Object(M.createElement)("br"));var s="bladeburner-"+t.name+"-autolevel-checkbox";e.appendChild(Object(M.createElement)("label",{for:s,innerText:"Autolevel",color:"white",tooltip:"Automatically increase operation level when possible"}));const l=Object(M.createElement)("div",{class:"bbcheckbox"}),c=Object(M.createElement)("input",{type:"checkbox",id:s,checked:t.autoLevel,changeListener:()=>{t.autoLevel=c.checked}}),u=Object(M.createElement)("label",{for:s});l.appendChild(c),l.appendChild(u),e.appendChild(l)},X.prototype.updateBlackOpsUIElement=function(e,t){Object(T.removeChildrenFromElement)(e);var n=e.classList.contains(B),r=null!=this.blackops[t.name],a=t.getSuccessChance(this,{est:!0}),i=(t.getDifficulty(),t.getActionTime(this)),o=this.rank>=t.reqdRank;if(r)e.appendChild(Object(M.createElement)("h2",{innerText:t.name+" (COMPLETED)",display:"block"}));else{if(e.appendChild(Object(M.createElement)("h2",{innerText:n?t.name+" (IN PROGRESS - "+Object(f.formatNumber)(this.actionTimeCurrent,0)+" / "+Object(f.formatNumber)(this.actionTimeToComplete,0)+")":t.name,display:"inline-block"})),n){var s=this.actionTimeCurrent/this.actionTimeToComplete;e.appendChild(Object(M.createElement)("p",{display:"block",innerText:Object(E.createProgressBarText)({progress:s})}))}else e.appendChild(Object(M.createElement)("a",{innerText:"Start",margin:"3px",padding:"3px",class:o?"a-link-button":"a-link-button-inactive",clickListener:()=>(this.action.type=q.BlackOperation,this.action.name=t.name,this.startAction(this.action),this.updateActionAndSkillsContent(),!1)})),e.appendChild(Object(M.createElement)("a",{innerText:"Set Team Size (Curr Size: "+Object(f.formatNumber)(t.teamCount,0)+")",class:"a-link-button",margin:"3px",padding:"3px",clickListener:()=>{var n="bladeburner-operation-set-team-size-popup",r=Object(M.createElement)("p",{innerText:"Enter the amount of team members you would like to take on this BlackOp. If you do not have the specified number of team members, then as many as possible will be used. Note that team members may be lost during operations."}),a=Object(M.createElement)("input",{type:"number",placeholder:"Team Members"}),i=Object(M.createElement)("a",{innerText:"Confirm",class:"a-link-button",clickListener:()=>{var r=Math.round(parseFloat(a.value));return isNaN(r)?Object(g.dialogBoxCreate)("Invalid value entered for number of Team Members (must be numeric)"):(t.teamCount=r,this.updateBlackOpsUIElement(e,t)),Object(A.removeElementById)(n),!1}}),o=Object(M.createElement)("a",{innerText:"Cancel",class:"a-link-button",clickListener:()=>(Object(A.removeElementById)(n),!1)});Object(x.createPopup)(n,[r,a,i,o])}}));Object(S.appendLineBreaks)(e,2),e.appendChild(Object(M.createElement)("p",{display:"inline-block",innerHTML:"
    "+t.desc+"

    "})),e.appendChild(Object(M.createElement)("p",{display:"block",color:o?"white":"red",innerHTML:"Required Rank: "+Object(f.formatNumber)(t.reqdRank,0)+"
    "})),e.appendChild(Object(M.createElement)("p",{display:"inline-block",innerHTML:`Estimated Success Chance: ${Object(f.formatNumber)(100*a,1)}% ${t.isStealth?R:""}${t.isKill?N:""}\n`+"Time Required(s): "+Object(f.formatNumber)(i,0)}))}},X.prototype.updateSkillsUIElement=function(e,t){Object(T.removeChildrenFromElement)(e);var n=t.name,r=0;this.skills[n]&&!isNaN(this.skills[n])&&(r=this.skills[n]);var a=t.calculateCost(r);e.appendChild(Object(M.createElement)("h2",{innerText:t.name+" (Lvl "+r+")",display:"inline-block"}));var i=this.skillPoints>=a,o=!!t.maxLvl&&r>=t.maxLvl;e.appendChild(Object(M.createElement)("a",{innerText:"Level",display:"inline-block",class:i&&!o?"a-link-button":"a-link-button-inactive",margin:"3px",padding:"3px",clickListener:()=>{if(!(this.skillPoints100&&this.consoleLogs.shift()),null!=t&&null!=Q.consoleDiv&&(e("#bladeubrner-console-input-row").before(''+t+""),Q.consoleTable.childNodes.length>100&&Q.consoleTable.removeChild(Q.consoleTable.firstChild),this.updateConsoleScroll())},X.prototype.updateConsoleScroll=function(){Q.consoleDiv.scrollTop=Q.consoleDiv.scrollHeight},X.prototype.resetConsoleInput=function(){Q.consoleInput.value=""},X.prototype.clearConsole=function(){for(;Q.consoleTable.childNodes.length>1;)Q.consoleTable.removeChild(Q.consoleTable.firstChild);this.consoleLogs.length=0},X.prototype.log=function(e){this.postToConsole(`[${Object(P.getTimestamp)()}] ${e}`)},X.prototype.executeConsoleCommands=function(e){try{this.consoleHistory[this.consoleHistory.length-1]!=e&&(this.consoleHistory.push(e),this.consoleHistory.length>50&&this.consoleHistory.splice(0,1)),L=this.consoleHistory.length;for(var t=e.split(";"),n=0;n"))}},X.prototype.executeLogConsoleCommand=function(e){if(e.length<3)return this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"),void this.postToConsole("Use 'help log' for more details and examples");var t=!0;switch(e[1].toLowerCase().includes("d")&&(t=!1),e[2].toLowerCase()){case"general":case"gen":this.logging.general=t,this.log("Logging "+(t?"enabled":"disabled")+" for general actions");break;case"contract":case"contracts":this.logging.contracts=t,this.log("Logging "+(t?"enabled":"disabled")+" for Contracts");break;case"ops":case"op":case"operations":case"operation":this.logging.ops=t,this.log("Logging "+(t?"enabled":"disabled")+" for Operations");break;case"blackops":case"blackop":case"black operations":case"black operation":this.logging.blackops=t,this.log("Logging "+(t?"enabled":"disabled")+" for BlackOps");break;case"event":case"events":this.logging.events=t,this.log("Logging "+(t?"enabled":"disabled")+" for events");break;case"all":this.logging.general=t,this.logging.contracts=t,this.logging.ops=t,this.logging.blackops=t,this.logging.events=t,this.log("Logging "+(t?"enabled":"disabled")+" for everything");break;default:this.postToConsole("Invalid action/event type specified: "+e[2]),this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]")}},X.prototype.executeSkillConsoleCommand=function(e){switch(e.length){case 1:this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"),this.postToConsole("Use 'help skill' for more info");break;case 2:if("list"===e[1].toLowerCase()){this.postToConsole("Skills: ");for(var t=Object.keys(U),n=0;n=c?(this.skillPoints-=c,this.upgradeSkill(r),this.log(r.name+" upgraded to Level "+this.skills[s]),this.createActionAndSkillsContent()):this.postToConsole("You do not have enough Skill Points to upgrade this. You need "+Object(f.formatNumber)(c,0))}else this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"),this.postToConsole("Use 'help skill' for more info");break;default:this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"),this.postToConsole("Use 'help skill' for more info")}},X.prototype.executeStartConsoleCommand=function(e){if(3!==e.length)return this.postToConsole("Invalid usage of 'start' console command: start [type] [name]"),void this.postToConsole("Use 'help start' for more info");var t=e[2];switch(e[1].toLowerCase()){case"general":case"gen":null!=K[t]?(this.action.type=q[t],this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid action name specified: "+e[2]);break;case"contract":case"contracts":null!=this.contracts[t]?(this.action.type=q.Contract,this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid contract name specified: "+e[2]);break;case"ops":case"op":case"operations":case"operation":null!=this.operations[t]?(this.action.type=q.Operation,this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid Operation name specified: "+e[2]);break;case"blackops":case"blackop":case"black operations":case"black operation":null!=J[t]?(this.action.type=q.BlackOperation,this.action.name=t,this.startAction(this.action),this.updateActionAndSkillsContent()):this.postToConsole("Invalid BlackOp name specified: "+e[2]);break;default:this.postToConsole("Invalid action/event type specified: "+e[1]),this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]")}},X.prototype.getActionIdFromTypeAndName=function(e="",t=""){if(""===e||""===t)return null;var n=new $,r=e.toLowerCase().trim(),a=t.toLowerCase().trim();switch(r){case"contract":case"contracts":case"contr":return n.type=q.Contract,this.contracts.hasOwnProperty(t)?(n.name=t,n):null;case"operation":case"operations":case"op":case"ops":return n.type=q.Operation,this.operations.hasOwnProperty(t)?(n.name=t,n):null;case"blackoperation":case"black operation":case"black operations":case"black op":case"black ops":case"blackop":case"blackops":return n.type=q.BlackOp,J.hasOwnProperty(t)?(n.name=t,n):null;case"general":case"general action":case"gen":break;default:return null}if(r.startsWith("gen")){switch(a){case"training":n.type=q.Training,n.name="Training";break;case"recruitment":case"recruit":n.type=q.Recruitment,n.name="Recruitment";break;case"field analysis":case"fieldanalysis":n.type=q["Field Analysis"],n.name="Field Analysis";break;case"diplomacy":n.type=q.Diplomacy,n.name="Diplomacy";break;case"hyperbolic regeneration chamber":n.type=q["Hyperbolic Regeneration Chamber"],n.name="Hyperbolic Regeneration Chamber";break;default:return null}return n}},X.prototype.getTypeAndNameFromActionId=function(e){var t={};let n=Object.keys(q);for(let r=0;rthis.rank)return n.log(`Failed to start Black Op ${a.name} due to insufficient rank`),!1;if(null!=this.blackops[a.name])return n.log(`Failed to start Black Op ${a.name} because its already been completed`),!1;var i=[];for(const e in J)J.hasOwnProperty(e)&&i.push(e);i.sort(function(e,t){return J[e].reqdRank-J[t].reqdRank});let e=i.indexOf(a.name);if(-1===e)return n.log("ERROR: Invalid Black Operation name passed into bladeburner.startAction(). Note that this name is case-sensitive & whitespace-sensitive"),!1;if(e>0&&null==this.blackops[i[e-1]])return n.log(`ERROR: Cannot attempt Black Operation ${a.name} because you have not done the preceding one`),!1}try{return this.startAction(a),n.shouldLog("startAction")&&n.log("Starting bladeburner action with type "+e+" and name "+t),!0}catch(r){return this.resetAction(),n.log("ERROR: bladeburner.startAction() failed to start action of type "+e+" due to invalid name: "+t+"Note that this name is case-sensitive and whitespace-sensitive"),!1}},X.prototype.getActionTimeNetscriptFn=function(e,t,n){var r="ERROR: bladeburner.getActionTime() failed due to an invalid action specified. Type: "+e+", Name: "+t+". Note that for contracts and operations, the name of the operation is case-sensitive.",a=this.getActionIdFromTypeAndName(e,t);if(null==a)return n.log(r),-1;var i=this.getActionObject(a);if(null==i)return n.log(r),-1;switch(a.type){case q.Contract:case q.Operation:case q.BlackOp:case q.BlackOperation:return i.getActionTime(this);case q.Training:case q["Field Analysis"]:case q.FieldAnalysis:return 30;case q.Recruitment:return this.getRecruitmentTime();default:return n.log(r),-1}},X.prototype.getActionEstimatedSuccessChanceNetscriptFn=function(e,t,n){var r="ERROR: bladeburner.getActionEstimatedSuccessChance() failed due to an invalid action specified. Type: "+e+", Name: "+t+". Note that for contracts and operations, the name of the operation is case-sensitive.",a=this.getActionIdFromTypeAndName(e,t);if(null==a)return n.log(r),-1;var i=this.getActionObject(a);if(null==i)return n.log(r),-1;switch(a.type){case q.Contract:case q.Operation:case q.BlackOp:case q.BlackOperation:return i.getSuccessChance(this,{est:!0});case q.Training:case q["Field Analysis"]:case q.FieldAnalysis:return 1;case q.Recruitment:return this.getRecruitmentSuccessChance();default:return n.log(r),-1}},X.prototype.getActionCountRemainingNetscriptFn=function(e,t,n){var r="ERROR: bladeburner.getActionCountRemaining() failed due to an invalid action specified. Type: "+e+", Name: "+t+". Note that for contracts and operations, the name of the operation is case-sensitive.",a=this.getActionIdFromTypeAndName(e,t);if(null==a)return n.log(r),-1;var i=this.getActionObject(a);if(null==i)return n.log(r),-1;switch(a.type){case q.Contract:case q.Operation:return Math.floor(i.count);case q.BlackOp:case q.BlackOperation:return null!=this.blackops[t]?0:1;case q.Training:case q["Field Analysis"]:case q.FieldAnalysis:return 1/0;default:return n.log(r),-1}},X.prototype.getSkillLevelNetscriptFn=function(e,t){var n="ERROR: bladeburner.getSkillLevel() failed due to an invalid skill specified: "+e+". Note that the name of the skill is case-sensitive";return""===e?-1:U.hasOwnProperty(e)?null==this.skills[e]?0:this.skills[e]:(t.log(n),-1)},X.prototype.getSkillUpgradeCostNetscriptFn=function(e,t){var n="ERROR: bladeburner.getSkillUpgradeCostNetscriptFn() failed due to an invalid skill specified: "+e+". Note that the name of the skill is case-sensitive";if(""===e)return-1;if(!U.hasOwnProperty(e))return t.log(n),-1;var r=U[e];return null==this.skills[e]?r.calculateCost(0):r.calculateCost(this.skills[e])},X.prototype.upgradeSkillNetscriptFn=function(e,t){var n="ERROR: bladeburner.upgradeSkill() failed due to an invalid skill specified: "+e+". Note that the name of the skill is case-sensitive";if(!U.hasOwnProperty(e))return t.log(n),!1;var r=U[e],a=0;this.skills[e]&&!isNaN(this.skills[e])&&(a=this.skills[e]);var i=r.calculateCost(a);return r.maxLvl&&a>=r.maxLvl?(t.shouldLog("upgradeSkill")&&t.log(`bladeburner.upgradeSkill() failed because ${e} is already maxed`),!1):this.skillPoints=25?(Object(u.joinFaction)(t),e.shouldLog("joinBladeburnerFaction")&&e.log("Joined Bladeburners Faction"),m.routing.isOn(m.Page.Bladeburner)&&(Object(T.removeChildrenFromElement)(Q.overviewDiv),this.createOverviewContent()),!0):(e.shouldLog("joinBladeburnerFaction")&&e.log("Failed to join Bladeburners Faction because you do not have the required 25 rank"),!1))},X.prototype.toJSON=function(){return Object(_.Generic_toJSON)("Bladeburner",this)},X.fromJSON=function(e){return Object(_.Generic_fromJSON)(X,e.data)},_.Reviver.constructors.Bladeburner=X}).call(this,n(85))},function(e,t,n){"use strict";n.d(t,"d",function(){return m}),n.d(t,"c",function(){return g}),n.d(t,"b",function(){return y}),n.d(t,"a",function(){return d});var r=n(14),a=n(0),i=n(19),o=n(33),s=n(49),l=n(27),c=n(3),u=n(36),p=n(29);const h=["Start","GoToCharacterPage","CharacterPage","CharacterGoToTerminalPage","TerminalIntro","TerminalHelp","TerminalLs","TerminalScan","TerminalScanAnalyze1","TerminalScanAnalyze2","TerminalConnect","TerminalAnalyze","TerminalNuke","TerminalManualHack","TerminalHackingMechanics","TerminalCreateScript","TerminalTypeScript","TerminalFree","TerminalRunScript","TerminalGoToActiveScriptsPage","ActiveScriptsPage","ActiveScriptsToTerminal","TerminalTailScript","GoToHacknetNodesPage","HacknetNodesIntroduction","HacknetNodesGoToWorldPage","WorldDescription","TutorialPageInfo","End"],m={};for(let e=0;em.Start&&(d.currStep-=1);_()}(),!1}),Object(l.clearEventListeners)("interactive-tutorial-next").addEventListener("click",function(){return y(),!1}),_()}function _(){if(d.isRunning){var e=Object(l.clearEventListeners)("terminal-menu-link"),t=Object(l.clearEventListeners)("stats-menu-link"),n=Object(l.clearEventListeners)("active-scripts-menu-link"),a=Object(l.clearEventListeners)("hacknet-nodes-menu-link"),i=Object(l.clearEventListeners)("city-menu-link"),o=Object(l.clearEventListeners)("tutorial-menu-link");e.removeAttribute("class"),t.removeAttribute("class"),n.removeAttribute("class"),a.removeAttribute("class"),i.removeAttribute("class"),o.removeAttribute("class");var s=document.getElementById("interactive-tutorial-next");switch(d.currStep){case m.Start:r.Engine.loadTerminalContent(),b("Welcome to Bitburner, a cyberpunk-themed incremental RPG! The game takes place in a dark, dystopian future...The year is 2077...

    This tutorial will show you the basics of the game. You may skip the tutorial at any time."),s.style.display="inline-block";break;case m.GoToCharacterPage:r.Engine.loadTerminalContent(),b("Let's start by heading to the Stats page. Click the 'Stats' tab on the main navigation menu (left-hand side of the screen)"),s.style.display="none",t.setAttribute("class","flashing-button"),t.addEventListener("click",function(){return r.Engine.loadCharacterContent(),y(),!1});break;case m.CharacterPage:r.Engine.loadCharacterContent(),b("The Stats page shows a lot of important information about your progress, such as your skills, money, and bonuses/multipliers. "),s.style.display="inline-block";break;case m.CharacterGoToTerminalPage:r.Engine.loadCharacterContent(),b("Let's head to your computer's terminal by clicking the 'Terminal' tab on the main navigation menu."),s.style.display="none",e.setAttribute("class","flashing-button"),e.addEventListener("click",function(){return r.Engine.loadTerminalContent(),y(),!1});break;case m.TerminalIntro:r.Engine.loadTerminalContent(),b("The Terminal is used to interface with your home computer as well as all of the other machines around the world."),s.style.display="inline-block";break;case m.TerminalHelp:r.Engine.loadTerminalContent(),b("Let's try it out. Start by entering the 'help' command into the Terminal (Don't forget to press Enter after typing the command)"),s.style.display="none";break;case m.TerminalLs:r.Engine.loadTerminalContent(),b("The 'help' command displays a list of all available Terminal commands, how to use them, and a description of what they do.

    Let's try another command. Enter the 'ls' command"),s.style.display="none";break;case m.TerminalScan:r.Engine.loadTerminalContent(),b("'ls' is a basic command that shows all of the contents (programs/scripts) on the computer. Right now, it shows that you have a program called 'NUKE.exe' on your computer. We'll get to what this does later.

    Using your home computer's terminal, you can connect to other machines throughout the world. Let's do that now by first entering the 'scan' command."),s.style.display="none";break;case m.TerminalScanAnalyze1:r.Engine.loadTerminalContent(),b("The 'scan' command shows all available network connections. In other words, it displays a list of all servers that can be connected to from your current machine. A server is identified by either its IP or its hostname.

    That's great and all, but there's so many servers. Which one should you go to? The 'scan-analyze' command gives some more detailed information about servers on the network. Try it now"),s.style.display="none";break;case m.TerminalScanAnalyze2:r.Engine.loadTerminalContent(),b("You just ran 'scan-analyze' with a depth of one. This command shows more detailed information about each server that you can connect to (servers that are a distance of one node away).

    It is also possible to run 'scan-analyze' with a higher depth. Let's try a depth of two with the following command: 'scan-analyze 2'."),s.style.display="none";break;case m.TerminalConnect:r.Engine.loadTerminalContent(),b("Now you can see information about all servers that are up to two nodes away, as well as figure out how to navigate to those servers through the network. You can only connect to a server that is one node away. To connect to a machine, use the 'connect [ip/hostname]' command. You can type in the ip or the hostname, but dont use both.

    From the results of the 'scan-analyze' command, we can see that the 'foodnstuff' server is only one node away. Let's connect so it now using: 'connect foodnstuff'"),s.style.display="none";break;case m.TerminalAnalyze:r.Engine.loadTerminalContent(),b("You are now connected to another machine! What can you do now? You can hack it!

    In the year 2077, currency has become digital and decentralized. People and corporations store their money on servers and computers. Using your hacking abilities, you can hack servers to steal money and gain experience.

    Before you try to hack a server, you should run diagnostics using the 'analyze' command"),s.style.display="none";break;case m.TerminalNuke:r.Engine.loadTerminalContent(),b("When the 'analyze' command finishes running it will show useful information about hacking the server.

    For this server, the required hacking skill is only 1, which means you can hack it right now. However, in order to hack a server you must first gain root access. The 'NUKE.exe' program that we saw earlier on your home computer is a virus that will grant you root access to a machine if there are enough open ports.

    The 'analyze' results shows that there do not need to be any open ports on this machine for the NUKE virus to work, so go ahead and run the virus using the 'run NUKE.exe' command."),s.style.display="none";break;case m.TerminalManualHack:r.Engine.loadTerminalContent(),b("You now have root access! You can hack the server using the 'hack' command. Try doing that now."),s.style.display="none";break;case m.TerminalHackingMechanics:r.Engine.loadTerminalContent(),b("You are now attempting to hack the server. Note that performing a hack takes time and only has a certain percentage chance of success. This time and success chance is determined by a variety of factors, including your hacking skill and the server's security level.

    If your attempt to hack the server is successful, you will steal a certain percentage of the server's total money. This percentage is affected by your hacking skill and the server's security level.

    The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money, then you will encounter diminishing returns in your hacking."),s.style.display="inline-block";break;case m.TerminalCreateScript:r.Engine.loadTerminalContent(),b("Hacking is the core mechanic of the game and is necessary for progressing. However, you don't want to be hacking manually the entire time. You can automate your hacking by writing scripts!

    To create a new script or edit an existing one, you can use the 'nano' command. Scripts must end with the '.script' extension. Let's make a script now by entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)"),s.style.display="none";break;case m.TerminalTypeScript:r.Engine.loadScriptEditorContent("foodnstuff.script",""),b("This is the script editor. You can use it to program your scripts. Scripts are written in the Netscript language, a programming language created for this game. There are details about the Netscript language in the documentation, which can be accessed in the 'Tutorial' tab on the main navigation menu. I highly suggest you check it out after this tutorial. For now, just copy and paste the following code into the script editor:

    while(true) {
      hack('foodnstuff');
    }

    For anyone with basic programming experience, this code should be straightforward. This script will continuously hack the 'foodnstuff' server.

    To save and close the script editor, press the button in the bottom left, or press ctrl + b."),s.style.display="none";break;case m.TerminalFree:r.Engine.loadTerminalContent(),b("Now we'll run the script. Scripts require a certain amount of RAM to run, and can be run on any machine which you have root access to. Different servers have different amounts of RAM. You can also purchase more RAM for your home server.

    To check how much RAM is available on this machine, enter the 'free' command."),s.style.display="none";break;case m.TerminalRunScript:r.Engine.loadTerminalContent(),b("We have 16GB of free RAM on this machine, which is enough to run our script. Let's run our script using 'run foodnstuff.script'."),s.style.display="none";break;case m.TerminalGoToActiveScriptsPage:r.Engine.loadTerminalContent(),b("Your script is now running! The script might take a few seconds to 'fully start up'. Your scripts will continuously run in the background and will automatically stop if the code ever completes (the 'foodnstuff.script' will never complete because it runs an infinite loop).

    These scripts can passively earn you income and hacking experience. Your scripts will also earn money and experience while you are offline, although at a much slower rate.

    Let's check out some statistics for our running scripts by clicking the 'Active Scripts' link in the main navigation menu."),s.style.display="none",n.setAttribute("class","flashing-button"),n.addEventListener("click",function(){return r.Engine.loadActiveScriptsContent(),y(),!1});break;case m.ActiveScriptsPage:r.Engine.loadActiveScriptsContent(),b("This page displays stats/information about all of your scripts that are running across every existing server. You can use this to gauge how well your scripts are doing. Let's go back to the Terminal now using the 'Terminal' link."),s.style.display="none",e.setAttribute("class","flashing-button"),e.addEventListener("click",function(){return r.Engine.loadTerminalContent(),y(),!1});break;case m.ActiveScriptsToTerminal:r.Engine.loadTerminalContent(),b("One last thing about scripts, each active script contains logs that detail what it's doing. We can check these logs using the 'tail' command. Do that now for the script we just ran by typing 'tail foodnstuff.script'"),s.style.display="none";break;case m.TerminalTailScript:r.Engine.loadTerminalContent(),b("The log for this script won't show much right now (it might show nothing at all) because it just started running...but check back again in a few minutes!

    This pretty much covers the basics of hacking. To learn more about writing scripts using the Netscript language, select the 'Tutorial' link in the main navigation menu to look at the documentation. If you are an experienced JavaScript developer, I would highly suggest you check out the section on NetscriptJS/Netscript 2.0.

    For now, let's move on to something else!"),s.style.display="inline-block";break;case m.GoToHacknetNodesPage:r.Engine.loadTerminalContent(),b("Hacking is not the only way to earn money. One other way to passively earn money is by purchasing and upgrading Hacknet Nodes. Let's go to the 'Hacknet Nodes' page through the main navigation menu now."),s.style.display="none",a.setAttribute("class","flashing-button"),a.addEventListener("click",function(){return r.Engine.loadHacknetNodesContent(),y(),!1});break;case m.HacknetNodesIntroduction:r.Engine.loadHacknetNodesContent(),b("From this page you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now."),s.style.display="none";break;case m.HacknetNodesGoToWorldPage:r.Engine.loadHacknetNodesContent(),b("You just purchased a Hacknet Node! This Hacknet Node will passively earn you money over time, both online and offline. When you get enough money, you can upgrade your newly-purchased Hacknet Node below.

    Let's go to the 'City' page through the main navigation menu."),s.style.display="none",i.setAttribute("class","flashing-button"),i.addEventListener("click",function(){return r.Engine.loadLocationContent(),y(),!1});break;case m.WorldDescription:r.Engine.loadLocationContent(),b("This page lists all of the different locations you can currently travel to. Each location has something that you can do. There's a lot of content out in the world, make sure you explore and discover!

    Lastly, click on the 'Tutorial' link in the main navigation menu."),s.style.display="none",o.setAttribute("class","flashing-button"),o.addEventListener("click",function(){return r.Engine.loadTutorialContent(),y(),!1});break;case m.TutorialPageInfo:r.Engine.loadTutorialContent(),b("This page contains a lot of different documentation about the game's content and mechanics. I know it's a lot, but I highly suggest you read (or at least skim) through this before you start playing. That's the end of the tutorial. Hope you enjoy the game!"),s.style.display="inline-block",s.innerHTML="Finish Tutorial";break;case m.End:f();break;default:throw new Error("Invalid tutorial step")}!0===d.stepIsDone[d.currStep]&&(s.style.display="inline-block")}else console.log("Interactive Tutorial not running")}function y(){d.currStep===m.GoToCharacterPage&&document.getElementById("stats-menu-link").removeAttribute("class"),d.currStep===m.CharacterGoToTerminalPage&&document.getElementById("terminal-menu-link").removeAttribute("class"),d.currStep===m.TerminalGoToActiveScriptsPage&&document.getElementById("active-scripts-menu-link").removeAttribute("class"),d.currStep===m.ActiveScriptsPage&&document.getElementById("terminal-menu-link").removeAttribute("class"),d.currStep===m.GoToHacknetNodesPage&&document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"),d.currStep===m.HacknetNodesGoToWorldPage&&document.getElementById("city-menu-link").removeAttribute("class"),d.currStep===m.WorldDescription&&document.getElementById("tutorial-menu-link").removeAttribute("class"),d.stepIsDone[d.currStep]=!0,d.currStep
    Getting Started GuideDocumentation

    The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. To read it, go to Terminal and enter

    cat hackers-starting-handbook.lit"}),n=Object(c.createElement)("a",{class:"a-link-button",float:"right",padding:"6px",innerText:"Got it!",clickListener:()=>{Object(p.removeElementById)(e)}});Object(u.createPopup)(e,[t,n]),a.Player.getHomeComputer().messages.push("hackers-starting-handbook.lit")}function b(e){var t=document.getElementById("interactive-tutorial-text");if(null==t)throw new Error("Could not find text box");t.innerHTML=e,t.parentElement.scrollTop=0}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(51),a=n(92);t.removeChildrenFromElement=function(e){if(null!==e)try{const t=r.isString(e)?a.getElementById(e):e;if(t instanceof Element)for(;null!==t.firstChild;)t.removeChild(t.firstChild)}catch(e){return void console.debug(e)}}},function(e,t,n){"use strict";n.d(t,"i",function(){return S}),n.d(t,"a",function(){return M}),n.d(t,"e",function(){return w}),n.d(t,"g",function(){return N}),n.d(t,"c",function(){return I}),n.d(t,"b",function(){return D}),n.d(t,"h",function(){return B}),n.d(t,"d",function(){return L}),n.d(t,"f",function(){return W});var r=n(103),a=n(95),i=n(8),o=n(14),s=n(259),l=n(6),c=n(128),u=n(258),p=n(112),h=n(0),m=n(138),d=n(168),g=n(67),_=n(21),y=n(19),f=n(68),b=n(176),v=n(80),E=n(9),k=n(151),C=n(79),P=n(142),O=n(5);const T=n(370),S=[],M=[];for(var x=0;x{if(t instanceof Error)throw e.errorMessage=Object(l.c)(e,t.message+(t.stack&&"\nstack:\n"+t.stack.toString()||"")),e;if(Object(l.a)(t))throw e.errorMessage=t,e;throw t})}function R(e){var t,n,a=e.code;e.running=!0;try{let r=function(e,t){const n=Object(v.parse)(e,{ecmaVersion:6,allowReserved:!0,sourceType:"module"});var r=t.getServer();if(null==r)throw new Error("Failed to find underlying Server object for script");let a="",i=!1;if(T.simple(n,{ImportDeclaration:e=>{i=!0;let t=e.source.value;t.startsWith("./")&&(t=t.slice(2));let n=function(e){for(let t=0;t{n.push(e.id.name),r.push(e)}}),a+="var "+t+";\n(function (namespace) {\n",r.forEach(e=>{a+=Object(b.generate)(e),a+="\n"}),n.forEach(e=>{a+="namespace."+e+" = "+e,a+="\n"}),a+="})("+t+" || ("+t+" = {}));\n"}else{let t=[];e.specifiers.forEach(e=>{t.push(e.local.name)});let n=[];T.simple(o,{FunctionDeclaration:e=>{t.includes(e.id.name)&&n.push(e)}}),n.forEach(e=>{a+=Object(b.generate)(e),a+="\n"})}}}),!i)return{code:e,lineOffset:0};var o=0;if("Program"!==n.type||null==n.body)throw new Error("Code could not be properly parsed");for(let e=n.body.length-1;e>=0;--e)"ImportDeclaration"===n.body[e].type&&(n.body.splice(e,1),++o);var s=(a.match(/\n/g)||[]).length-o;return e=Object(b.generate)(n),{code:e=a+e,lineOffset:s}}(a,e);t=r.code,n=r.lineOffset}catch(t){return Object(E.dialogBoxCreate)("Error processing Imports in "+e.name+":
    "+t),e.env.stopFlag=!0,void(e.running=!1)}var i;try{i=new s.a(t,function(t,n){var r=Object(c.a)(e);for(let e in r){let a=r[e];if("function"==typeof a)if("hack"===e||"grow"===e||"weaken"===e||"sleep"===e||"prompt"===e||"run"===e||"exec"===e){let r=function(){let e=[];for(let n=0;n"+t),e.env.stopFlag=!0,void(e.running=!1)}return new Promise(function(t,n){try{!function r(){try{if(e.env.stopFlag)return n(e);i.step()?Object(f.setTimeoutRef)(r,y.Settings.CodeInstructionRunTime):t(e)}catch(t){return t=t.toString(),Object(l.a)(t)||(t=Object(l.c)(e,t)),e.errorMessage=t,n(e)}}()}catch(t){return Object(O.isString)(t)?(e.errorMessage=t,n(e)):t instanceof r.WorkerScript?n(t):n(e)}})}function N(){let e=!1;for(let t=S.length-1;t>=0;t--)if(0==S[t].running&&1==S[t].env.stopFlag){e=!0;const n=S[t].serverIp,r=S[t].name;_.AllServers[n].ramUsed=0;for(let e=0;eServer Ip: "+n+"
    Script name: "+a+"
    Args:"+Object(C.arrayToString)(e.args)+"
    "+i),e.scriptRef.log("Script crashed with runtime error")}else e.scriptRef.log("Script killed");e.running=!1,e.env.stopFlag=!0}else{if(Object(l.a)(e))return Object(E.dialogBoxCreate)("Script runtime unknown error. This is a bug please contact game developer"),void console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: "+e.toString());Object(E.dialogBoxCreate)("An unknown script died for an unknown reason. This is a bug please contact game dev"),console.log(e)}else console.log("Script returning with value: "+e[1])})}Object(f.setTimeoutRef)(N,3e3)}function I(e,t){for(var n=0;nt.maxRam-t.ramUsed)Object(E.dialogBoxCreate)("Not enough RAM to run script "+e.filename+" with args "+Object(C.arrayToString)(e.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.)");else{t.ramUsed=Object(P.roundToTwo)(t.ramUsed+i);var o=new r.WorkerScript(e,c.a);o.ramUsage=i,Object(a.a)(o),S.push(o)}}function B(e=1){for(var t=e*o.Engine._idleSpeed/1e3,n=0;ns)return r.scriptRef.log("Cannot run script "+t+"(t="+a+") on "+e.hostname+" because there is not enough available RAM!"),Promise.resolve(!1);{null==r.disableLogs.ALL&&null==r.disableLogs.exec&&null==r.disableLogs.run&&null==r.disableLogs.spawn&&r.scriptRef.log(`Running script: ${t} on ${e.hostname} with ${a} threads and args: ${Object(C.arrayToString)(n)}. May take a few seconds to start up...`);let o=new m.RunningScript(i,n);return o.threads=a,D(o,e),e.runScript(o,h.Player.hacknet_node_money_mult),Promise.resolve(!0)}}return r.scriptRef.log("Could not find script "+t+" on "+e.hostname),Promise.resolve(!1)}},,function(e,t,n){"use strict";n.d(t,"a",function(){return C}),n.d(t,"b",function(){return P}),n.d(t,"i",function(){return O}),n.d(t,"c",function(){return T}),n.d(t,"h",function(){return S}),n.d(t,"d",function(){return M}),n.d(t,"f",function(){return x}),n.d(t,"g",function(){return w}),n.d(t,"k",function(){return A}),n.d(t,"j",function(){return N}),n.d(t,"e",function(){return B});var r=n(97),a=n(178),i=n(105),o=n(83),s=(n(136),n(127),n(270)),l=n(66),c=n(57),u=n(116),p=n(269),h=n(8),m=n(103),d=n(0),g=n(12),_=n(2),y=n(9),f=n(18),b=n(1),v=n.n(b),E=n(64),k=n.n(E);let C={},P={};function O(e,t,n,r,s,l=null){const c=l instanceof m.WorkerScript;if(!(e instanceof o.Stock))return c?l.log("ERROR: Invalid stock passed to placeOrder() function"):Object(y.dialogBoxCreate)("ERROR: Invalid stock passed to placeOrder() function"),!1;if("number"!=typeof t||"number"!=typeof n)return c?l.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"):Object(y.dialogBoxCreate)("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"),!1;const u=new a.Order(e.symbol,t,n,r,s);if(null==C.Orders){const e={};for(const t in C){const n=C[t];n instanceof o.Stock&&(e[n.symbol]=[])}C.Orders=e}C.Orders[e.symbol].push(u);const p={rerenderFn:B,stockMarket:C,symbolToStockMap:P};return Object(i.processOrders)(e,u.type,u.pos,p),B(),!0}function T(e,t=null){var n=t instanceof m.WorkerScript;if(null==C.Orders)return!1;if(e.order&&e.order instanceof a.Order){const t=e.order;for(var r=C.Orders[t.stockSymbol],i=0;i=t.cap&&(a=.1,t.b=!1),isNaN(a)&&(a=.5);const s=Math.random(),u={rerenderFn:B,stockMarket:C,symbolToStockMap:P};s50&&(t.otlkMag=50),t.otlkMag<0&&(t.otlkMag*=-1,t.b=!t.b),t.shareTxUntilMovement=Math.min(t.shareTxUntilMovementUp+5,t.shareTxForMovement),t.shareTxUntilMovement=Math.min(t.shareTxUntilMovementDown+5,t.shareTxForMovement)}B()}}let I=null;function D(){x(),w()}function B(){g.routing.isOn(g.Page.StockMarket)&&I instanceof HTMLElement&&k.a.render(v.a.createElement(p.StockMarketRoot,{buyStockLong:r.buyStock,buyStockShort:r.shortStock,cancelOrder:T,initStockMarket:D,p:d.Player,placeOrder:O,sellStockLong:r.sellStock,sellStockShort:r.sellShort,stockMarket:C}),I)}document.addEventListener("DOMContentLoaded",function e(){I=document.getElementById("stock-market-container"),document.removeEventListener("DOMContentLoaded",e)})},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(27),a=n(32);t.yesNoBoxOpen=!1;const i=document.getElementById("yes-no-box-container"),o=document.getElementById("yes-no-box-text");function s(e){if(e.keyCode===a.KEY.ESC)l();else if(e.keyCode===a.KEY.ENTER){const e=document.getElementById("yes-no-box-yes");e?e.click():console.error("Could not find YesNoBox Yes button DOM element")}}function l(){return i?i.style.display="none":console.error("Container not found for YesNoBox"),t.yesNoBoxOpen=!1,document.removeEventListener("keydown",s),!1}t.yesNoBoxClose=l,t.yesNoBoxGetYesButton=function(){return r.clearEventListeners("yes-no-box-yes")},t.yesNoBoxGetNoButton=function(){return r.clearEventListeners("yes-no-box-no")},t.yesNoBoxCreate=function(e){return!t.yesNoBoxOpen&&(t.yesNoBoxOpen=!0,o?o.innerHTML=e:console.error("Text element not found for YesNoBox"),i?i.style.display="flex":console.error("Container not found for YesNoBox"),document.addEventListener("keydown",s),!0)};const c=document.getElementById("yes-no-text-input-box-container"),u=document.getElementById("yes-no-text-input-box-input"),p=document.getElementById("yes-no-text-input-box-text");function h(e){if(e.keyCode===a.KEY.ESC)m();else if(e.keyCode===a.KEY.ENTER){const e=document.getElementById("yes-no-text-input-box-yes");e?e.click():console.error("Could not find YesNoTxtInputBox Yes button DOM element")}}function m(){return null==c?(console.error("Container not found for YesNoTextInputBox"),!1):(c.style.display="none",t.yesNoBoxOpen=!1,u.value="",document.removeEventListener("keydown",h),!1)}t.yesNoTxtInpBoxHotkeyHandler=h,t.yesNoTxtInpBoxClose=m,t.yesNoTxtInpBoxGetYesButton=function(){return r.clearEventListeners("yes-no-text-input-box-yes")},t.yesNoTxtInpBoxGetNoButton=function(){return r.clearEventListeners("yes-no-text-input-box-no")},t.yesNoTxtInpBoxGetInput=function(){if(null==u)return console.error("Could not find YesNoTextInputBox input element"),"";let e=u.value;return e=e.replace(/\s+/g,"")},t.yesNoTxtInpBoxCreate=function(e){t.yesNoBoxOpen=!0,p&&(p.innerHTML=e),c?c.style.display="flex":console.error("Container not found for YesNoTextInputBox"),document.addEventListener("keydown",h),u.focus()}},function(e,t,n){"use strict";n.d(t,"b",function(){return o}),n.d(t,"a",function(){return s});var r=n(0),a=n(69),i=n(6);function o(e,t){return`gang.${e}() failed with exception: `+t}function s(e,t){const n=`gang.${t}() failed because you do not currently have a Gang`;if(!(r.Player.gang instanceof a.b))throw Object(i.c)(e,n)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(632),a=n(135);t.CompanyPositions={},r.companyPositionMetadata.forEach(e=>{!function(e){null!=t.CompanyPositions[e.name]&&console.warn(`Duplicate Company Position being defined: ${e.name}`),t.CompanyPositions[e.name]=new a.CompanyPosition(e)}(e)})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(9);t.exceptionAlert=function(e){console.error(e),r.dialogBoxCreate("Caught an exception: "+e+"

    Filename: "+(e.fileName||"UNKNOWN FILE NAME")+"

    Line Number: "+(e.lineNumber||"UNKNOWN LINE NUMBER")+"

    This is a bug, please report to game developer with this message as well as details about how to reproduce the bug.

    If you want to be safe, I suggest refreshing the game WITHOUT saving so that your safe doesn't get corrupted",!1)}},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(a,i){function o(e){try{l(r.next(e))}catch(e){i(e)}}function s(e){try{l(r.throw(e))}catch(e){i(e)}}function l(e){e.done?a(e.value):new n(function(t){t(e.value)}).then(o,s)}l((r=r.apply(e,t||[])).next())})};Object.defineProperty(t,"__esModule",{value:!0});const a=n(671),i=n(18),o=n(32),s=n(3),l=n(36),c=n(29);class u{constructor(e,t,n,r,a,i){this.name=e,this.desc=t,this.generate=n,this.solver=r,this.difficulty=a,this.numTries=i}}t.CodingContractType=u,t.CodingContractTypes={};for(const e of a.codingContractTypesMetadata)t.CodingContractTypes[e.name]=new u(e.name,e.desc,e.gen,e.solver,e.difficulty,e.numTries);var p;console.info(`${Object.keys(t.CodingContractTypes).length} Coding Contract Types loaded`),function(e){e[e.FactionReputation=0]="FactionReputation",e[e.FactionReputationAll=1]="FactionReputationAll",e[e.CompanyReputation=2]="CompanyReputation",e[e.Money=3]="Money"}(t.CodingContractRewardType||(t.CodingContractRewardType={})),function(e){e[e.Success=0]="Success",e[e.Failure=1]="Failure",e[e.Cancelled=2]="Cancelled"}(p=t.CodingContractResult||(t.CodingContractResult={}));class h{constructor(e="",n="Find Largest Prime Factor",r=null){if(this.tries=0,this.fn=e,this.fn.endsWith(".cct")||(this.fn+=".cct"),null==t.CodingContractTypes[n])throw new Error(`Error: invalid contract type: ${n} please contact developer`);this.type=n,this.data=t.CodingContractTypes[n].generate(),this.reward=r}static fromJSON(e){return i.Generic_fromJSON(h,e.data)}getData(){return this.data}getDescription(){return t.CodingContractTypes[this.type].desc(this.data)}getDifficulty(){return t.CodingContractTypes[this.type].difficulty}getMaxNumTries(){return t.CodingContractTypes[this.type].numTries}getType(){return t.CodingContractTypes[this.type].name}isSolution(e){return t.CodingContractTypes[this.type].solver(this.data,e)}prompt(){return r(this,void 0,void 0,function*(){return new Promise((e,n)=>{const r=t.CodingContractTypes[this.type],a=`coding-contract-prompt-popup-${this.fn}`,i=s.createElement("p",{innerHTML:["You are attempting to solve a Coding Contract. You have",`${this.getMaxNumTries()-this.tries} tries remaining,`,"after which the contract will self-destruct.

    ",`${r.desc(this.data).replace(/\n/g,"
    ")}`].join(" ")});let u,h,m;u=s.createElement("input",{onkeydown:e=>{e.keyCode===o.KEY.ENTER&&""!==u.value?(e.preventDefault(),h.click()):e.keyCode===o.KEY.ESC&&(e.preventDefault(),m.click())},placeholder:"Enter Solution here",width:"50%"}),h=s.createElement("a",{class:"a-link-button",clickListener:()=>{const t=u.value;this.isSolution(t)?e(p.Success):e(p.Failure),c.removeElementById(a)},innerText:"Solve"}),m=s.createElement("a",{class:"a-link-button",clickListener:()=>{e(p.Cancelled),c.removeElementById(a)},innerText:"Cancel"});const d=s.createElement("br");l.createPopup(a,[i,d,d,u,h,m]),u.focus()})})}toJSON(){return i.Generic_toJSON("CodingContract",this)}}t.CodingContract=h,i.Reviver.constructors.CodingContract=h},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isString=function(e){return"string"==typeof e||e instanceof String}},function(e,t,n){"use strict";n.r(t),n.d(t,"inviteToFaction",function(){return O}),n.d(t,"joinFaction",function(){return T}),n.d(t,"startHackingMission",function(){return S}),n.d(t,"displayFactionContent",function(){return M}),n.d(t,"purchaseAugmentationBoxCreate",function(){return x}),n.d(t,"hasAugmentationPrereqs",function(){return w}),n.d(t,"purchaseAugmentation",function(){return A}),n.d(t,"getNextNeurofluxLevel",function(){return R}),n.d(t,"processPassiveFactionRepGain",function(){return N});var r=n(1),a=n.n(r),i=n(64),o=n.n(i),s=n(282),l=n(11),c=n(72),u=n(126),p=n(4),h=n(20),m=n(8),d=n(14),g=n(70),_=n(13),y=n(75),f=n(0),b=n(19),v=n(12),E=n(9),k=n(264),C=(n(18),n(5)),P=n(46);function O(e){b.Settings.SuppressFactionInvites?(e.alreadyInvited=!0,f.Player.factionInvitations.push(e.name),v.routing.isOn(v.Page.Factions)&&d.Engine.loadFactionsContent()):Object(k.a)(e)}function T(e){e.isMember=!0,f.Player.factions.push(e.name);const t=e.getInfo();for(const e in t.enemies){const n=t.enemies[e];_.Factions[n]instanceof g.Faction&&(_.Factions[n].isBanned=!0)}}function S(e){const t=new y.a(e.playerReputation,e);Object(y.d)(!0,t),t.init()}function M(e,t=!1){const n=_.Factions[e];if(null==n)throw new Error(`Invalid factionName passed into displayFactionContent(): ${e}`);if(!n.isMember)throw new Error("Not a member of this faction. Cannot display faction information");o.a.render(a.a.createElement(s.FactionRoot,{engine:d.Engine,initiallyOnAugmentationsPage:t,faction:n,p:f.Player,startHackingMissionFn:S}),d.Engine.Display.factionContent)}function x(e,t){const n=t.getInfo(),r=Object(P.yesNoBoxGetYesButton)();r.innerHTML="Purchase",r.addEventListener("click",function(){!Object(c.f)(e)&&f.Player.hasAugmentation(e)||(A(e,t),Object(P.yesNoBoxClose)())});const a=Object(P.yesNoBoxGetNoButton)();a.innerHTML="Cancel",a.addEventListener("click",function(){Object(P.yesNoBoxClose)()}),Object(P.yesNoBoxCreate)("

    "+e.name+"


    "+e.info+"


    Would you like to purchase the "+e.name+" Augmentation for $"+Object(C.formatNumber)(e.baseCost*n.augmentationPriceMult,2)+"?")}function w(e){let t=!0;if(e.prereqs&&e.prereqs.length>0)for(let n=0;n=t.HacknetNodeMaxCores)return 1/0;const a=t.BaseCostForHacknetNodeCore,i=t.HacknetNodeUpgradeCoreMult;let o=0,s=this.cores;for(let e=0;e=t.HacknetNodeMaxLevel)return 1/0;const a=t.HacknetNodeUpgradeLevelMult;let i=0,o=this.level;for(let e=0;e=t.HacknetNodeMaxRam)return 1/0;let a=0,i=Math.round(Math.log2(this.ram)),o=this.ram;for(let e=0;e1?1:r<0?0:r}function o(e){null==e.baseDifficulty&&(e.baseDifficulty=e.hackDifficulty);var t=3;return(t+=e.baseDifficulty*a.Player.hacking_exp_mult*.3)*r.BitNodeMultipliers.HackExpGain}function s(e){const t=(100-e.hackDifficulty)/100*((a.Player.hacking_skill-(e.requiredHackingSkill-1))/a.Player.hacking_skill)*a.Player.hacking_money_mult/240;return t<0?0:t>1?1:t*r.BitNodeMultipliers.ScriptHackMoney}function l(e,t,n){const r=e.requiredHackingSkill*e.hackDifficulty;null==t&&(t=a.Player.hacking_skill),null==n&&(n=a.Player.intelligence);var i=2.5*r+500;return 5*(i/=t+50+.1*n)/a.Player.hacking_speed_mult}function c(e,t,n){return 3.2*l(e,t,n)}function u(e,t,n){return 4*l(e,t,n)}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),function(e){e.Long="L",e.Short="S"}(t.PositionTypes||(t.PositionTypes={}))},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(3),a=n(77);t.createPopupCloseButton=function(e,t){let n;function i(e){27===e.keyCode&&n.click()}return n=r.createElement("button",{class:t.class?t.class:"popup-box-button",display:t.display?t.display:"inline-block",innerText:null==t.innerText?"Cancel":t.innerText,clickListener:()=>{if(e instanceof Element)a.removeElement(e);else try{const t=document.getElementById(e);t instanceof Element&&a.removeElement(t)}catch(e){console.error(`createPopupCloseButton() threw: ${e}`)}return document.removeEventListener("keydown",i),!1}}),document.addEventListener("keydown",i),n}},,function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(3);t.createOptionElement=function(e,t=""){let n=t;return""===n&&(n=e),r.createElement("option",{text:e,value:n})}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isScriptFilename=function(e){return e.endsWith(".js")||e.endsWith(".script")||e.endsWith(".ns")}},function(e,t,n){"use strict";n.d(t,"a",function(){return i});var r=n(1);const a=n.n(r).a.Component;class i extends a{corp(){return this.props.corp}eventHandler(){return this.props.eventHandler}routing(){return this.props.routing}render(){}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7);function a(e){return t.GlobalAliases.hasOwnProperty(e)?t.GlobalAliases[e]:null}t.Aliases={},t.GlobalAliases={},t.loadAliases=function(e){t.Aliases=""===e?{}:JSON.parse(e)},t.loadGlobalAliases=function(e){t.GlobalAliases=""===e?{}:JSON.parse(e)},t.printAliases=function(){for(var e in t.Aliases)t.Aliases.hasOwnProperty(e)&&r.post("alias "+e+"="+t.Aliases[e]);for(var e in t.GlobalAliases)t.GlobalAliases.hasOwnProperty(e)&&r.post("global alias "+e+"="+t.GlobalAliases[e])},t.parseAliasDeclaration=function(e,n=!1){var r=e.match(/^([_|\w|!|%|,|@]+)="(.+)"$/);return null!=r&&3==r.length&&(n?function(e,n){e in t.Aliases&&delete t.Aliases[e],t.GlobalAliases[e]=n}(r[1],r[2]):function(e,n){e in t.GlobalAliases&&delete t.GlobalAliases[e],t.Aliases[e]=n}(r[1],r[2]),!0)},t.removeAlias=function(e){return t.Aliases.hasOwnProperty(e)?(delete t.Aliases[e],!0):!!t.GlobalAliases.hasOwnProperty(e)&&(delete t.GlobalAliases[e],!0)},t.substituteAliases=function(e){const n=e.split(" ");if(n.length>0){if("unalias"===n[0])return n.join(" ");null!=(i=function(e){return t.Aliases.hasOwnProperty(e)?t.Aliases[e]:null}(n[0]))?n[0]=i:null!=(i=a(n[0]))&&(n[0]=i);for(var r=0;r{let e=M();return null!=e&&e.beautifyScript(),!1}});T=Object(P.createElement)("p",{display:"inline-block",margin:"10px",id:"script-editor-status-text"});const n=Object(P.createElement)("label",{for:"script-editor-ram-check",margin:"4px",marginTop:"8px",innerText:"Dynamic RAM Usage Checker",color:"white",tooltip:"Enable/Disable the dynamic RAM Usage display. You may want to disable it for very long scripts because there may be performance issues"});(O=Object(P.createElement)("input",{type:"checkbox",name:"script-editor-ram-check",id:"script-editor-ram-check",margin:"4px",marginTop:"8px"})).checked=!0;const r=Object(P.createElement)("a",{class:"std-button",display:"inline-block",href:"https://bitburner.readthedocs.io/en/latest/index.html",innerText:"Netscript Documentation",target:"_blank"}),a=Object(P.createElement)("button",{class:"std-button",display:"inline-block",innerText:"Save & Close (Ctrl/Cmd + b)",clickListener:()=>(w(),!1)});e.appendChild(t),e.appendChild(a),e.appendChild(T),e.appendChild(O),e.appendChild(n),e.appendChild(r);const i={saveAndCloseFn:w,quitFn:l.Engine.loadTerminalContent};h.a.init(i),m.a.init(i);const o=document.getElementById("script-editor-option-editor");if(null==o)return console.error("Could not find DOM Element for editor selector (id=script-editor-option-editor)"),!1;for(let e=0;e{const e=o.value;switch(e){case y.EditorSetting.Ace:{const e=m.a.getCode(),t=m.a.getFilename();h.a.create(),m.a.setInvisible(),h.a.openScript(t,e);break}case y.EditorSetting.CodeMirror:{const e=h.a.getCode(),t=h.a.getFilename();m.a.create(),h.a.setInvisible(),m.a.openScript(t,e);break}default:return void console.error(`Unrecognized Editor Setting: ${e}`)}_.Settings.Editor=e}),o.onchange()}function M(){switch(_.Settings.Editor){case y.EditorSetting.Ace:return h.a;case y.EditorSetting.CodeMirror:return m.a;default:throw console.log(`Invalid Editor Setting: ${_.Settings.Editor}`),new Error(`Invalid Editor Setting: ${_.Settings.Editor}`)}}async function x(){var e=document.getElementById("script-editor-filename").value;if(null==O||!O.checked||!Object(o.isScriptFilename)(e))return void(T.innerText="N/A");let t;try{t=M().getCode()}catch(e){return void(T.innerText="RAM: ERROR")}var n=t.repeat(1),r=await Object(i.calculateRamUsage)(n,p.Player.getCurrentServer().scripts);if(r>0)T.innerText="RAM: "+E.numeralWrapper.format(r,"0.00")+" GB";else switch(r){case a.RamCalculationErrorCode.ImportError:T.innerText="RAM: Import Error";break;case a.RamCalculationErrorCode.URLImportError:T.innerText="RAM: HTTP Import Error";break;case a.RamCalculationErrorCode.SyntaxError:default:T.innerText="RAM: Syntax Error"}}function w(){var e=document.getElementById("script-editor-filename").value;let t;try{t=M().getCode()}catch(e){return void Object(k.dialogBoxCreate)("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details")}if(u.a.isRunning&&u.a.currStep===u.d.TerminalTypeScript){if("foodnstuff.script"!==e)return void Object(k.dialogBoxCreate)("Leave the script name as 'foodnstuff'!");if(-1==(t=t.replace(/\s/g,"")).indexOf("while(true){hack('foodnstuff');}"))return void Object(k.dialogBoxCreate)("Please copy and paste the code from the tutorial!");let a=p.Player.getCurrentServer();for(var n=0;n=1&&(n=1),e.dataMap)if(e.dataMap.hasOwnProperty(r)){if(0==e.dataMap[r][2]||null==e.dataMap[r][2])continue;if(null==(h=d.AllServers[r]))continue;var a=Math.round(.5*e.dataMap[r][2]/e.onlineRunningTime*t);console.log(e.filename+" called grow() on "+h.hostname+" "+a+" times while offline"),e.log("Called grow() on "+h.hostname+" "+a+" times while offline");var i=Object(g.processSingleServerGrowth)(h,450*a,p.Player);e.log(h.hostname+" grown by "+E.numeralWrapper.format(100*i-100,"0.000000%")+" from grow() calls made while offline")}var o=0;for(var r in e.dataMap)if(e.dataMap.hasOwnProperty(r)){if(0==e.dataMap[r][0]||null==e.dataMap[r][0])continue;if(null==(h=d.AllServers[r]))continue;var l=.5*e.dataMap[r][0]/e.onlineRunningTime*t;(l*=n)>h.moneyAvailable&&(l=h.moneyAvailable),o+=l,p.Player.gainMoney(l),p.Player.recordMoneySource(l,"hacking"),console.log(e.filename+" generated $"+l+" while offline by hacking "+h.hostname),e.log(e.filename+" generated $"+l+" while offline by hacking "+h.hostname),h.moneyAvailable-=l,h.moneyAvailable<0&&(h.moneyAvailable=0),isNaN(h.moneyAvailable)&&(h.moneyAvailable=0)}var c=e.onlineExpGained/e.onlineRunningTime*.5*t;for(var r in c*=n,p.Player.gainHackingExp(c),e.offlineMoneyMade+=o,e.offlineRunningTime+=t,e.offlineExpGained+=c,e.dataMap)if(e.dataMap.hasOwnProperty(r)){if(0==e.dataMap[r][1]||null==e.dataMap[r][1])continue;if(null==(h=d.AllServers[r]))continue;var u=Math.round(.5*e.dataMap[r][1]/e.onlineRunningTime*t);console.log(e.filename+" hacked "+h.hostname+" "+u+" times while offline"),e.log("Hacked "+h.hostname+" "+u+" times while offline"),h.fortify(s.CONSTANTS.ServerFortifyAmount*u)}for(var r in e.dataMap)if(e.dataMap.hasOwnProperty(r)){if(0==e.dataMap[r][3]||null==e.dataMap[r][3])continue;var h;if(null==(h=d.AllServers[r]))continue;var m=Math.round(.5*e.dataMap[r][3]/e.onlineRunningTime*t);console.log(e.filename+" called weaken() on "+h.hostname+" "+m+" times while offline"),e.log("Called weaken() on "+h.hostname+" "+m+" times while offline"),h.weaken(s.CONSTANTS.ServerWeakenAmount*m)}return o}function R(e,t,n){for(var r=0;rt&&(n=1),this.wanted=n,this.wanted<1&&(this.wanted=1)}else console.warn("ERROR: wantedLevelGains is NaN");"number"==typeof n?(t.gainMoney(n*e),t.recordMoneySource(n*e,"gang")):console.warn("ERROR: respectGains is NaN")},S.prototype.processTerritoryAndPowerGains=function(e=1){if(this.storedTerritoryAndPowerCycles+=e,!(this.storedTerritoryAndPowerCycles<100)){this.storedTerritoryAndPowerCycles-=100;var t=this.facName;for(const e in P)if(P.hasOwnProperty(e))if(e==t)P[e].power+=this.calculatePower();else{const t=Math.random();if(t<.5){const t=.005*P[e].power;P[e].power+=Math.min(.85,t)}else{const n=.75*t*P[e].territory;P[e].power+=n}}this.territoryWarfareEngaged?this.territoryClashChance=1:this.territoryClashChance>0&&(this.territoryClashChance=Math.max(0,this.territoryClashChance-.01));for(let e=0;et!==C[e]),r=Object(g.getRandomInt)(0,n.length-1),a=C[e],i=n[r];if(!(a!==t&&i!==t||Math.random()=30)&&this.respect>=this.getRespectNeededToRecruitMember()},S.prototype.getRespectNeededToRecruitMember=function(){if(this.members.length<3)return 0;const e=this.members.length-2;return Math.round(.9*Math.pow(e,3)+Math.pow(e,2))},S.prototype.recruitMember=function(e){if(""===(e=String(e))||!this.canRecruitMember())return!1;if(this.members.filter(t=>t.name===e).length>=1)return!1;let t=new M(e);return this.members.push(t),c.routing.isOn(c.Page.Gang)&&(this.createGangMemberDisplayElement(t),this.updateGangContent()),!0},S.prototype.getWantedPenalty=function(){return this.respect/(this.respect+this.wanted)},S.prototype.processExperienceGains=function(e=1){for(var t=0;t=0;--e){const n=this.members[e];if("Territory Warfare"!==n.task)continue;const r=t/Math.pow(n.def,.6);Math.random()")):t.log(`Ascended Gang member ${e.name}`),c.routing.isOn(c.Page.Gang)&&this.displayGangMemberList(),n}catch(e){if(null!=t)throw e;Object(d.exceptionAlert)(e)}},S.prototype.getDiscount=function(){const e=this.getPower(),t=this.respect,n=Math.pow(t,.01)+t/5e6+Math.pow(e,.01)+e/1e6-1;return Math.max(1,n)},S.prototype.getAllTaskNames=function(){let e=[];const t=Object.keys(w);return e=this.isHackingGang?t.filter(e=>{let t=w[e];return null!=t&&("Unassigned"!==e&&t.isHacking)}):t.filter(e=>{let t=w[e];return null!=t&&("Unassigned"!==e&&t.isCombat)})},S.prototype.getAllUpgradeNames=function(){return Object.keys(R)},S.prototype.getUpgradeCost=function(e){return null==R[e]?1/0:R[e].getCost(this)},S.prototype.getUpgradeType=function(e){const t=R[e];if(null==t)return"";switch(t.type){case"w":return"Weapon";case"a":return"Armor";case"v":return"Vehicle";case"r":return"Rootkit";case"g":return"Augmentation";default:return""}},S.prototype.toJSON=function(){return Object(h.Generic_toJSON)("Gang",this)},S.fromJSON=function(e){return Object(h.Generic_fromJSON)(S,e.data)},h.Reviver.constructors.Gang=S,M.prototype.calculateSkill=function(e,t=1){return Math.max(Math.floor(t*(32*Math.log(e+534.5)-200)),1)},M.prototype.updateSkillLevels=function(){this.hack=this.calculateSkill(this.hack_exp,this.hack_mult*this.hack_asc_mult),this.str=this.calculateSkill(this.str_exp,this.str_mult*this.str_asc_mult),this.def=this.calculateSkill(this.def_exp,this.def_mult*this.def_asc_mult),this.dex=this.calculateSkill(this.dex_exp,this.dex_mult*this.dex_asc_mult),this.agi=this.calculateSkill(this.agi_exp,this.agi_mult*this.agi_asc_mult),this.cha=this.calculateSkill(this.cha_exp,this.cha_mult*this.cha_asc_mult)},M.prototype.calculatePower=function(){return(this.hack+this.str+this.def+this.dex+this.agi+this.cha)/95},M.prototype.assignToTask=function(e){return w.hasOwnProperty(e)?(this.task=e,!0):(this.task="Unassigned",!1)},M.prototype.unassignFromTask=function(){this.task="Unassigned"},M.prototype.getTask=function(){return this.task instanceof x&&(this.task=this.task.name),w.hasOwnProperty(this.task)?w[this.task]:w.Unassigned},M.prototype.calculateRespectGain=function(e){const t=this.getTask();if(null==t||!(t instanceof x)||0===t.baseRespect)return 0;var n=t.hackWeight/100*this.hack+t.strWeight/100*this.str+t.defWeight/100*this.def+t.dexWeight/100*this.dex+t.agiWeight/100*this.agi+t.chaWeight/100*this.cha;if((n-=4*t.difficulty)<=0)return 0;const r=Math.pow(100*P[e.facName].territory,t.territory.respect)/100;if(isNaN(r)||r<=0)return 0;var a=e.getWantedPenalty();return 11*t.baseRespect*n*r*a},M.prototype.calculateWantedLevelGain=function(e){const t=this.getTask();if(null==t||!(t instanceof x)||0===t.baseWanted)return 0;let n=t.hackWeight/100*this.hack+t.strWeight/100*this.str+t.defWeight/100*this.def+t.dexWeight/100*this.dex+t.agiWeight/100*this.agi+t.chaWeight/100*this.cha;if((n-=3.5*t.difficulty)<=0)return 0;const r=Math.pow(100*P[e.facName].territory,t.territory.wanted)/100;if(isNaN(r)||r<=0)return 0;if(t.baseWanted<0)return.4*t.baseWanted*n*r;{const e=7*t.baseWanted/Math.pow(3*n*r,.8);return Math.min(100,e)}},M.prototype.calculateMoneyGain=function(e){const t=this.getTask();if(null==t||!(t instanceof x)||0===t.baseMoney)return 0;var n=t.hackWeight/100*this.hack+t.strWeight/100*this.str+t.defWeight/100*this.def+t.dexWeight/100*this.dex+t.agiWeight/100*this.agi+t.chaWeight/100*this.cha;if((n-=3.2*t.difficulty)<=0)return 0;const r=Math.pow(100*P[e.facName].territory,t.territory.money)/100;if(isNaN(r)||r<=0)return 0;var a=e.getWantedPenalty();return 5*t.baseMoney*n*r*a},M.prototype.gainExperience=function(e=1){const t=this.getTask();if(null==t||!(t instanceof x)||t===w.Unassigned)return;const n=Math.pow(t.difficulty,.9)*e;this.hack_exp+=t.hackWeight/1500*n,this.str_exp+=t.strWeight/1500*n,this.def_exp+=t.defWeight/1500*n,this.dex_exp+=t.dexWeight/1500*n,this.agi_exp+=t.agiWeight/1500*n,this.cha_exp+=t.chaWeight/1500*n},M.prototype.recordEarnedRespect=function(e=1,t){this.earnedRespect+=this.calculateRespectGain(t)*e},M.prototype.ascend=function(){const e=this.getAscensionResults(),t=e.hack,n=e.str,r=e.def,a=e.dex,i=e.agi,o=e.cha;this.hack_asc_mult+=t,this.str_asc_mult+=n,this.def_asc_mult+=r,this.dex_asc_mult+=a,this.agi_asc_mult+=i,this.cha_asc_mult+=o,this.upgrades.length=0,this.hack_mult=1,this.str_mult=1,this.def_mult=1,this.dex_mult=1,this.agi_mult=1,this.cha_mult=1;for(let e=0;e{!function(e,t,n,r,a){w[e]=new x(e,t,n,r,a)}(e.name,e.desc,e.isHacking,e.isCombat,e.params)}),A.prototype.getCost=function(e){const t=e.getDiscount();return this.cost/t},A.prototype.createDescription=function(){const e=["Increases:"];null!=this.mults.str&&e.push(`* Strength by ${Math.round(100*(this.mults.str-1))}%`),null!=this.mults.def&&e.push(`* Defense by ${Math.round(100*(this.mults.def-1))}%`),null!=this.mults.dex&&e.push(`* Dexterity by ${Math.round(100*(this.mults.dex-1))}%`),null!=this.mults.agi&&e.push(`* Agility by ${Math.round(100*(this.mults.agi-1))}%`),null!=this.mults.cha&&e.push(`* Charisma by ${Math.round(100*(this.mults.cha-1))}%`),null!=this.mults.hack&&e.push(`* Hacking by ${Math.round(100*(this.mults.hack-1))}%`),this.desc=e.join("
    ")},A.prototype.apply=function(e){null!=this.mults.str&&(e.str_mult*=this.mults.str),null!=this.mults.def&&(e.def_mult*=this.mults.def),null!=this.mults.dex&&(e.dex_mult*=this.mults.dex),null!=this.mults.agi&&(e.agi_mult*=this.mults.agi),null!=this.mults.cha&&(e.cha_mult*=this.mults.cha),null!=this.mults.hack&&(e.hack_mult*=this.mults.hack)},A.prototype.toJSON=function(){return Object(h.Generic_toJSON)("GangMemberUpgrade",this)},A.fromJSON=function(e){return Object(h.Generic_fromJSON)(A,e.data)},h.Reviver.constructors.GangMemberUpgrade=A;const R={};a.gangMemberUpgradesMetadata.forEach(e=>{!function(e,t,n,r){R[e]=new A(e,t,n,r)}(e.name,e.cost,e.upgType,e.mults)}),S.prototype.createGangMemberUpgradeBox=function(e,t=""){const n="gang-member-upgrade-popup-box";if(N.gangMemberUpgradeBoxOpened){if(null==N.gangMemberUpgradeBoxElements||null==N.gangMemberUpgradeBox||null==N.gangMemberUpgradeBoxContent)return void console.error("Refreshing Gang member upgrade box throws error because required elements are null");for(var r=2;r-1||this.members[r].task.indexOf(a)>-1){var i=this.members[r].createGangMemberUpgradePanel(this,e);N.gangMemberUpgradeBoxContent.appendChild(i),N.gangMemberUpgradeBoxElements.push(i)}}else{N.gangMemberUpgradeBoxFilter=Object(f.createElement)("input",{type:"text",placeholder:"Filter gang members",value:t,onkeyup:()=>{var t=N.gangMemberUpgradeBoxFilter.value.toString();this.createGangMemberUpgradeBox(e,t)}}),N.gangMemberUpgradeBoxDiscount=Object(f.createElement)("p",{innerText:"Discount: -"+u.numeralWrapper.format(1-1/this.getDiscount(),"0.00%"),marginLeft:"6px",tooltip:"You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to more discounts."}),N.gangMemberUpgradeBoxElements=[N.gangMemberUpgradeBoxFilter,N.gangMemberUpgradeBoxDiscount];for(a=N.gangMemberUpgradeBoxFilter.value.toString(),r=0;r-1||this.members[r].task.indexOf(a)>-1)&&N.gangMemberUpgradeBoxElements.push(this.members[r].createGangMemberUpgradePanel(this,e));N.gangMemberUpgradeBox=Object(b.createPopup)(n,N.gangMemberUpgradeBoxElements),N.gangMemberUpgradeBoxContent=document.getElementById(n+"-content"),N.gangMemberUpgradeBoxOpened=!0}},M.prototype.createGangMemberUpgradePanel=function(e,t){var n=Object(f.createElement)("div",{border:"1px solid white"}),r=Object(f.createElement)("h1",{innerText:this.name+" ("+this.task+")"});n.appendChild(r);var a=Object(f.createElement)("pre",{fontSize:"14px",display:"inline-block",width:"20%",innerText:"Hack: "+this.hack+" (x"+Object(m.formatNumber)(this.hack_mult*this.hack_asc_mult,2)+")\nStr: "+this.str+" (x"+Object(m.formatNumber)(this.str_mult*this.str_asc_mult,2)+")\nDef: "+this.def+" (x"+Object(m.formatNumber)(this.def_mult*this.def_asc_mult,2)+")\nDex: "+this.dex+" (x"+Object(m.formatNumber)(this.dex_mult*this.dex_asc_mult,2)+")\nAgi: "+this.agi+" (x"+Object(m.formatNumber)(this.agi_mult*this.agi_asc_mult,2)+")\nCha: "+this.cha+" (x"+Object(m.formatNumber)(this.cha_mult*this.cha_asc_mult,2)+")\n"});const i=[];function o(e){const t=R[e];null!=t?i.push(Object(f.createElement)("div",{class:"gang-owned-upgrade",innerText:e,tooltip:t.desc})):console.error(`Could not find GangMemberUpgrade object for name ${e}`)}for(const e of this.upgrades)o(e);for(const e of this.augmentations)o(e);var s=Object(f.createElement)("div",{class:"gang-owned-upgrades-div",innerText:"Purchased Upgrades:"});for(const e of i)s.appendChild(e);n.appendChild(a),n.appendChild(s),n.appendChild(Object(f.createElement)("br",{}));const l=[],c=[],p=[],h=[],d=[];for(let n in R)if(R.hasOwnProperty(n)){let r=R[n];if(t.money.lt(r.getCost(e)))continue;if(this.upgrades.includes(n)||this.augmentations.includes(n))continue;switch(r.type){case"w":l.push(r);break;case"a":c.push(r);break;case"v":p.push(r);break;case"r":h.push(r);break;case"g":d.push(r);break;default:console.error(`ERROR: Invalid Gang Member Upgrade Type: ${r.type}`)}}const g=Object(f.createElement)("div",{width:"20%",display:"inline-block"}),_=Object(f.createElement)("div",{width:"20%",display:"inline-block"}),y=Object(f.createElement)("div",{width:"20%",display:"inline-block"}),b=Object(f.createElement)("div",{width:"20%",display:"inline-block"}),v=Object(f.createElement)("div",{width:"20%",display:"inline-block"});g.appendChild(Object(f.createElement)("h2",{innerText:"Weapons"})),_.appendChild(Object(f.createElement)("h2",{innerText:"Armor"})),y.appendChild(Object(f.createElement)("h2",{innerText:"Vehicles"})),b.appendChild(Object(f.createElement)("h2",{innerText:"Rootkits"})),v.appendChild(Object(f.createElement)("h2",{innerText:"Augmentations"}));const E=[l,c,p,h,d],k=[g,_,y,b,v];for(let n=0;n(a.buyUpgrade(n,t,e),!1)};i>=3?s.tooltipleft=n.desc:s.tooltip=n.desc,r.appendChild(Object(f.createElement)("a",s))}(r[i],a,this,n,e)}}return n.appendChild(g),n.appendChild(_),n.appendChild(y),n.appendChild(b),n.appendChild(v),n};const N={gangContentCreated:!1,gangContainer:null,managementButton:null,territoryButton:null,gangManagementSubpage:null,gangTerritorySubpage:null,gangDesc:null,gangInfo:null,gangRecruitMemberButton:null,gangRecruitRequirementText:null,gangExpandAllButton:null,gangCollapseAllButton:null,gangMemberFilter:null,gangManageEquipmentButton:null,gangMemberList:null,gangMemberPanels:{},gangMemberUpgradeBoxOpened:!1,gangMemberUpgradeBox:null,gangMemberUpgradeBoxContent:null,gangMemberUpgradeBoxFilter:null,gangMemberUpgradeBoxDiscount:null,gangMemberUpgradeBoxElements:null,gangTerritoryDescText:null,gangTerritoryWarfareCheckbox:null,gangTerritoryWarfareCheckboxLabel:null,gangTerritoryWarfareClashChance:null,gangTerritoryDeathNotifyCheckbox:null,gangTerritoryDeathNotifyCheckboxLabel:null,gangTerritoryInfoText:null};S.prototype.displayGangContent=function(e){if(!N.gangContentCreated||null==N.gangContainer){N.gangContentCreated=!0,N.gangContainer=Object(f.createElement)("div",{id:"gang-container",class:"generic-menupage-container"});var t=this.facName;this.members,this.wanted,this.respect;N.gangContainer.appendChild(Object(f.createElement)("a",{class:"a-link-button",display:"inline-block",innerText:"Back",clickListener:()=>(i.Engine.loadFactionContent(),Object(l.displayFactionContent)(t),!1)})),N.managementButton=Object(f.createElement)("a",{id:"gang-management-subpage-button",class:"a-link-button-inactive",display:"inline-block",innerHTML:"Gang Management (Alt+1)",clickListener:()=>(N.gangManagementSubpage.style.display="block",N.gangTerritorySubpage.style.display="none",N.managementButton.classList.toggle("a-link-button-inactive"),N.managementButton.classList.toggle("a-link-button"),N.territoryButton.classList.toggle("a-link-button-inactive"),N.territoryButton.classList.toggle("a-link-button"),this.updateGangContent(),!1)}),N.territoryButton=Object(f.createElement)("a",{id:"gang-territory-subpage-button",class:"a-link-button",display:"inline-block",innerHTML:"Gang Territory (Alt+2)",clickListener:()=>(N.gangManagementSubpage.style.display="none",N.gangTerritorySubpage.style.display="block",N.managementButton.classList.toggle("a-link-button-inactive"),N.managementButton.classList.toggle("a-link-button"),N.territoryButton.classList.toggle("a-link-button-inactive"),N.territoryButton.classList.toggle("a-link-button"),this.updateGangContent(),!1)}),N.gangContainer.appendChild(N.managementButton),N.gangContainer.appendChild(N.territoryButton),N.gangManagementSubpage=Object(f.createElement)("div",{display:"block",id:"gang-management-subpage"});var n="";n=this.isHackingGang?"Ethical Hacking":"Vigilante Justice",N.gangDesc=Object(f.createElement)("p",{width:"70%",innerHTML:"This page is used to manage your gang members and get an overview of your gang's stats.

    If a gang member is not earning much money or respect, the task that you have assigned to that member might be too difficult. Consider training that member's stats or choosing an easier task. The tasks closer to the top of the dropdown list are generally easier. Alternatively, the gang member's low production might be due to the fact that your wanted level is too high. Consider assigning a few members to the '"+n+"' task to lower your wanted level.

    Installing Augmentations does NOT reset your progress with your Gang. Furthermore, after installing Augmentations, you will automatically be a member of whatever Faction you created your gang with.

    You can also manage your gang programmatically through Netscript using the Gang API"}),N.gangManagementSubpage.appendChild(N.gangDesc),N.gangInfo=Object(f.createElement)("p",{id:"gang-info",width:"70%"}),N.gangManagementSubpage.appendChild(N.gangInfo),N.gangRecruitMemberButton=Object(f.createElement)("a",{id:"gang-management-recruit-member-btn",class:"a-link-button-inactive",innerHTML:"Recruit Gang Member",display:"inline-block",margin:"10px",clickListener:()=>{const e="recruit-gang-member-popup";let t;const n=Object(f.createElement)("p",{innerText:"Please enter a name for your new Gang member:"}),r=Object(f.createElement)("br"),a=Object(f.createElement)("input",{onkeyup:e=>{e.keyCode===_.KEY.ENTER&&t.click()},placeholder:"Name must be unique",type:"text"});t=Object(f.createElement)("a",{class:"std-button",clickListener:()=>{let t=a.value;return""===t?(Object(p.dialogBoxCreate)("You must enter a name for your Gang member!"),!1):this.canRecruitMember()?this.recruitMember(t)?(Object(k.removeElementById)(e),!1):(Object(p.dialogBoxCreate)("You already have a gang member with this name!"),!1):(Object(p.dialogBoxCreate)("You cannot recruit another Gang member!"),!1)},innerText:"Recruit Gang Member"});const i=Object(f.createElement)("a",{class:"std-button",clickListener:()=>(Object(k.removeElementById)(e),!1),innerText:"Cancel"});Object(b.createPopup)(e,[n,r,a,t,i]),a.focus()}}),N.gangManagementSubpage.appendChild(N.gangRecruitMemberButton),N.gangRecruitRequirementText=Object(f.createElement)("p",{color:"red",id:"gang-recruit-requirement-text",margin:"10px"}),N.gangManagementSubpage.appendChild(N.gangRecruitRequirementText),N.gangManagementSubpage.appendChild(Object(f.createElement)("br",{})),N.gangExpandAllButton=Object(f.createElement)("a",{class:"a-link-button",display:"inline-block",innerHTML:"Expand All",clickListener:()=>{for(var e=N.gangManagementSubpage.getElementsByClassName("accordion-header"),t=0;t{for(var e=N.gangManagementSubpage.getElementsByClassName("accordion-header"),t=0;t{this.displayGangMemberList()}}),N.gangManageEquipmentButton=Object(f.createElement)("a",{class:"a-link-button",display:"inline-block",innerHTML:"Manage Equipment",clickListener:()=>{this.createGangMemberUpgradeBox(e)}}),N.gangManagementSubpage.appendChild(N.gangExpandAllButton),N.gangManagementSubpage.appendChild(N.gangCollapseAllButton),N.gangManagementSubpage.appendChild(N.gangMemberFilter),N.gangManagementSubpage.appendChild(N.gangManageEquipmentButton),N.gangMemberList=Object(f.createElement)("ul",{id:"gang-member-list"}),this.displayGangMemberList(),N.gangManagementSubpage.appendChild(N.gangMemberList),N.gangTerritorySubpage=Object(f.createElement)("div",{id:"gang-territory-subpage",display:"none"}),N.gangTerritoryDescText=Object(f.createElement)("p",{width:"70%",innerHTML:"This page shows how much territory your Gang controls. This statistic is listed as a percentage, which represents how much of the total territory you control.

    Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The accumulation rate is determined by the stats of all Gang members you have assigned to the 'Territory Warfare' task. Gang members that are not assigned to this task do not contribute to your gang's power. Your gang also loses a small amount of power whenever you lose a clash

    NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of whether you win or lose the clash. A gang member being killed results in both respect and power loss for your gang.

    The amount of territory you have affects all aspects of your Gang members' production, including money, respect, and wanted level. It is very beneficial to have high territory control.

    "}),N.gangTerritorySubpage.appendChild(N.gangTerritoryDescText),N.gangTerritoryWarfareCheckbox=Object(f.createElement)("input",{display:"inline-block",id:"gang-management-territory-warfare-checkbox",changeListener:()=>{this.territoryWarfareEngaged=N.gangTerritoryWarfareCheckbox.checked},margin:"2px",type:"checkbox"}),N.gangTerritoryWarfareCheckbox.checked=this.territoryWarfareEngaged,N.gangTerritoryWarfareCheckboxLabel=Object(f.createElement)("label",{color:"white",display:"inline-block",for:"gang-management-territory-warfare-checkbox",innerText:"Engage in Territory Warfare",tooltip:"Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance to gradually decrease until it reaches 0%"}),N.gangTerritorySubpage.appendChild(N.gangTerritoryWarfareCheckbox),N.gangTerritorySubpage.appendChild(N.gangTerritoryWarfareCheckboxLabel),N.gangTerritorySubpage.appendChild(Object(f.createElement)("br")),N.gangTerritoryWarfareClashChance=Object(f.createElement)("p",{display:"inline-block"}),N.gangTerritorySubpage.appendChild(N.gangTerritoryWarfareClashChance),N.gangTerritorySubpage.appendChild(Object(f.createElement)("div",{class:"help-tip",display:"inline-block",innerText:"?",clickListener:()=>{Object(p.dialogBoxCreate)("This percentage represents the chance you have of 'clashing' with with another gang. If you do not wish to gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.")}})),N.gangTerritoryDeathNotifyCheckbox=Object(f.createElement)("input",{display:"inline-block",id:"gang-management-notify-member-death-checkbox",changeListener:()=>{this.notifyMemberDeath=N.gangTerritoryDeathNotifyCheckbox.checked},margin:"2px",type:"checkbox"}),N.gangTerritoryDeathNotifyCheckbox.checked=this.notifyMemberDeath,N.gangTerritoryDeathNotifyCheckboxLabel=Object(f.createElement)("label",{color:"white",display:"inline-block",for:"gang-management-notify-member-death-checkbox",innerText:"Notify about Gang Member Deaths",tooltip:"If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies in a territory clash."}),N.gangTerritorySubpage.appendChild(Object(f.createElement)("br")),N.gangTerritorySubpage.appendChild(N.gangTerritoryDeathNotifyCheckbox),N.gangTerritorySubpage.appendChild(N.gangTerritoryDeathNotifyCheckboxLabel),N.gangTerritorySubpage.appendChild(Object(f.createElement)("br"));var r=Object(f.createElement)("fieldset",{display:"block",margin:"6px",width:"50%"});N.gangTerritoryInfoText=Object(f.createElement)("p"),r.appendChild(N.gangTerritoryInfoText),N.gangTerritorySubpage.appendChild(r),N.gangContainer.appendChild(N.gangTerritorySubpage),N.gangContainer.appendChild(N.gangManagementSubpage),document.getElementById("entire-game-container").appendChild(N.gangContainer)}N.gangContainer.style.display="block",this.updateGangContent()},S.prototype.displayGangMemberList=function(){Object(v.removeChildrenFromElement)(N.gangMemberList),N.gangMemberPanels={};const e=this.members,t=N.gangMemberFilter.value.toString();for(var n=0;n-1||e[n].task.indexOf(t)>-1)&&this.createGangMemberDisplayElement(e[n])},S.prototype.updateGangContent=function(){if(N.gangContentCreated)if(N.gangMemberUpgradeBoxOpened&&(N.gangMemberUpgradeBoxDiscount.childNodes[0].nodeValue="Discount: -"+u.numeralWrapper.format(1-1/this.getDiscount(),"0.00%")),"block"===N.gangTerritorySubpage.style.display){N.gangTerritoryWarfareClashChance.innerText=`Territory Clash Chance: ${u.numeralWrapper.format(this.territoryClashChance,"0.000%")}`,N.gangTerritoryWarfareCheckbox.checked=this.territoryWarfareEngaged,N.gangTerritoryInfoText.innerHTML="";const e=P[this.facName].power;for(const t in P)if(P.hasOwnProperty(t)){const n=P[t];let r,a=100*n.territory;if(r=a<=0?Object(m.formatNumber)(0,2):a>=100?Object(m.formatNumber)(100,2):Object(m.formatNumber)(a,2),t===this.facName){let e=`${t}
    Power: ${Object(m.formatNumber)(n.power,6)}
    `;e+=`Territory: ${r}%

    `,N.gangTerritoryInfoText.innerHTML+=e}else{const a=e/(n.power+e);let i=`${t}
    Power: ${Object(m.formatNumber)(n.power,6)}
    `;i+=`Territory: ${r}%
    `,i+=`Chance to win clash with this gang: ${u.numeralWrapper.format(a,"0.000%")}

    `,N.gangTerritoryInfoText.innerHTML+=i}}}else{if(N.gangInfo instanceof Element){var e,t=s.Factions[this.facName];e=t instanceof o.Faction?t.playerReputation:"ERROR",Object(v.removeChildrenFromElement)(N.gangInfo),N.gangInfo.appendChild(Object(f.createElement)("p",{display:"inline-block",innerText:"Respect: "+Object(m.formatNumber)(this.respect,6)+" ("+Object(m.formatNumber)(5*this.respectGainRate,6)+" / sec)",tooltip:"Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction."})),N.gangInfo.appendChild(Object(f.createElement)("br")),N.gangInfo.appendChild(Object(f.createElement)("p",{display:"inline-block",innerText:"Wanted Level: "+Object(m.formatNumber)(this.wanted,6)+" ("+Object(m.formatNumber)(5*this.wantedGainRate,6)+" / sec)",tooltip:"Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1."})),N.gangInfo.appendChild(Object(f.createElement)("br"));var n=this.getWantedPenalty();n=100*(1-n),N.gangInfo.appendChild(Object(f.createElement)("p",{display:"inline-block",innerText:`Wanted Level Penalty: -${Object(m.formatNumber)(n,2)}%`,tooltip:"Penalty for respect and money gain rates due to Wanted Level"})),N.gangInfo.appendChild(Object(f.createElement)("br")),N.gangInfo.appendChild(Object(f.createElement)("p",{display:"inline-block",innerText:`Money gain rate: ${u.numeralWrapper.format(5*this.moneyGainRate,"$0.000a")} / sec`})),N.gangInfo.appendChild(Object(f.createElement)("br"));var r=100*P[this.facName].territory;let a;a=r<=0?Object(m.formatNumber)(0,2):r>=100?Object(m.formatNumber)(100,2):Object(m.formatNumber)(r,2),N.gangInfo.appendChild(Object(f.createElement)("p",{display:"inline-block",innerText:`Territory: ${Object(m.formatNumber)(a,3)}%`,tooltip:"The percentage of total territory your Gang controls"})),N.gangInfo.appendChild(Object(f.createElement)("br")),N.gangInfo.appendChild(Object(f.createElement)("p",{display:"inline-block",innerText:"Faction reputation: "+Object(m.formatNumber)(e,3)})),N.gangInfo.appendChild(Object(f.createElement)("br"));const l=1e3/i.Engine._idleSpeed;N.gangInfo.appendChild(Object(f.createElement)("p",{innerText:`Bonus time(s): ${this.storedCycles/l}`,display:"inline-block",tooltip:"You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed"})),N.gangInfo.appendChild(Object(f.createElement)("br"))}else console.error("gang-info DOM element DNE");const a=this.members.length,l=this.getRespectNeededToRecruitMember(),c=N.gangRecruitMemberButton;a>=30?(c.className="a-link-button-inactive",N.gangRecruitRequirementText.style.display="inline-block",N.gangRecruitRequirementText.innerHTML="You have reached the maximum amount of gang members"):this.canRecruitMember()?(c.className="a-link-button",N.gangRecruitRequirementText.style.display="none"):(c.className="a-link-button-inactive",N.gangRecruitRequirementText.style.display="inline-block",N.gangRecruitRequirementText.innerHTML=`${Object(m.formatNumber)(l,2)} respect needed to recruit next member`);for(let e=0;e")});N.gangMemberPanels[t].statsDiv=i;const o=Object(f.createElement)("pre",{display:"inline",id:t+"gang-member-stats-text"}),s=Object(f.createElement)("br"),l=Object(f.createElement)("button",{class:"accordion-button",innerText:"Ascend",clickListener:()=>{const t=`gang-management-ascend-member ${e.name}`,n=e.getAscensionResults(),r=Object(f.createElement)("pre",{innerText:["Are you sure you want to ascend this member? (S)he will lose all of","his non-Augmentation upgrades and his/her stats will reset back to 1.","",`Furthermore, your gang will lose ${u.numeralWrapper.format(e.earnedRespect,"0.000000")} respect`,"","In return, (s)he will gain the following permanent boost to stat multipliers:\n",`Hacking: +${u.numeralWrapper.format(n.hack,"0.00%")}`,`Strength: +${u.numeralWrapper.format(n.str,"0.00%")}`,`Defense: +${u.numeralWrapper.format(n.def,"0.00%")}`,`Dexterity: +${u.numeralWrapper.format(n.dex,"0.00%")}`,`Agility: +${u.numeralWrapper.format(n.agi,"0.00%")}`,`Charisma: +${u.numeralWrapper.format(n.cha,"0.00%")}`].join("\n")}),a=Object(f.createElement)("button",{class:"std-button",clickListener:()=>(this.ascendMember(e),this.updateGangMemberDisplayElement(e),Object(k.removeElementById)(t),!1),innerText:"Ascend"}),i=Object(f.createElement)("button",{class:"std-button",clickListener:()=>(Object(k.removeElementById)(t),!1),innerText:"Cancel"});Object(b.createPopup)(t,[r,a,i])}}),c=Object(f.createElement)("div",{class:"help-tip",clickListener:()=>{Object(p.dialogBoxCreate)(["Ascending a Gang Member resets the member's progress and stats in exchange","for a permanent boost to their stat multipliers.","

    The additional stat multiplier that the Gang Member gains upon ascension","is based on the amount of multipliers the member has from non-Augmentation Equipment.","

    Upon ascension, the member will lose all of its non-Augmentation Equipment and your","gang will lose respect equal to the total respect earned by the member."].join(" "))},innerText:"?",marginTop:"5px"});i.appendChild(o),i.appendChild(s),i.appendChild(l),i.appendChild(c);const h=Object(f.createElement)("div",{class:"gang-member-info-div",id:t+"gang-member-task"}),m=Object(f.createElement)("select",{class:"dropdown",id:t+"gang-member-task-selector"});let d=this.getAllTaskNames();d.unshift("---");for(var g=0;g{var t=m.options[m.selectedIndex].text;e.assignToTask(t),this.setGangMemberTaskDescription(e,t),this.updateGangContent()}),w.hasOwnProperty(e.task)){var v=e.task,E=0;for(let e=0;e"))}var a=document.getElementById(t+"gang-member-gain-info");a&&(a.innerHTML=[`Money: $ ${Object(m.formatNumber)(5*e.calculateMoneyGain(this),2)} / sec`,`Respect: ${Object(m.formatNumber)(5*e.calculateRespectGain(this),6)} / sec`,`Wanted Level: ${Object(m.formatNumber)(5*e.calculateWantedLevelGain(this),6)} / sec`,`Total Respect Earned: ${Object(m.formatNumber)(e.earnedRespect,6)}`].join("
    "));const i=document.getElementById(t+"gang-member-task-selector");if(i){let t=this.getAllTaskNames();if(t.unshift("---"),w.hasOwnProperty(e.task)){const n=e.task;let r=0;for(let e=0;e0&&t>=n;)++e,t-=n,n*=r.CONSTANTS.FactionReputationToFavorMult;return[e,t]}toJSON(){return i.Generic_toJSON("Faction",this)}}t.Faction=o,i.Reviver.constructors.Faction=o},function(e,t,n){"use strict";n.d(t,"a",function(){return f}),n.d(t,"b",function(){return _}),n.d(t,"e",function(){return g}),n.d(t,"d",function(){return b}),n.d(t,"c",function(){return E});var r=n(65),a=(n(16),n(11)),i=n(4),o=n(30),s=n(75),l=n(0),c=n(84),u=n(22),p=n(19),h=n(9),m=n(18);function d(e,t=!1){e.recvd=!0,!t&&p.Settings.SuppressMessages||g(e),function(e,t){var n=Object(u.GetServerByHostname)(t);if(null==n)return void console.warn(`Could not find server ${t}`);for(var r=0;r

    This message was saved as "+e.filename+" onto your home computer.";Object(h.dialogBoxCreate)(t)}function _(){var e=f[v.Jumper0],t=f[v.Jumper1],n=f[v.Jumper2],r=f[v.Jumper3],u=f[v.Jumper4],p=f[v.CyberSecTest],m=f[v.NiteSecTest],g=f[v.BitRunnersTest],_=f[v.RedPill],y=!1;if(a.Augmentations[i.AugmentationNames.TheRedPill].owned&&(y=!0),_&&y&&0===l.Player.sourceFiles.length&&!c.b&&!s.c)h.dialogBoxOpened||d(_,!0);else if(_&&y)c.b||s.c||h.dialogBoxOpened||d(_);else if(e&&!e.recvd&&l.Player.hacking_skill>=25){d(e);const t=o.Programs.Flight.name,n=l.Player.getHomeComputer();n.programs.includes(t)||n.programs.push(t)}else t&&!t.recvd&&l.Player.hacking_skill>=40?d(t):p&&!p.recvd&&l.Player.hacking_skill>=50?d(p):n&&!n.recvd&&l.Player.hacking_skill>=175?d(n):m&&!m.recvd&&l.Player.hacking_skill>=200?d(m):r&&!r.recvd&&l.Player.hacking_skill>=350?d(r):u&&!u.recvd&&l.Player.hacking_skill>=490?d(u):g&&!g.recvd&&l.Player.hacking_skill>=500&&d(g)}function y(e){f[e.filename]=e}let f={};function b(e){f=JSON.parse(e,m.Reviver)}let v={Jumper0:"j0.msg",Jumper1:"j1.msg",Jumper2:"j2.msg",Jumper3:"j3.msg",Jumper4:"j4.msg",CyberSecTest:"csec-test.msg",NiteSecTest:"nitesec-test.msg",BitRunnersTest:"19dfj3l1nd.msg",RedPill:"icarus.msg"};function E(){f={},y(new r.Message(v.Jumper0,"I know you can sense it. I know you're searching for it. It's why you spend night after night at your computer.

    It's real, I've seen it. And I can help you find it. But not right now. You're not ready yet.

    Use this program to track your progress

    The fl1ght.exe program was added to your home computer

    -jump3R")),y(new r.Message(v.Jumper1,"Soon you will be contacted by a hacking group known as CyberSec. They can help you with your search.

    You should join them, garner their favor, and exploit them for their Augmentations. But do not trust them. They are not what they seem. No one is.

    -jump3R")),y(new r.Message(v.Jumper2,"Do not try to save the world. There is no world to save. If you want to find the truth, worry only about yourself. Ethics and morals will get you killed.

    Watch out for a hacking group known as NiteSec.

    -jump3R")),y(new r.Message(v.Jumper3,"You must learn to walk before you can run. And you must run before you can fly. Look for the black hand.

    I.I.I.I

    -jump3R")),y(new r.Message(v.Jumper4,"To find what you are searching for, you must understand the bits. The bits are all around us. The runners will help you.

    -jump3R")),y(new r.Message(v.CyberSecTest,"We've been watching you. Your skills are very impressive. But you're wasting your talents. If you join us, you can put your skills to good use and change the world for the better. If you join us, we can unlock your full potential.

    But first, you must pass our test. Find and hack our server using the Terminal.

    -CyberSec")),y(new r.Message(v.NiteSecTest,"People say that the corrupted governments and corporations rule the world. Yes, maybe they do. But do you know who everyone really fears? People like us. Because they can't hide from us. Because they can't fight shadows and ideas with bullets.

    Join us, and people will fear you, too.

    Find and hack our hidden server using the Terminal. Then, we will contact you again.

    -NiteSec")),y(new r.Message(v.BitRunnersTest,"We know what you are doing. We know what drives you. We know what you are looking for.

    We can help you find the answers.

    run4theh111z")),y(new r.Message(v.RedPill,"@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%
    )@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)
    @_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB
    DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)"))}},function(e,t,n){"use strict";n.d(t,"f",function(){return I}),n.d(t,"e",function(){return x}),n.d(t,"d",function(){return T}),n.d(t,"a",function(){return M}),n.d(t,"b",function(){return w}),n.d(t,"c",function(){return A});var r=n(16),a=n(11),i=n(126),o=n(4),s=n(20),l=n(8),c=n(13),u=n(42),p=n(0),h=n(140),m=n(98),d=n(138),g=(n(117),n(106),n(53)),_=n(19),y=n(101),f=n(9),b=n(110),v=(n(18),n(5)),E=n(111),k=n(3),C=n(51),P=n(41);function O(e){var t=e.name;a.Augmentations[t]=e}function T(){for(var e in c.Factions)c.Factions.hasOwnProperty(e)&&(c.Factions[e].augmentations=[]);Object(E.clearObject)(a.Augmentations);const t=new r.Augmentation({name:o.AugmentationNames.HemoRecirculator,moneyCost:9e6,repCost:4e3,info:"A heart implant that greatly increases the body's ability to effectively use and pump blood.

    This augmentation increases all of the player's combat stats by 8%.",strength_mult:1.08,defense_mult:1.08,agility_mult:1.08,dexterity_mult:1.08});t.addToFactions(["Tetrads","The Dark Army","The Syndicate"]),w(o.AugmentationNames.HemoRecirculator)&&delete a.Augmentations[o.AugmentationNames.HemoRecirculator],O(t);const n=new r.Augmentation({name:o.AugmentationNames.Targeting1,moneyCost:3e6,repCost:2e3,info:"This cranial implant is embedded within the player's inner ear structure and optic nerves. It regulates and enhances the user's balance and hand-eye coordination. It is also capable of augmenting reality by projecting digital information directly onto the retina. These enhancements allow the player to better lock-on and keep track of enemies.

    This augmentation increases the player's dexterity by 10%.",dexterity_mult:1.1});n.addToFactions(["Slum Snakes","The Dark Army","The Syndicate","Sector-12","Volhaven","Ishima","OmniTek Incorporated","KuaiGong International","Blade Industries"]),w(o.AugmentationNames.Targeting1)&&delete a.Augmentations[o.AugmentationNames.Targeting1],O(n);const i=new r.Augmentation({name:o.AugmentationNames.Targeting2,moneyCost:85e5,repCost:3500,info:"This is an upgrade of the Augmented Targeting I cranial implant, which is capable of augmenting reality and enhances the user's balance and hand-eye coordination.

    This augmentation increases the player's dexterity by 20%.",prereqs:[o.AugmentationNames.Targeting1],dexterity_mult:1.2});i.addToFactions(["The Dark Army","The Syndicate","Sector-12","Volhaven","Ishima","OmniTek Incorporated","KuaiGong International","Blade Industries"]),w(o.AugmentationNames.Targeting2)&&delete a.Augmentations[o.AugmentationNames.Targeting2],O(i);const u=new r.Augmentation({name:o.AugmentationNames.Targeting3,moneyCost:23e6,repCost:11e3,info:"This is an upgrade of the Augmented Targeting II cranial implant, which is capable of augmenting reality and enhances the user's balance and hand-eye coordination.

    This augmentation increases the player's dexterity by 30%.",prereqs:[o.AugmentationNames.Targeting2],dexterity_mult:1.3});u.addToFactions(["The Dark Army","The Syndicate","OmniTek Incorporated","KuaiGong International","Blade Industries","The Covenant"]),w(o.AugmentationNames.Targeting3)&&delete a.Augmentations[o.AugmentationNames.Targeting3],O(u);const h=new r.Augmentation({name:o.AugmentationNames.SyntheticHeart,moneyCost:575e6,repCost:3e5,info:"This advanced artificial heart, created from plasteel and graphene, is capable of pumping more blood at much higher efficiencies than a normal human heart.

    This augmentation increases the player's agility and strength by 50%.",agility_mult:1.5,strength_mult:1.5});h.addToFactions(["KuaiGong International","Fulcrum Secret Technologies","Speakers for the Dead","NWO","The Covenant","Daedalus","Illuminati"]),w(o.AugmentationNames.SyntheticHeart)&&delete a.Augmentations[o.AugmentationNames.SyntheticHeart],O(h);const m=new r.Augmentation({name:o.AugmentationNames.SynfibrilMuscle,repCost:175e3,moneyCost:225e6,info:"The myofibrils in human muscles are injected with special chemicals that react with the proteins inside the myofibrils, altering their underlying structure. The end result is muscles that are stronger and more elastic. Scientists have named these artificially enhanced units 'synfibrils'.

    This augmentation increases the player's strength and defense by 30%.",strength_mult:1.3,defense_mult:1.3});m.addToFactions(["KuaiGong International","Fulcrum Secret Technologies","Speakers for the Dead","NWO","The Covenant","Daedalus","Illuminati","Blade Industries"]),w(o.AugmentationNames.SynfibrilMuscle)&&delete a.Augmentations[o.AugmentationNames.SynfibrilMuscle],O(m);const d=new r.Augmentation({name:o.AugmentationNames.CombatRib1,repCost:3e3,moneyCost:475e4,info:"The human body's ribs are replaced with artificial ribs that automatically and continuously release cognitive and performance-enhancing drugs into the bloodstream, improving the user's abilities in combat.

    This augmentation increases the player's strength and defense by 10%.",strength_mult:1.1,defense_mult:1.1});d.addToFactions(["Slum Snakes","The Dark Army","The Syndicate","Sector-12","Volhaven","Ishima","OmniTek Incorporated","KuaiGong International","Blade Industries"]),w(o.AugmentationNames.CombatRib1)&&delete a.Augmentations[o.AugmentationNames.CombatRib1],O(d);const g=new r.Augmentation({name:o.AugmentationNames.CombatRib2,repCost:7500,moneyCost:13e6,info:"This is an upgrade to the Combat Rib I augmentation, and is capable of releasing even more potent combat-enhancing drugs into the bloodstream.

    This augmentation increases the player's strength and defense by 14%.",prereqs:[o.AugmentationNames.CombatRib1],strength_mult:1.14,defense_mult:1.14});g.addToFactions(["The Dark Army","The Syndicate","Sector-12","Volhaven","Ishima","OmniTek Incorporated","KuaiGong International","Blade Industries"]),w(o.AugmentationNames.CombatRib2)&&delete a.Augmentations[o.AugmentationNames.CombatRib2],O(g);const _=new r.Augmentation({name:o.AugmentationNames.CombatRib3,repCost:14e3,moneyCost:24e6,info:"This is an upgrade to the Combat Rib II augmentation, and is capable of releasing even more potent combat-enhancing drugs into the bloodstream

    .This augmentation increases the player's strength and defense by 18%.",prereqs:[o.AugmentationNames.CombatRib2],strength_mult:1.18,defense_mult:1.18});_.addToFactions(["The Dark Army","The Syndicate","OmniTek Incorporated","KuaiGong International","Blade Industries","The Covenant"]),w(o.AugmentationNames.CombatRib3)&&delete a.Augmentations[o.AugmentationNames.CombatRib3],O(_);const y=new r.Augmentation({name:o.AugmentationNames.NanofiberWeave,repCost:15e3,moneyCost:25e6,info:"Synthetic nanofibers are woven into the skin's extracellular matrix using electrospinning. This improves the skin's ability to regenerate itself and protect the body from external stresses and forces.

    This augmentation increases the player's strength and defense by 20%.",strength_mult:1.2,defense_mult:1.2});y.addToFactions(["Tian Di Hui","The Syndicate","The Dark Army","Speakers for the Dead","Blade Industries","Fulcrum Secret Technologies","OmniTek Incorporated"]),w(o.AugmentationNames.NanofiberWeave)&&delete a.Augmentations[o.AugmentationNames.NanofiberWeave],O(y);const f=new r.Augmentation({name:o.AugmentationNames.SubdermalArmor,repCost:35e4,moneyCost:65e7,info:"The NEMEAN Subdermal Weave is a thin, light-weight, graphene plating that houses a dilatant fluid. The material is implanted underneath the skin, and is the most advanced form of defensive enhancement that has ever been created. The dilatant fluid, despite being thin and light, is extremely effective at stopping piercing blows and reducing blunt trauma. The properties of graphene allow the plating to mitigate damage from any fire-related or electrical traumas.

    This augmentation increases the player's defense by 120%.",defense_mult:2.2});f.addToFactions(["The Syndicate","Fulcrum Secret Technologies","Illuminati","Daedalus","The Covenant"]),w(o.AugmentationNames.SubdermalArmor)&&delete a.Augmentations[o.AugmentationNames.SubdermalArmor],O(f);const b=new r.Augmentation({name:o.AugmentationNames.WiredReflexes,repCost:500,moneyCost:5e5,info:"Synthetic nerve-enhancements are injected into all major parts of the somatic nervous system, supercharging the body's ability to send signals through neurons. This results in increased reflex speed.

    This augmentation increases the player's agility and dexterity by 5%.",agility_mult:1.05,dexterity_mult:1.05});b.addToFactions(["Tian Di Hui","Slum Snakes","Sector-12","Volhaven","Aevum","Ishima","The Syndicate","The Dark Army","Speakers for the Dead"]),w(o.AugmentationNames.WiredReflexes)&&delete a.Augmentations[o.AugmentationNames.WiredReflexes],O(b);const v=new r.Augmentation({name:o.AugmentationNames.GrapheneBoneLacings,repCost:45e4,moneyCost:85e7,info:"A graphene-based material is grafted and fused into the user's bones, significantly increasing their density and tensile strength.

    This augmentation increases the player's strength and defense by 70%.",strength_mult:1.7,defense_mult:1.7});v.addToFactions(["Fulcrum Secret Technologies","The Covenant"]),w(o.AugmentationNames.GrapheneBoneLacings)&&delete a.Augmentations[o.AugmentationNames.GrapheneBoneLacings],O(v);const k=new r.Augmentation({name:o.AugmentationNames.BionicSpine,repCost:18e3,moneyCost:25e6,info:"An artificial spine created from plasteel and carbon fibers that completely replaces the organic spine. Not only is the Bionic Spine physically stronger than a human spine, but it is also capable of digitally stimulating and regulating the neural signals that are sent and received by the spinal cord. This results in greatly improved senses and reaction speeds.

    This augmentation increases all of the player's combat stats by 15%.",strength_mult:1.15,defense_mult:1.15,agility_mult:1.15,dexterity_mult:1.15});k.addToFactions(["Speakers for the Dead","The Syndicate","KuaiGong International","OmniTek Incorporated","Blade Industries"]),w(o.AugmentationNames.BionicSpine)&&delete a.Augmentations[o.AugmentationNames.BionicSpine],O(k);const C=new r.Augmentation({name:o.AugmentationNames.GrapheneBionicSpine,repCost:65e4,moneyCost:12e8,info:"An upgrade to the Bionic Spine augmentation. It fuses the implant with an advanced graphene material to make it much stronger and lighter.

    This augmentation increases all of the player's combat stats by 60%.",prereqs:[o.AugmentationNames.BionicSpine],strength_mult:1.6,defense_mult:1.6,agility_mult:1.6,dexterity_mult:1.6});C.addToFactions(["Fulcrum Secret Technologies","ECorp"]),w(o.AugmentationNames.GrapheneBionicSpine)&&delete a.Augmentations[o.AugmentationNames.GrapheneBionicSpine],O(C);const P=new r.Augmentation({name:o.AugmentationNames.BionicLegs,repCost:6e4,moneyCost:75e6,info:"Cybernetic legs created from plasteel and carbon fibers that completely replace the user's organic legs.

    This augmentation increases the player's agility by 60%.",agility_mult:1.6});P.addToFactions(["Speakers for the Dead","The Syndicate","KuaiGong International","OmniTek Incorporated","Blade Industries"]),w(o.AugmentationNames.BionicLegs)&&delete a.Augmentations[o.AugmentationNames.BionicLegs],O(P);const T=new r.Augmentation({name:o.AugmentationNames.GrapheneBionicLegs,repCost:3e5,moneyCost:9e8,info:"An upgrade to the Bionic Legs augmentation. It fuses the implant with an advanced graphene material to make it much stronger and lighter.

    This augmentation increases the player's agility by 150%.",prereqs:[o.AugmentationNames.BionicLegs],agility_mult:2.5});T.addToFactions(["MegaCorp","ECorp","Fulcrum Secret Technologies"]),w(o.AugmentationNames.GrapheneBionicLegs)&&delete a.Augmentations[o.AugmentationNames.GrapheneBionicLegs],O(T);const M=new r.Augmentation({name:o.AugmentationNames.SpeechProcessor,repCost:3e3,moneyCost:1e7,info:"A cochlear implant with an embedded computer that analyzes incoming speech. The embedded computer processes characteristics of incoming speech, such as tone and inflection, to pick up on subtle cues and aid in social interactions.

    This augmentation increases the player's charisma by 20%.",charisma_mult:1.2});M.addToFactions(["Tian Di Hui","Chongqing","Sector-12","New Tokyo","Aevum","Ishima","Volhaven","Silhouette"]),w(o.AugmentationNames.SpeechProcessor)&&delete a.Augmentations[o.AugmentationNames.SpeechProcessor],O(M);const x=new r.Augmentation({name:o.AugmentationNames.TITN41Injection,repCost:1e4,moneyCost:38e6,info:"TITN is a series of viruses that targets and alters the sequences of human DNA in genes that control personality. The TITN-41 strain alters these genes so that the subject becomes more outgoing and socialable.

    This augmentation increases the player's charisma and charisma experience gain rate by 15%.",charisma_mult:1.15,charisma_exp_mult:1.15});x.addToFactions(["Silhouette"]),w(o.AugmentationNames.TITN41Injection)&&delete a.Augmentations[o.AugmentationNames.TITN41Injection],O(x);const A=new r.Augmentation({name:o.AugmentationNames.EnhancedSocialInteractionImplant,repCost:15e4,moneyCost:275e6,info:"A cranial implant that greatly assists in the user's ability to analyze social situations and interactions. The system uses a wide variety of factors such as facial expression, body language, and the voice's tone/inflection to determine the best course of action during socialsituations. The implant also uses deep learning software to continuously learn new behaviorpatterns and how to best respond.

    This augmentation increases the player's charisma and charisma experience gain rate by 60%.",charisma_mult:1.6,charisma_exp_mult:1.6});A.addToFactions(["Bachman & Associates","NWO","Clarke Incorporated","OmniTek Incorporated","Four Sigma"]),w(o.AugmentationNames.EnhancedSocialInteractionImplant)&&delete a.Augmentations[o.AugmentationNames.EnhancedSocialInteractionImplant],O(A);const R=new r.Augmentation({name:o.AugmentationNames.BitWire,repCost:1500,moneyCost:2e6,info:"A small brain implant embedded in the cerebrum. This regulates and improves the brain's computing capabilities.

    This augmentation increases the player's hacking skill by 5%.",hacking_mult:1.05});R.addToFactions(["CyberSec","NiteSec"]),w(o.AugmentationNames.BitWire)&&delete a.Augmentations[o.AugmentationNames.BitWire],O(R);const N=new r.Augmentation({name:o.AugmentationNames.ArtificialBioNeuralNetwork,repCost:11e4,moneyCost:6e8,info:"A network consisting of millions of nanoprocessors is embedded into the brain. The network is meant to mimick the way a biological brain solves a problem, which each nanoprocessor acting similar to the way a neuron would in a neural network. However, these nanoprocessors are programmed to perform computations much faster than organic neurons, allowing its user to solve much more complex problems at a much faster rate.

    This augmentation:
    Increases the player's hacking speed by 3%.
    Increases the amount of money the player's gains from hacking by 15%.
    Increases the player's hacking skill by 12%.",hacking_speed_mult:1.03,hacking_money_mult:1.15,hacking_mult:1.12});N.addToFactions(["BitRunners","Fulcrum Secret Technologies"]),w(o.AugmentationNames.ArtificialBioNeuralNetwork)&&delete a.Augmentations[o.AugmentationNames.ArtificialBioNeuralNetwork],O(N);const I=new r.Augmentation({name:o.AugmentationNames.ArtificialSynapticPotentiation,repCost:2500,moneyCost:16e6,info:"The body is injected with a chemical that artificially induces synaptic potentiation, otherwise known as the strengthening of synapses. This results in a enhanced cognitive abilities.

    This augmentation:
    Increases the player's hacking speed by 2%
    Increases the player's hacking chance by 5%.
    Increases the player's hacking experience gain rate by 5%.",hacking_speed_mult:1.02,hacking_chance_mult:1.05,hacking_exp_mult:1.05});I.addToFactions(["The Black Hand","NiteSec"]),w(o.AugmentationNames.ArtificialSynapticPotentiation)&&delete a.Augmentations[o.AugmentationNames.ArtificialSynapticPotentiation],O(I);const D=new r.Augmentation({name:o.AugmentationNames.EnhancedMyelinSheathing,repCost:4e4,moneyCost:275e6,info:"Electrical signals are used to induce a new, artificial form of myelinogensis in the human body. This process results in the proliferation of new, synthetic myelin sheaths in the nervous system. These myelin sheaths can propogate neuro-signals much faster than their organic counterparts, leading to greater processing speeds and better brain function.

    This augmentation:
    Increases the player's hacking speed by 3%.
    Increases the player's hacking skill by 8%.
    Increases the player's hacking experience gain rate by 10%.",hacking_speed_mult:1.03,hacking_exp_mult:1.1,hacking_mult:1.08});D.addToFactions(["Fulcrum Secret Technologies","BitRunners","The Black Hand"]),w(o.AugmentationNames.EnhancedMyelinSheathing)&&delete a.Augmentations[o.AugmentationNames.EnhancedMyelinSheathing],O(D);const B=new r.Augmentation({name:o.AugmentationNames.SynapticEnhancement,repCost:800,moneyCost:15e5,info:"A small cranial implant that continuously uses weak electric signals to stimulate the brain and induce stronger synaptic activity. This improves the user's cognitive abilities.

    This augmentation increases the player's hacking speed by 3%.",hacking_speed_mult:1.03});B.addToFactions(["CyberSec"]),w(o.AugmentationNames.SynapticEnhancement)&&delete a.Augmentations[o.AugmentationNames.SynapticEnhancement],O(B);const L=new r.Augmentation({name:o.AugmentationNames.NeuralRetentionEnhancement,repCost:8e3,moneyCost:5e7,info:"Chemical injections are used to permanently alter and strengthen the brain's neuronal circuits, strengthening its ability to retain information.

    This augmentation increases the player's hacking experience gain rate by 25%.",hacking_exp_mult:1.25});L.addToFactions(["NiteSec"]),w(o.AugmentationNames.NeuralRetentionEnhancement)&&delete a.Augmentations[o.AugmentationNames.NeuralRetentionEnhancement],O(L);const W=new r.Augmentation({name:o.AugmentationNames.DataJack,repCost:45e3,moneyCost:9e7,info:"A brain implant that provides an interface for direct, wireless communication between a computer's main memory and the mind. This implant allows the user to not only access a computer's memory, but also alter and delete it.

    This augmentation increases the amount of money the player gains from hacking by 25%.",hacking_money_mult:1.25});W.addToFactions(["BitRunners","The Black Hand","NiteSec","Chongqing","New Tokyo"]),w(o.AugmentationNames.DataJack)&&delete a.Augmentations[o.AugmentationNames.DataJack],O(W);const j=new r.Augmentation({name:o.AugmentationNames.ENM,repCost:6e3,moneyCost:5e7,info:"A thin device embedded inside the arm containing a wireless module capable of connecting to nearby networks. Once connected, the Netburner Module is capable of capturing and processing all of the traffic on that network. By itself, the Embedded Netburner Module does not do much, but a variety of very powerful upgrades can be installed that allow you to fully control the traffic on a network.

    This augmentation increases the player's hacking skill by 8%.",hacking_mult:1.08});j.addToFactions(["BitRunners","The Black Hand","NiteSec","ECorp","MegaCorp","Fulcrum Secret Technologies","NWO","Blade Industries"]),w(o.AugmentationNames.ENM)&&delete a.Augmentations[o.AugmentationNames.ENM],O(j);const F=new r.Augmentation({name:o.AugmentationNames.ENMCore,repCost:1e5,moneyCost:5e8,info:"The Core library is an implant that upgrades the firmware of the Embedded Netburner Module. This upgrade allows the Embedded Netburner Module to generate its own data on a network.

    This augmentation:
    Increases the player's hacking speed by 3%.
    Increases the amount of money the player gains from hacking by 10%.
    Increases the player's chance of successfully performing a hack by 3%.
    Increases the player's hacking experience gain rate by 7%.
    Increases the player's hacking skill by 7%.",prereqs:[o.AugmentationNames.ENM],hacking_speed_mult:1.03,hacking_money_mult:1.1,hacking_chance_mult:1.03,hacking_exp_mult:1.07,hacking_mult:1.07});F.addToFactions(["BitRunners","The Black Hand","ECorp","MegaCorp","Fulcrum Secret Technologies","NWO","Blade Industries"]),w(o.AugmentationNames.ENMCore)&&delete a.Augmentations[o.AugmentationNames.ENMCore],O(F);const U=new r.Augmentation({name:o.AugmentationNames.ENMCoreV2,repCost:4e5,moneyCost:9e8,info:"The Core V2 library is an implant that upgrades the firmware of the Embedded Netburner Module. This upgraded firmware allows the Embedded Netburner Module to control the information on a network by re-routing traffic, spoofing IP addresses, or altering the data inside network packets.

    This augmentation:
    Increases the player's hacking speed by 5%.
    Increases the amount of money the player gains from hacking by 30%.
    Increases the player's chance of successfully performing a hack by 5%.
    Increases the player's hacking experience gain rate by 15%.
    Increases the player's hacking skill by 8%.",prereqs:[o.AugmentationNames.ENMCore],hacking_speed_mult:1.05,hacking_money_mult:1.3,hacking_chance_mult:1.05,hacking_exp_mult:1.15,hacking_mult:1.08});U.addToFactions(["BitRunners","ECorp","MegaCorp","Fulcrum Secret Technologies","NWO","Blade Industries","OmniTek Incorporated","KuaiGong International"]),w(o.AugmentationNames.ENMCoreV2)&&delete a.Augmentations[o.AugmentationNames.ENMCoreV2],O(U);const H=new r.Augmentation({name:o.AugmentationNames.ENMCoreV3,repCost:7e5,moneyCost:15e8,info:"The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into any device on a network.

    This augmentation:
    Increases the player's hacking speed by 5%.
    Increases the amount of money the player gains from hacking by 40%.
    Increases the player's chance of successfully performing a hack by 10%.
    Increases the player's hacking experience gain rate by 25%.
    Increases the player's hacking skill by 10%.",prereqs:[o.AugmentationNames.ENMCoreV2],hacking_speed_mult:1.05,hacking_money_mult:1.4,hacking_chance_mult:1.1,hacking_exp_mult:1.25,hacking_mult:1.1});H.addToFactions(["ECorp","MegaCorp","Fulcrum Secret Technologies","NWO","Daedalus","The Covenant","Illuminati"]),w(o.AugmentationNames.ENMCoreV3)&&delete a.Augmentations[o.AugmentationNames.ENMCoreV3],O(H);const G=new r.Augmentation({name:o.AugmentationNames.ENMAnalyzeEngine,repCost:25e4,moneyCost:12e8,info:"Installs the Analyze Engine for the Embedded Netburner Module, which is a CPU cluster that vastly outperforms the Netburner Module's native single-core processor.

    This augmentation increases the player's hacking speed by 10%.",prereqs:[o.AugmentationNames.ENM],hacking_speed_mult:1.1});G.addToFactions(["ECorp","MegaCorp","Fulcrum Secret Technologies","NWO","Daedalus","The Covenant","Illuminati"]),w(o.AugmentationNames.ENMAnalyzeEngine)&&delete a.Augmentations[o.AugmentationNames.ENMAnalyzeEngine],O(G);const K=new r.Augmentation({name:o.AugmentationNames.ENMDMA,repCost:4e5,moneyCost:14e8,info:"This implant installs a Direct Memory Access (DMA) controller into the Embedded Netburner Module. This allows the Module to send and receive data directly to and from the main memory of devices on a network.

    This augmentation:
    Increases the amount of money the player gains from hacking by 40%.
    Increases the player's chance of successfully performing a hack by 20%.",prereqs:[o.AugmentationNames.ENM],hacking_money_mult:1.4,hacking_chance_mult:1.2});K.addToFactions(["ECorp","MegaCorp","Fulcrum Secret Technologies","NWO","Daedalus","The Covenant","Illuminati"]),w(o.AugmentationNames.ENMDMA)&&delete a.Augmentations[o.AugmentationNames.ENMDMA],O(K);const q=new r.Augmentation({name:o.AugmentationNames.Neuralstimulator,repCost:2e4,moneyCost:6e8,info:"A cranial implant that intelligently stimulates certain areas of the brain in order to improve cognitive functions.

    This augmentation:
    Increases the player's hacking speed by 2%.
    Increases the player's chance of successfully performing a hack by 10%.
    Increases the player's hacking experience gain rate by 12%.",hacking_speed_mult:1.02,hacking_chance_mult:1.1,hacking_exp_mult:1.12});q.addToFactions(["The Black Hand","Chongqing","Sector-12","New Tokyo","Aevum","Ishima","Volhaven","Bachman & Associates","Clarke Incorporated","Four Sigma"]),w(o.AugmentationNames.Neuralstimulator)&&delete a.Augmentations[o.AugmentationNames.Neuralstimulator],O(q);const $=new r.Augmentation({name:o.AugmentationNames.NeuralAccelerator,repCost:8e4,moneyCost:35e7,info:"A microprocessor that accelerates the processing speed of biological neural networks. This is a cranial implant that is embedded inside the brain.

    This augmentation:
    Increases the player's hacking skill by 10%.
    Increases the player's hacking experience gain rate by 15%.
    Increases the amount of money the player gains from hacking by 20%.",hacking_mult:1.1,hacking_exp_mult:1.15,hacking_money_mult:1.2});$.addToFactions(["BitRunners"]),w(o.AugmentationNames.NeuralAccelerator)&&delete a.Augmentations[o.AugmentationNames.NeuralAccelerator],O($);const Y=new r.Augmentation({name:o.AugmentationNames.CranialSignalProcessorsG1,repCost:4e3,moneyCost:14e6,info:"The first generation of Cranial Signal Processors. Cranial Signal Processors are a set of specialized microprocessors that are attached to neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations so that the brain doesn't have to.

    This augmentation:
    Increases the player's hacking speed by 1%.
    Increases the player's hacking skill by 5%.",hacking_speed_mult:1.01,hacking_mult:1.05});Y.addToFactions(["CyberSec"]),w(o.AugmentationNames.CranialSignalProcessorsG1)&&delete a.Augmentations[o.AugmentationNames.CranialSignalProcessorsG1],O(Y);const z=new r.Augmentation({name:o.AugmentationNames.CranialSignalProcessorsG2,repCost:7500,moneyCost:25e6,info:"The second generation of Cranial Signal Processors. Cranial Signal Processors are a set of specialized microprocessors that are attached to neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations so that the brain doesn't have to.

    This augmentation:
    Increases the player's hacking speed by 2%.
    Increases the player's chance of successfully performing a hack by 5%.
    Increases the player's hacking skill by 7%.",prereqs:[o.AugmentationNames.CranialSignalProcessorsG1],hacking_speed_mult:1.02,hacking_chance_mult:1.05,hacking_mult:1.07});z.addToFactions(["CyberSec","NiteSec"]),w(o.AugmentationNames.CranialSignalProcessorsG2)&&delete a.Augmentations[o.AugmentationNames.CranialSignalProcessorsG2],O(z);const V=new r.Augmentation({name:o.AugmentationNames.CranialSignalProcessorsG3,repCost:2e4,moneyCost:11e7,info:"The third generation of Cranial Signal Processors. Cranial Signal Processors are a set of specialized microprocessors that are attached to neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations so that the brain doesn't have to.

    This augmentation:
    Increases the player's hacking speed by 2%.
    Increases the amount of money the player gains from hacking by 15%.
    Increases the player's hacking skill by 9%.",prereqs:[o.AugmentationNames.CranialSignalProcessorsG2],hacking_speed_mult:1.02,hacking_money_mult:1.15,hacking_mult:1.09});V.addToFactions(["NiteSec","The Black Hand","BitRunners"]),w(o.AugmentationNames.CranialSignalProcessorsG3)&&delete a.Augmentations[o.AugmentationNames.CranialSignalProcessorsG3],O(V);const J=new r.Augmentation({name:o.AugmentationNames.CranialSignalProcessorsG4,repCost:5e4,moneyCost:22e7,info:"The fourth generation of Cranial Signal Processors. Cranial Signal Processors are a set of specialized microprocessors that are attached to neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations so that the brain doesn't have to.

    This augmentation:
    Increases the player's hacking speed by 2%.
    Increases the amount of money the player gains from hacking by 20%.
    Increases the amount of money the player can inject into servers using grow() by 25%.",prereqs:[o.AugmentationNames.CranialSignalProcessorsG3],hacking_speed_mult:1.02,hacking_money_mult:1.2,hacking_grow_mult:1.25});J.addToFactions(["The Black Hand","BitRunners"]),w(o.AugmentationNames.CranialSignalProcessorsG4)&&delete a.Augmentations[o.AugmentationNames.CranialSignalProcessorsG4],O(J);const X=new r.Augmentation({name:o.AugmentationNames.CranialSignalProcessorsG5,repCost:1e5,moneyCost:45e7,info:"The fifth generation of Cranial Signal Processors. Cranial Signal Processors are a set of specialized microprocessors that are attached to neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations so that the brain doesn't have to.

    This augmentation:
    Increases the player's hacking skill by 30%.
    Increases the amount of money the player gains from hacking by 25%.
    Increases the amount of money the player can inject into servers using grow() by 75%.",prereqs:[o.AugmentationNames.CranialSignalProcessorsG4],hacking_mult:1.3,hacking_money_mult:1.25,hacking_grow_mult:1.75});X.addToFactions(["BitRunners"]),w(o.AugmentationNames.CranialSignalProcessorsG5)&&delete a.Augmentations[o.AugmentationNames.CranialSignalProcessorsG5],O(X);const Q=new r.Augmentation({name:o.AugmentationNames.NeuronalDensification,repCost:75e3,moneyCost:275e6,info:"The brain is surgically re-engineered to have increased neuronal density by decreasing the neuron gap junction. Then, the body is genetically modified to enhance the production and capabilities of its neural stem cells.

    This augmentation:
    Increases the player's hacking skill by 15%.
    Increases the player's hacking experience gain rate by 10%.
    Increases the player's hacking speed by 3%.",hacking_mult:1.15,hacking_exp_mult:1.1,hacking_speed_mult:1.03});Q.addToFactions(["Clarke Incorporated"]),w(o.AugmentationNames.NeuronalDensification)&&delete a.Augmentations[o.AugmentationNames.NeuronalDensification],O(Q);const Z=new r.Augmentation({name:o.AugmentationNames.NuoptimalInjectorImplant,repCost:2e3,moneyCost:4e6,info:"This torso implant automatically injects nootropic supplements into the bloodstream to improve memory, increase focus, and provide other cognitive enhancements.

    This augmentation increases the amount of reputation the player gains when working for a company by 20%.",company_rep_mult:1.2});Z.addToFactions(["Tian Di Hui","Volhaven","New Tokyo","Chongqing","Ishima","Clarke Incorporated","Four Sigma","Bachman & Associates"]),w(o.AugmentationNames.NuoptimalInjectorImplant)&&delete a.Augmentations[o.AugmentationNames.NuoptimalInjectorImplant],O(Z);const ee=new r.Augmentation({name:o.AugmentationNames.SpeechEnhancement,repCost:1e3,moneyCost:25e5,info:"An advanced neural implant that improves your speaking abilities, making you more convincing and likable in conversations and overall improving your social interactions.

    This augmentation:
    Increases the player's charisma by 10%.
    Increases the amount of reputation the player gains when working for a company by 10%.",company_rep_mult:1.1,charisma_mult:1.1});ee.addToFactions(["Tian Di Hui","Speakers for the Dead","Four Sigma","KuaiGong International","Clarke Incorporated","Bachman & Associates"]),w(o.AugmentationNames.SpeechEnhancement)&&delete a.Augmentations[o.AugmentationNames.SpeechEnhancement],O(ee);const te=new r.Augmentation({name:o.AugmentationNames.FocusWire,repCost:3e4,moneyCost:18e7,info:"A cranial implant that stops procrastination by blocking specific neural pathways in the brain.

    This augmentation:
    Increases all experience gains by 5%.
    Increases the amount of money the player gains from working by 20%.
    Increases the amount of reputation the player gains when working for a company by 10%.",hacking_exp_mult:1.05,strength_exp_mult:1.05,defense_exp_mult:1.05,dexterity_exp_mult:1.05,agility_exp_mult:1.05,charisma_exp_mult:1.05,company_rep_mult:1.1,work_money_mult:1.2});te.addToFactions(["Bachman & Associates","Clarke Incorporated","Four Sigma","KuaiGong International"]),w(o.AugmentationNames.FocusWire)&&delete a.Augmentations[o.AugmentationNames.FocusWire],O(te);const ne=new r.Augmentation({name:o.AugmentationNames.PCDNI,repCost:15e4,moneyCost:75e7,info:"Installs a Direct-Neural Interface jack into your arm that is compatible with most computers. Connecting to a computer through this jack allows you to interface with it using the brain's electrochemical signals.

    This augmentation:
    Increases the amount of reputation the player gains when working for a company by 30%.
    Increases the player's hacking skill by 8%.",company_rep_mult:1.3,hacking_mult:1.08});ne.addToFactions(["Four Sigma","OmniTek Incorporated","ECorp","Blade Industries"]),w(o.AugmentationNames.PCDNI)&&delete a.Augmentations[o.AugmentationNames.PCDNI],O(ne);const re=new r.Augmentation({name:o.AugmentationNames.PCDNIOptimizer,repCost:2e5,moneyCost:9e8,info:"This is a submodule upgrade to the PC Direct-Neural Interface augmentation. It improves the performance of the interface and gives the user more control options to the connected computer.

    This augmentation:
    Increases the amount of reputation the player gains when working for a company by 75%.
    Increases the player's hacking skill by 10%.",prereqs:[o.AugmentationNames.PCDNI],company_rep_mult:1.75,hacking_mult:1.1});re.addToFactions(["Fulcrum Secret Technologies","ECorp","Blade Industries"]),w(o.AugmentationNames.PCDNIOptimizer)&&delete a.Augmentations[o.AugmentationNames.PCDNIOptimizer],O(re);const ae=new r.Augmentation({name:o.AugmentationNames.PCDNINeuralNetwork,repCost:6e5,moneyCost:15e8,info:"This is an additional installation that upgrades the functionality of the PC Direct-Neural Interface augmentation. When connected to a computer, The NeuroNet Injector upgrade allows the user to use his/her own brain's processing power to aid the computer in computational tasks.

    This augmentation:
    Increases the amount of reputation the player gains when working for a company by 100%.
    Increases the player's hacking skill by 10%.
    Increases the player's hacking speed by 5%.",prereqs:[o.AugmentationNames.PCDNI],company_rep_mult:2,hacking_mult:1.1,hacking_speed_mult:1.05});ae.addToFactions(["Fulcrum Secret Technologies"]),w(o.AugmentationNames.PCDNINeuralNetwork)&&delete a.Augmentations[o.AugmentationNames.PCDNINeuralNetwork],O(ae);const ie=new r.Augmentation({name:o.AugmentationNames.ADRPheromone1,repCost:1500,moneyCost:35e5,info:"The body is genetically re-engineered so that it produces the ADR-V1 pheromone, an artificial pheromone discovered by scientists. The ADR-V1 pheromone, when excreted, triggers feelings of admiration and approval in other people.

    This augmentation:
    Increases the amount of reputation the player gains when working for a company by 10%
    Increases the amount of reputation the player gains for a faction by 10%.",company_rep_mult:1.1,faction_rep_mult:1.1});ie.addToFactions(["Tian Di Hui","The Syndicate","NWO","MegaCorp","Four Sigma"]),w(o.AugmentationNames.ADRPheromone1)&&delete a.Augmentations[o.AugmentationNames.ADRPheromone1],O(ie);const oe=new r.Augmentation({name:o.AugmentationNames.ADRPheromone2,repCost:25e3,moneyCost:11e7,info:"The body is genetically re-engineered so that it produces the ADR-V2 pheromone, which is similar to but more potent than ADR-V1. This pheromone, when excreted, triggers feelings of admiration, approval, and respect in others.

    This augmentation:
    Increases the amount of reputation the player gains for a faction and company by 20%.",company_rep_mult:1.2,faction_rep_mult:1.2});oe.addToFactions(["Silhouette","Four Sigma","Bachman & Associates","Clarke Incorporated"]),w(o.AugmentationNames.ADRPheromone2)&&delete a.Augmentations[o.AugmentationNames.ADRPheromone2],O(oe);const se=new r.Augmentation({name:o.AugmentationNames.ShadowsSimulacrum,repCost:15e3,moneyCost:8e7,info:"A crude but functional matter phase-shifter module that is embedded in the brainstem and cerebellum. This augmentation was developed by criminal organizations and allows the user to project and control holographic simulacrums within a large radius. These simulacrums are commonly used for espionage and surveillance work.

    This augmentation:
    Increases the amount of reputation the player gains when working for a faction or company by 15%.",company_rep_mult:1.15,faction_rep_mult:1.15});se.addToFactions(["The Syndicate","The Dark Army","Speakers for the Dead"]),w(o.AugmentationNames.ShadowsSimulacrum)&&delete a.Augmentations[o.AugmentationNames.ShadowsSimulacrum],O(se);const le=new r.Augmentation({name:o.AugmentationNames.HacknetNodeCPUUpload,repCost:1500,moneyCost:22e5,info:"Uploads the architecture and design details of a Hacknet Node's CPU into the brain. This allows the user to engineer custom hardware and software for the Hacknet Node that provides better performance.

    This augmentation:
    Increases the amount of money produced by Hacknet Nodes by 15%.
    Decreases the cost of purchasing a Hacknet Node by 15%.",hacknet_node_money_mult:1.15,hacknet_node_purchase_cost_mult:.85});le.addToFactions(["Netburners"]),w(o.AugmentationNames.HacknetNodeCPUUpload)&&delete a.Augmentations[o.AugmentationNames.HacknetNodeCPUUpload],O(le);const ce=new r.Augmentation({name:o.AugmentationNames.HacknetNodeCacheUpload,repCost:1e3,moneyCost:11e5,info:"Uploads the architecture and design details of a Hacknet Node's main-memory cache into the brain. This allows the user to engineer custom cache hardware for the Hacknet Node that offers better performance.

    This augmentation:
    Increases the amount of money produced by Hacknet Nodes by 10%.
    Decreases the cost of leveling up a Hacknet Node by 15%.",hacknet_node_money_mult:1.1,hacknet_node_level_cost_mult:.85});ce.addToFactions(["Netburners"]),w(o.AugmentationNames.HacknetNodeCacheUpload)&&delete a.Augmentations[o.AugmentationNames.HacknetNodeCacheUpload],O(ce);const ue=new r.Augmentation({name:o.AugmentationNames.HacknetNodeNICUpload,repCost:750,moneyCost:9e5,info:"Uploads the architecture and design details of a Hacknet Node's Network Interface Card (NIC) into the brain. This allows the user to engineer a custom NIC for the Hacknet Node that offers better performance.

    This augmentation:
    Increases the amount of money produced by Hacknet Nodes by 10%.
    Decreases the cost of purchasing a Hacknet Node by 10%.",hacknet_node_money_mult:1.1,hacknet_node_purchase_cost_mult:.9});ue.addToFactions(["Netburners"]),w(o.AugmentationNames.HacknetNodeNICUpload)&&delete a.Augmentations[o.AugmentationNames.HacknetNodeNICUpload],O(ue);const pe=new r.Augmentation({name:o.AugmentationNames.HacknetNodeKernelDNI,repCost:3e3,moneyCost:8e6,info:"Installs a Direct-Neural Interface jack into the arm that is capable of connecting to a Hacknet Node. This lets the user access and manipulate the Node's kernel using the mind's electrochemical signals.

    This augmentation increases the amount of money produced by Hacknet Nodes by 25%.",hacknet_node_money_mult:1.25});pe.addToFactions(["Netburners"]),w(o.AugmentationNames.HacknetNodeKernelDNI)&&delete a.Augmentations[o.AugmentationNames.HacknetNodeKernelDNI],O(pe);const he=new r.Augmentation({name:o.AugmentationNames.HacknetNodeCoreDNI,repCost:5e3,moneyCost:12e6,info:"Installs a Direct-Neural Interface jack into the arm that is capable of connecting to a Hacknet Node. This lets the user access and manipulate the Node's processing logic using the mind's electrochemical signals.

    This augmentation increases the amount of money produced by Hacknet Nodes by 45%.",hacknet_node_money_mult:1.45});he.addToFactions(["Netburners"]),w(o.AugmentationNames.HacknetNodeCoreDNI)&&delete a.Augmentations[o.AugmentationNames.HacknetNodeCoreDNI],O(he);const me=new r.Augmentation({name:o.AugmentationNames.NeuroFluxGovernor,repCost:500,moneyCost:75e4,info:"A device that is embedded in the back of the neck. The NeuroFlux Governor monitors and regulates nervous impulses coming to and from the spinal column, essentially 'governing' the body. By doing so, it improves the functionality of the body's nervous system.

    This is a special augmentation because it can be leveled up infinitely. Each level of this augmentation increases ALL of the player's multipliers by 1%.",hacking_chance_mult:1.01,hacking_speed_mult:1.01,hacking_money_mult:1.01,hacking_grow_mult:1.01,hacking_mult:1.01,strength_mult:1.01,defense_mult:1.01,dexterity_mult:1.01,agility_mult:1.01,charisma_mult:1.01,hacking_exp_mult:1.01,strength_exp_mult:1.01,defense_exp_mult:1.01,dexterity_exp_mult:1.01,agility_exp_mult:1.01,charisma_exp_mult:1.01,company_rep_mult:1.01,faction_rep_mult:1.01,crime_money_mult:1.01,crime_success_mult:1.01,hacknet_node_money_mult:1.01,hacknet_node_purchase_cost_mult:.99,hacknet_node_ram_cost_mult:.99,hacknet_node_core_cost_mult:.99,hacknet_node_level_cost_mult:.99,work_money_mult:1.01});let de=0;for(let e=0;e
    This augmentation increases the player's experience gain rate for all stats by 10%.",hacking_exp_mult:1.1,strength_exp_mult:1.1,defense_exp_mult:1.1,dexterity_exp_mult:1.1,agility_exp_mult:1.1,charisma_exp_mult:1.1});ye.addToFactions(["CyberSec"]),w(o.AugmentationNames.Neurotrainer1)&&delete a.Augmentations[o.AugmentationNames.Neurotrainer1],O(ye);const fe=new r.Augmentation({name:o.AugmentationNames.Neurotrainer2,repCost:4e3,moneyCost:9e6,info:"A decentralized cranial implant that improves the brain's ability to learn. This is a more powerful version of the Neurotrainer I augmentation, but it does not require Neurotrainer I to be installed as a prerequisite.

    This augmentation increases the player's experience gain rate for all stats by 15%.",hacking_exp_mult:1.15,strength_exp_mult:1.15,defense_exp_mult:1.15,dexterity_exp_mult:1.15,agility_exp_mult:1.15,charisma_exp_mult:1.15});fe.addToFactions(["BitRunners","NiteSec"]),w(o.AugmentationNames.Neurotrainer2)&&delete a.Augmentations[o.AugmentationNames.Neurotrainer2],O(fe);const be=new r.Augmentation({name:o.AugmentationNames.Neurotrainer3,repCost:1e4,moneyCost:26e6,info:"A decentralized cranial implant that improves the brain's ability to learn. This is a more powerful version of the Neurotrainer I and Neurotrainer II augmentation, but it does not require either of them to be installed as a prerequisite.

    This augmentation increases the player's experience gain rate for all stats by 20%.",hacking_exp_mult:1.2,strength_exp_mult:1.2,defense_exp_mult:1.2,dexterity_exp_mult:1.2,agility_exp_mult:1.2,charisma_exp_mult:1.2});be.addToFactions(["NWO","Four Sigma"]),w(o.AugmentationNames.Neurotrainer3)&&delete a.Augmentations[o.AugmentationNames.Neurotrainer3],O(be);const ve=new r.Augmentation({name:o.AugmentationNames.Hypersight,repCost:6e4,moneyCost:55e7,info:"A bionic eye implant that grants sight capabilities far beyond those of a natural human. Embedded circuitry within the implant provides the ability to detect heat and movement through solid objects such as wells, thus providing 'x-ray vision'-like capabilities.

    This augmentation:
    Increases the player's dexterity by 40%.
    Increases the player's hacking speed by 3%.
    Increases the amount of money the player gains from hacking by 10%.",dexterity_mult:1.4,hacking_speed_mult:1.03,hacking_money_mult:1.1});ve.addToFactions(["Blade Industries","KuaiGong International"]),w(o.AugmentationNames.Hypersight)&&delete a.Augmentations[o.AugmentationNames.Hypersight],O(ve);const Ee=new r.Augmentation({name:o.AugmentationNames.LuminCloaking1,repCost:600,moneyCost:1e6,info:"A skin implant that reinforces the skin with highly-advanced synthetic cells. These cells, when powered, have a negative refractive index. As a result, they bend light around the skin, making the user much harder to see from the naked eye.

    This augmentation:
    Increases the player's agility by 5%
    Increases the amount of money the player gains from crimes by 10%.",agility_mult:1.05,crime_money_mult:1.1});Ee.addToFactions(["Slum Snakes","Tetrads"]),w(o.AugmentationNames.LuminCloaking1)&&delete a.Augmentations[o.AugmentationNames.LuminCloaking1],O(Ee);const ke=new r.Augmentation({name:o.AugmentationNames.LuminCloaking2,repCost:2e3,moneyCost:6e6,info:"This is a more advanced version of the LuminCloaking-V2 augmentation. This skin implant reinforces the skin with highly-advanced synthetic cells. These cells, when powered, are capable of not only bending light but also of bending heat, making the user more resilient as well as stealthy.

    This augmentation:
    Increases the player's agility by 10%
    Increases the player's defense by 10%
    Increases the amount of money the player gains from crimes by 25%.",prereqs:[o.AugmentationNames.LuminCloaking1],agility_mult:1.1,defense_mult:1.1,crime_money_mult:1.25});ke.addToFactions(["Slum Snakes","Tetrads"]),w(o.AugmentationNames.LuminCloaking2)&&delete a.Augmentations[o.AugmentationNames.LuminCloaking2],O(ke);const Ce=new r.Augmentation({name:o.AugmentationNames.SmartSonar,repCost:9e3,moneyCost:15e6,info:"A cochlear implant that helps the player detect and locate enemies using sound propagation.

    This augmentation:
    Increases the player's dexterity by 10%.
    Increases the player's dexterity experience gain rate by 15%.
    Increases the amount of money the player gains from crimes by 25%.",dexterity_mult:1.1,dexterity_exp_mult:1.15,crime_money_mult:1.25});Ce.addToFactions(["Slum Snakes"]),w(o.AugmentationNames.SmartSonar)&&delete a.Augmentations[o.AugmentationNames.SmartSonar],O(Ce);const Pe=new r.Augmentation({name:o.AugmentationNames.PowerRecirculator,repCost:1e4,moneyCost:36e6,info:"The body's nerves are attached with polypyrrole nanocircuits that are capable of capturing wasted energy (in the form of heat) and converting it back into usable power.

    This augmentation:
    Increases all of the player's stats by 5%.
    Increases the player's experience gain rate for all stats by 10%.",hacking_mult:1.05,strength_mult:1.05,defense_mult:1.05,dexterity_mult:1.05,agility_mult:1.05,charisma_mult:1.05,hacking_exp_mult:1.1,strength_exp_mult:1.1,defense_exp_mult:1.1,dexterity_exp_mult:1.1,agility_exp_mult:1.1,charisma_exp_mult:1.1});Pe.addToFactions(["Tetrads","The Dark Army","The Syndicate","NWO"]),w(o.AugmentationNames.PowerRecirculator)&&delete a.Augmentations[o.AugmentationNames.PowerRecirculator],O(Pe);const Oe=new r.Augmentation({name:o.AugmentationNames.QLink,repCost:75e4,moneyCost:5e12,info:"A brain implant that wirelessly connects you to the Illuminati's quantum supercomputer, allowing you to access and use its incredible computing power.

    This augmentation:
    Increases the player's hacking skill by 75%.
    Increases the player's hacking speed by 100%.
    Increases the player's chance of successfully performing a hack by 150%.
    Increases the amount of money the player gains from hacking by 300%.",hacking_mult:1.75,hacking_speed_mult:2,hacking_chance_mult:2.5,hacking_money_mult:4});Oe.addToFactions(["Illuminati"]),w(o.AugmentationNames.QLink)&&delete a.Augmentations[o.AugmentationNames.QLink],O(Oe);const Te=new r.Augmentation({name:o.AugmentationNames.TheRedPill,repCost:1e6,moneyCost:0,info:"It's time to leave the cave."});Te.addToFactions(["Daedalus"]),w(o.AugmentationNames.TheRedPill)&&delete a.Augmentations[o.AugmentationNames.TheRedPill],O(Te);const Se=new r.Augmentation({name:o.AugmentationNames.SPTN97,repCost:5e5,moneyCost:975e6,info:"The SPTN-97 gene is injected into the genome. The SPTN-97 gene is an artificially-synthesized gene that was developed by DARPA to create super-soldiers through genetic modification. The gene was outlawed in 2056.

    This augmentation:
    Increases all of the player's combat stats by 75%.
    Increases the player's hacking skill by 15%.",strength_mult:1.75,defense_mult:1.75,dexterity_mult:1.75,agility_mult:1.75,hacking_mult:1.15});Se.addToFactions(["The Covenant"]),w(o.AugmentationNames.SPTN97)&&delete a.Augmentations[o.AugmentationNames.SPTN97],O(Se);const Me=new r.Augmentation({name:o.AugmentationNames.HiveMind,repCost:6e5,moneyCost:11e8,info:"A brain implant developed by ECorp. They do not reveal what exactly the implant does, but they promise that it will greatly enhance your abilities.",hacking_grow_mult:3});Me.addToFactions(["ECorp"]),w(o.AugmentationNames.HiveMind)&&delete a.Augmentations[o.AugmentationNames.HiveMind],O(Me);const xe=new r.Augmentation({name:o.AugmentationNames.CordiARCReactor,repCost:45e4,moneyCost:1e9,info:"The thoracic cavity is equipped with a small chamber designed to hold and sustain hydrogen plasma. The plasma is used to generate fusion power through nuclear fusion, providing limitless amount of clean energy for the body.

    This augmentation:
    Increases all of the player's combat stats by 35%.
    Increases all of the player's combat stat experience gain rate by 35%.",strength_mult:1.35,defense_mult:1.35,dexterity_mult:1.35,agility_mult:1.35,strength_exp_mult:1.35,defense_exp_mult:1.35,dexterity_exp_mult:1.35,agility_exp_mult:1.35});xe.addToFactions(["MegaCorp"]),w(o.AugmentationNames.CordiARCReactor)&&delete a.Augmentations[o.AugmentationNames.CordiARCReactor],O(xe);const we=new r.Augmentation({name:o.AugmentationNames.SmartJaw,repCost:15e4,moneyCost:55e7,info:"A bionic jaw that contains advanced hardware and software capable of psychoanalyzing and profiling the personality of others using optical imaging software.

    This augmentation:
    Increases the player's charisma by 50%.
    Increases the player's charisma experience gain rate by 50%.
    Increases the amount of reputation the player gains for a company by 25%.
    Increases the amount of reputation the player gains for a faction by 25%.",charisma_mult:1.5,charisma_exp_mult:1.5,company_rep_mult:1.25,faction_rep_mult:1.25});we.addToFactions(["Bachman & Associates"]),w(o.AugmentationNames.SmartJaw)&&delete a.Augmentations[o.AugmentationNames.SmartJaw],O(we);const Ae=new r.Augmentation({name:o.AugmentationNames.Neotra,repCost:225e3,moneyCost:575e6,info:"A highly-advanced techno-organic drug that is injected into the skeletal and integumentary system. The drug permanently modifies the DNA of the body's skin and bone cells, granting them the ability to repair and restructure themselves.

    This augmentation increases the player's strength and defense by 55%.",strength_mult:1.55,defense_mult:1.55});Ae.addToFactions(["Blade Industries"]),w(o.AugmentationNames.Neotra)&&delete a.Augmentations[o.AugmentationNames.Neotra],O(Ae);const Re=new r.Augmentation({name:o.AugmentationNames.Xanipher,repCost:35e4,moneyCost:85e7,info:"A concoction of advanced nanobots that is orally ingested into the body. These nanobots induce physiological change and significantly improve the body's functionining in all aspects.

    This augmentation:
    Increases all of the player's stats by 20%.
    Increases the player's experience gain rate for all stats by 15%.",hacking_mult:1.2,strength_mult:1.2,defense_mult:1.2,dexterity_mult:1.2,agility_mult:1.2,charisma_mult:1.2,hacking_exp_mult:1.15,strength_exp_mult:1.15,defense_exp_mult:1.15,dexterity_exp_mult:1.15,agility_exp_mult:1.15,charisma_exp_mult:1.15});Re.addToFactions(["NWO"]),w(o.AugmentationNames.Xanipher)&&delete a.Augmentations[o.AugmentationNames.Xanipher],O(Re);const Ne=new r.Augmentation({name:o.AugmentationNames.nextSENS,repCost:175e3,moneyCost:385e6,info:"The body is genetically re-engineered to maintain a state of negligible senescence, preventing the body from deteriorating with age.

    This augmentation increases all of the player's stats by 20%.",hacking_mult:1.2,strength_mult:1.2,defense_mult:1.2,dexterity_mult:1.2,agility_mult:1.2,charisma_mult:1.2});Ne.addToFactions(["Clarke Incorporated"]),w(o.AugmentationNames.nextSENS)&&delete a.Augmentations[o.AugmentationNames.nextSENS],O(Ne);const Ie=new r.Augmentation({name:o.AugmentationNames.OmniTekInfoLoad,repCost:25e4,moneyCost:575e6,info:"OmniTek's data and information repository is uploaded into your brain, enhancing your programming and hacking abilities.

    This augmentation:
    Increases the player's hacking skill by 20%.
    Increases the player's hacking experience gain rate by 25%.",hacking_mult:1.2,hacking_exp_mult:1.25});Ie.addToFactions(["OmniTek Incorporated"]),w(o.AugmentationNames.OmniTekInfoLoad)&&delete a.Augmentations[o.AugmentationNames.OmniTekInfoLoad],O(Ie);const De=new r.Augmentation({name:o.AugmentationNames.PhotosyntheticCells,repCost:225e3,moneyCost:55e7,info:"Chloroplasts are added to epidermal stem cells and are applied to the body using a skin graft. The result is photosynthetic skin cells, allowing users to generate their own energy and nutrition using solar power.

    This augmentation increases the player's strength, defense, and agility by 40%.",strength_mult:1.4,defense_mult:1.4,agility_mult:1.4});De.addToFactions(["KuaiGong International"]),w(o.AugmentationNames.PhotosyntheticCells)&&delete a.Augmentations[o.AugmentationNames.PhotosyntheticCells],O(De);const Be=new r.Augmentation({name:o.AugmentationNames.Neurolink,repCost:35e4,moneyCost:875e6,info:"A brain implant that provides a high-bandwidth, direct neural link between your mind and BitRunners' data servers, which reportedly contain the largest database of hacking tools and information in the world.

    This augmentation:
    Increases the player's hacking skill by 15%.
    Increases the player's hacking experience gain rate by 20%.
    Increases the player's chance of successfully performing a hack by 10%.
    Increases the player's hacking speed by 5%.
    Lets the player start with the FTPCrack.exe and relaySMTP.exe programs after a reset.",hacking_mult:1.15,hacking_exp_mult:1.2,hacking_chance_mult:1.1,hacking_speed_mult:1.05});Be.addToFactions(["BitRunners"]),w(o.AugmentationNames.Neurolink)&&delete a.Augmentations[o.AugmentationNames.Neurolink],O(Be);const Le=new r.Augmentation({name:o.AugmentationNames.TheBlackHand,repCost:4e4,moneyCost:11e7,info:"A highly advanced bionic hand. This prosthetic not only enhances strength and dexterity but it is also embedded with hardware and firmware that lets the user connect to, access and hack devices and machines just by touching them.

    This augmentation:
    Increases the player's strength and dexterity by 15%.
    Increases the player's hacking skill by 10%.
    Increases the player's hacking speed by 2%.
    Increases the amount of money the player gains from hacking by 10%.",strength_mult:1.15,dexterity_mult:1.15,hacking_mult:1.1,hacking_speed_mult:1.02,hacking_money_mult:1.1});Le.addToFactions(["The Black Hand"]),w(o.AugmentationNames.TheBlackHand)&&delete a.Augmentations[o.AugmentationNames.TheBlackHand],O(Le);const We=new r.Augmentation({name:o.AugmentationNames.CRTX42AA,repCost:18e3,moneyCost:45e6,info:"The CRTX42-AA gene is injected into the genome. The CRTX42-AA is an artificially-synthesized gene that targets the visual and prefrontal cortex and improves cognitive abilities.

    This augmentation:
    Improves the player's hacking skill by 8%.
    Improves the player's hacking experience gain rate by 15%.",hacking_mult:1.08,hacking_exp_mult:1.15});We.addToFactions(["NiteSec"]),w(o.AugmentationNames.CRTX42AA)&&delete a.Augmentations[o.AugmentationNames.CRTX42AA],O(We);const je=new r.Augmentation({name:o.AugmentationNames.Neuregen,repCost:15e3,moneyCost:75e6,info:"A drug that genetically modifies the neurons in the brain. The result is that these neurons never die and continuously regenerate and strengthen themselves.

    This augmentation increases the player's hacking experience gain rate by 40%.",hacking_exp_mult:1.4});je.addToFactions(["Chongqing"]),w(o.AugmentationNames.Neuregen)&&delete a.Augmentations[o.AugmentationNames.Neuregen],O(je);const Fe=new r.Augmentation({name:o.AugmentationNames.CashRoot,repCost:5e3,moneyCost:25e6,info:"A collection of digital assets saved on a small chip. The chip is implanted into your wrist. A small jack in the chip allows you to connect it to a computer and upload the assets.

    This augmentation:
    Lets the player start with $1,000,000 after a reset.
    Lets the player start with the BruteSSH.exe program after a reset."});Fe.addToFactions(["Sector-12"]),w(o.AugmentationNames.CashRoot)&&delete a.Augmentations[o.AugmentationNames.CashRoot],O(Fe);const Ue=new r.Augmentation({name:o.AugmentationNames.NutriGen,repCost:2500,moneyCost:5e5,info:"A thermo-powered artificial nutrition generator. Endogenously synthesizes glucose, amino acids, and vitamins and redistributes them across the body. The device is powered by the body's naturally wasted energy in the form of heat.

    This augmentation:
    Increases the player's experience gain rate for all combat stats by 20%.",strength_exp_mult:1.2,defense_exp_mult:1.2,dexterity_exp_mult:1.2,agility_exp_mult:1.2});Ue.addToFactions(["New Tokyo"]),w(o.AugmentationNames.NutriGen)&&delete a.Augmentations[o.AugmentationNames.NutriGen],O(Ue);const He=new r.Augmentation({name:o.AugmentationNames.INFRARet,repCost:3e3,moneyCost:6e6,info:"A retina implant consisting of a tiny chip that sits behind the retina. This implant lets people visually detect infrared radiation.

    This augmentation:
    Increases the player's crime success rate by 25%.
    Increases the amount of money the player gains from crimes by 10%.
    Increases the player's dexterity by 10%.",crime_success_mult:1.25,crime_money_mult:1.1,dexterity_mult:1.1});He.addToFactions(["Ishima"]),w(o.AugmentationNames.INFRARet)&&delete a.Augmentations[o.AugmentationNames.INFRARet],O(He);const Ge=new r.Augmentation({name:o.AugmentationNames.DermaForce,repCost:6e3,moneyCost:1e7,info:"A synthetic skin is grafted onto the body. The skin consists of millions of nanobots capable of projecting high-density muon beams, creating an energy barrier around the user.

    This augmentation increases the player's defense by 40%.",defense_mult:1.4});Ge.addToFactions(["Volhaven"]),w(o.AugmentationNames.DermaForce)&&delete a.Augmentations[o.AugmentationNames.DermaForce],O(Ge);const Ke=new r.Augmentation({name:o.AugmentationNames.GrapheneBrachiBlades,repCost:9e4,moneyCost:5e8,info:"An upgrade to the BrachiBlades augmentation. It infuses the retractable blades with an advanced graphene material to make them much stronger and lighter.

    This augmentation:
    Increases the player's strength and defense by 40%.
    Increases the player's crime success rate by 10%.
    Increases the amount of money the player gains from crimes by 30%.",prereqs:[o.AugmentationNames.BrachiBlades],strength_mult:1.4,defense_mult:1.4,crime_success_mult:1.1,crime_money_mult:1.3});Ke.addToFactions(["Speakers for the Dead"]),w(o.AugmentationNames.GrapheneBrachiBlades)&&delete a.Augmentations[o.AugmentationNames.GrapheneBrachiBlades],O(Ke);const qe=new r.Augmentation({name:o.AugmentationNames.GrapheneBionicArms,repCost:2e5,moneyCost:75e7,info:"An upgrade to the Bionic Arms augmentation. It infuses the prosthetic arms with an advanced graphene material to make them much stronger and lighter.

    This augmentation increases the player's strength and dexterity by 85%.",prereqs:[o.AugmentationNames.BionicArms],strength_mult:1.85,dexterity_mult:1.85});qe.addToFactions(["The Dark Army"]),w(o.AugmentationNames.GrapheneBionicArms)&&delete a.Augmentations[o.AugmentationNames.GrapheneBionicArms],O(qe);const $e=new r.Augmentation({name:o.AugmentationNames.BrachiBlades,repCost:5e3,moneyCost:18e6,info:"A set of retractable plasteel blades are implanted in the arm, underneath the skin.

    This augmentation:
    Increases the player's strength and defense by 15%.
    Increases the player's crime success rate by 10%.
    Increases the amount of money the player gains from crimes by 15%.",strength_mult:1.15,defense_mult:1.15,crime_success_mult:1.1,crime_money_mult:1.15});$e.addToFactions(["The Syndicate"]),w(o.AugmentationNames.BrachiBlades)&&delete a.Augmentations[o.AugmentationNames.BrachiBlades],O($e);const Ye=new r.Augmentation({name:o.AugmentationNames.BionicArms,repCost:25e3,moneyCost:55e6,info:"Cybernetic arms created from plasteel and carbon fibers that completely replace the user's organic arms.

    This augmentation increases the user's strength and dexterity by 30%.",strength_mult:1.3,dexterity_mult:1.3});Ye.addToFactions(["Tetrads"]),w(o.AugmentationNames.BionicArms)&&delete a.Augmentations[o.AugmentationNames.BionicArms],O(Ye);const ze=new r.Augmentation({name:o.AugmentationNames.SNA,repCost:2500,moneyCost:6e6,info:"A cranial implant that affects the user's personality, making them better at negotiation in social situations.

    This augmentation:
    Increases the amount of money the player earns at a company by 10%.
    Increases the amount of reputation the player gains when working for a company or faction by 15%.",work_money_mult:1.1,company_rep_mult:1.15,faction_rep_mult:1.15});ze.addToFactions(["Tian Di Hui"]),w(o.AugmentationNames.SNA)&&delete a.Augmentations[o.AugmentationNames.SNA],O(ze);if(Object(c.factionExists)("Bladeburners")){const e=new r.Augmentation({name:o.AugmentationNames.EsperEyewear,repCost:500,moneyCost:33e6,info:"Ballistic-grade protective and retractable eyewear that was designed specially for Bladeburner units. This is implanted by installing a mechanical frame in the skull's orbit. This frame interfaces with the brain and allows the user to automatically extrude and extract the eyewear. The eyewear protects against debris, shrapnel, laser, flash, and gas. It is also embedded with a data processing chip that can be programmed to display an AR HUD and assist the user in field missions.

    This augmentation:
    Increases the player's success chance in Bladeburner contracts/operations by 3%.
    Increases the player's dexterity by 5%.",bladeburner_success_chance_mult:1.03,dexterity_mult:1.05,isSpecial:!0});e.addToFactions(["Bladeburners"]),S(e);const t=new r.Augmentation({name:o.AugmentationNames.EMS4Recombination,repCost:1e3,moneyCost:55e6,info:"A DNA recombination of the EMS-4 Gene. This genetic engineering technique was originally used on Bladeburners during the Synthoid uprising to induce wakefulness and concentration, suppress fear, reduce empathy, and improve reflexes and memory-recall among other things.

    This augmentation:
    Increases the player's sucess chance in Bladeburner contracts/operations by 3%.
    Increases the player's effectiveness in Bladeburner Field Analysis by 5%.
    Increases the player's Bladeburner stamina gain rate by 2%.",bladeburner_success_chance_mult:1.03,bladeburner_analysis_mult:1.05,bladeburner_stamina_gain_mult:1.02,isSpecial:!0});t.addToFactions(["Bladeburners"]),S(t);const n=new r.Augmentation({name:o.AugmentationNames.OrionShoulder,repCost:2500,moneyCost:11e7,info:"A bionic shoulder augmentation for the right shoulder. Using cybernetics, the ORION-MKIV shoulder enhances the strength and dexterity of the user's right arm. It also provides protection due to its crystallized graphene plating.

    This augmentation:
    Increases the player's defense by 5%.
    Increases the player's strength and dexterity by 5%.
    Increases the player's success chance in Bladeburner contracts/operations by 4%.",defense_mult:1.05,strength_mult:1.05,dexterity_mult:1.05,bladeburner_success_chance_mult:1.04,isSpecial:!0});n.addToFactions(["Bladeburners"]),S(n);const a=new r.Augmentation({name:o.AugmentationNames.HyperionV1,repCost:5e3,moneyCost:55e7,info:"A pair of mini plasma cannons embedded into the hands. The Hyperion is capable of rapidly firing bolts of high-density plasma. The weapon is meant to be used against augmented enemies as the ionized nature of the plasma disrupts the electrical systems of Augmentations. However, it can also be effective against non-augmented enemies due to its high temperature and concussive force.

    This augmentation:
    Increases the player's success chance in Bladeburner contracts/operations by 6%.",bladeburner_success_chance_mult:1.06,isSpecial:!0});a.addToFactions(["Bladeburners"]),S(a);const i=new r.Augmentation({name:o.AugmentationNames.HyperionV2,repCost:1e4,moneyCost:11e8,info:"A pair of mini plasma cannons embedded into the hands. This augmentation is more advanced and powerful than the original V1 model. This V2 model is more power-efficiency, more accurate, and can fire plasma bolts at a much higher velocity than the V1 model.

    This augmentation:
    Increases the player's success chance in Bladeburner contracts/operations by 8%.",prereqs:[o.AugmentationNames.HyperionV1],bladeburner_success_chance_mult:1.08,isSpecial:!0});i.addToFactions(["Bladeburners"]),S(i);const s=new r.Augmentation({name:o.AugmentationNames.GolemSerum,repCost:12500,moneyCost:22e8,info:"A serum that permanently enhances many aspects of a human's capabilities, including strength, speed, immune system performance, and mitochondrial efficiency. The serum was originally developed by the Chinese military in an attempt to create super soldiers.

    This augmentation:
    Increases all of the player's combat stats by 7%.
    Increases the player's Bladeburner stamina gain rate by 5%.
    ",strength_mult:1.07,defense_mult:1.07,dexterity_mult:1.07,agility_mult:1.07,bladeburner_stamina_gain_mult:1.05,isSpecial:!0});s.addToFactions(["Bladeburners"]),S(s);const l=new r.Augmentation({name:o.AugmentationNames.VangelisVirus,repCost:7500,moneyCost:55e7,info:"A synthetic symbiotic virus that is injected into the human brain tissue. The Vangelis virus heightens the senses and focus of its host, and also enhances its intuition.

    This augmentation:
    Increases the player's effectiveness in Bladeburner Field Analysis by 10%.
    Increases the player's success chance in Bladeburner contracts/operations by 4%.
    Increases the player's dexterity experience gain rate by 10%.",dexterity_exp_mult:1.1,bladeburner_analysis_mult:1.1,bladeburner_success_chance_mult:1.04,isSpecial:!0});l.addToFactions(["Bladeburners"]),S(l);const c=new r.Augmentation({name:o.AugmentationNames.VangelisVirus3,repCost:15e3,moneyCost:22e8,info:"An improved version of Vangelis, a synthetic symbiotic virus that is injected into the human brain tissue. On top of the benefits of the original virus, this also grants an accelerated healing factor and enhanced agility/reflexes.

    This augmentation:
    Increases the player's effectiveness in Bladeburner Field Analysis by 15%.
    Increases the player's defense and dexterity experience gain rate by 10%.
    Increases the player's success chance in Bladeburner contracts/operations by 5%.",prereqs:[o.AugmentationNames.VangelisVirus],defense_exp_mult:1.1,dexterity_exp_mult:1.1,bladeburner_analysis_mult:1.15,bladeburner_success_chance_mult:1.05,isSpecial:!0});c.addToFactions(["Bladeburners"]),S(c);const u=new r.Augmentation({name:o.AugmentationNames.INTERLINKED,repCost:1e4,moneyCost:11e8,info:"The DNA is genetically modified to enhance the human's body extracellular matrix (ECM). This improves the ECM's ability to structurally support the body and grants heightened strength and durability.

    This augmentation:
    Increases the player's experience gain rate for all combat stats by 5%.
    Increases the player's Bladeburner max stamina by 10%.",strength_exp_mult:1.05,defense_exp_mult:1.05,dexterity_exp_mult:1.05,agility_exp_mult:1.05,bladeburner_max_stamina_mult:1.1,isSpecial:!0});u.addToFactions(["Bladeburners"]),S(u);const p=new r.Augmentation({name:o.AugmentationNames.BladeRunner,repCost:8e3,moneyCost:165e7,info:"A cybernetic foot augmentation that was specially created for Bladeburners during the Synthoid Uprising. The organic musculature of the human foot is enhanced with flexible carbon nanotube matrices that are controlled by intelligent servo-motors.

    This augmentation:
    Increases the player's agility by 5%.
    Increases the player's Bladeburner max stamina by 5%.
    Increases the player's Bladeburner stamina gain rate by 5%.
    ",agility_mult:1.05,bladeburner_max_stamina_mult:1.05,bladeburner_stamina_gain_mult:1.05,isSpecial:!0});p.addToFactions(["Bladeburners"]),S(p);const h=new r.Augmentation({name:o.AugmentationNames.BladeArmor,repCost:5e3,moneyCost:275e6,info:"A powered exoskeleton suit (exosuit) designed as armor for Bladeburner units. This exoskeleton is incredibly adaptable and can protect the wearer from blunt, piercing, concussive, thermal, chemical, and electric trauma. It also enhances the user's strength and agility.

    This augmentation:
    Increases all of the player's combat stats by 4%.
    Increases the player's Bladeburner stamina gain rate by 2%.
    Increases the player's success chance in Bladeburner contracts/operations by 3%.",strength_mult:1.04,defense_mult:1.04,dexterity_mult:1.04,agility_mult:1.04,bladeburner_stamina_gain_mult:1.02,bladeburner_success_chance_mult:1.03,isSpecial:!0});h.addToFactions(["Bladeburners"]),S(h);const m=new r.Augmentation({name:o.AugmentationNames.BladeArmorPowerCells,repCost:7500,moneyCost:55e7,info:"Upgrades the BLADE-51b Tesla Armor with Ion Power Cells, which are capable of more efficiently storing and using power.

    This augmentation:
    Increases the player's success chance in Bladeburner contracts/operations by 5%.
    Increases the player's Bladeburner stamina gain rate by 2%.
    Increases the player's Bladeburner max stamina by 5%.",prereqs:[o.AugmentationNames.BladeArmor],bladeburner_success_chance_mult:1.05,bladeburner_stamina_gain_mult:1.02,bladeburner_max_stamina_mult:1.05,isSpecial:!0});m.addToFactions(["Bladeburners"]),S(m);const d=new r.Augmentation({name:o.AugmentationNames.BladeArmorEnergyShielding,repCost:8500,moneyCost:11e8,info:"Upgrades the BLADE-51b Tesla Armor with a plasma energy propulsion system that is capable of projecting an energy shielding force field.

    This augmentation:
    Increases the player's defense by 5%.
    Increases the player's success chance in Bladeburner contracts/operations by 6%.",prereqs:[o.AugmentationNames.BladeArmor],defense_mult:1.05,bladeburner_success_chance_mult:1.06,isSpecial:!0});d.addToFactions(["Bladeburners"]),S(d);const g=new r.Augmentation({name:o.AugmentationNames.BladeArmorUnibeam,repCost:12500,moneyCost:33e8,info:"Upgrades the BLADE-51b Tesla Armor with a concentrated deuterium-fluoride laser weapon. It's precision an accuracy makes it useful for quickly neutralizing threats while keeping casualties to a minimum.

    This augmentation:
    Increases the player's success chance in Bladeburner contracts/operations by 8%.",prereqs:[o.AugmentationNames.BladeArmor],bladeburner_success_chance_mult:1.08,isSpecial:!0});g.addToFactions(["Bladeburners"]),S(g);const _=new r.Augmentation({name:o.AugmentationNames.BladeArmorOmnibeam,repCost:25e3,moneyCost:55e8,info:"Upgrades the BLADE-51b Tesla Armor Unibeam augmentation to use multiple-fiber system. The upgraded weapon uses multiple fiber laser modules that combine together to form a single, more powerful beam of up to 2000MW.

    This augmentation:
    Increases the player's success chance in Bladeburner contracts/operations by 10%.",prereqs:[o.AugmentationNames.BladeArmorUnibeam],bladeburner_success_chance_mult:1.1,isSpecial:!0});_.addToFactions(["Bladeburners"]),S(_);const y=new r.Augmentation({name:o.AugmentationNames.BladeArmorIPU,repCost:6e3,moneyCost:22e7,info:"Upgrades the BLADE-51b Tesla Armor with an AI Information Processing Unit that was specially designed to analyze Synthoid related data and information.

    This augmentation:
    Increases the player's effectiveness in Bladeburner Field Analysis by 15%.
    Increases the player's success chance in Bladeburner contracts/operations by 2%.",prereqs:[o.AugmentationNames.BladeArmor],bladeburner_analysis_mult:1.15,bladeburner_success_chance_mult:1.02,isSpecial:!0});y.addToFactions(["Bladeburners"]),S(y);const f=new r.Augmentation({name:o.AugmentationNames.BladesSimulacrum,repCost:500,moneyCost:3e10,info:"A highly-advanced matter phase-shifter module that is embedded in the brainstem and cerebellum. This augmentation allows the user to project and control a holographic simulacrum within an extremely large radius. These specially-modified holograms were specially weaponized by Bladeburner units to be used against Synthoids.

    This augmentation allows you to perform Bladeburner actions and other actions (such as working, commiting crimes, etc.) at the same time.",isSpecial:!0});f.addToFactions(["Bladeburners"]),S(f)}for(var e in _e=Math.pow(l.CONSTANTS.MultipleAugMultiplier,p.Player.queuedAugmentations.length),a.Augmentations)a.Augmentations.hasOwnProperty(e)&&(a.Augmentations[e].baseCost*=_e);p.Player.reapplyAllAugmentations()}function S(e){if(!(e instanceof r.Augmentation))throw new Error("Invalid argument 'newAugObject' passed into resetAugmentation");var t=e.name;w(t)&&delete a.Augmentations[t],O(e)}function M(e,t=!1){a.Augmentations[e.name].owned=!0;const n=a.Augmentations[e.name];for(const e in n.mults)null==p.Player[e]?console.warn(`Augmentation has unrecognized multiplier property: ${e}`):p.Player[e]*=n.mults[e];if(e.name===o.AugmentationNames.NeuroFluxGovernor&&!t){a.Augmentations[e.name].level=e.level;for(let t=0;t"):console.log("ERROR. Invalid augmentation")}if(p.Player.queuedAugmentations=[],Object(f.dialogBoxCreate)("You slowly drift to sleep as scientists put you under in order to install the following Augmentations:
    "+t+"
    You wake up in your home...you feel different..."),Object(h.a)(),e&&Object(C.isString)(e)){var i=p.Player.getHomeComputer();for(n=0;ni.maxRam-i.ramUsed)return;var s=new d.RunningScript(o,[]);s.threads=1,i.runScript(s,p.Player.hacknet_node_money_mult),Object(u.b)(s,i)}}}function w(e){return a.Augmentations.hasOwnProperty(e)}function A(e){Object(P.removeChildrenFromElement)(e),e.appendChild(Object(k.createElement)("h1",{innerText:"Purchased Augmentations"})),e.appendChild(Object(k.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.\nWARNING: Installing your Augmentations resets most of your progress, including:\n\nStats/Skill levels and Experience\nMoney\nScripts on every computer but your home computer\nPurchased servers\nHacknet Nodes\nFaction/Company reputation\nStocks\nInstalling 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)."})),e.appendChild(Object(k.createElement)("a",{class:"a-link-button",innerText:"Install Augmentations",tooltip:"'I never asked for this'",clickListener:()=>(x(),!1)})),e.appendChild(Object(k.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:()=>(m.b.exportGame(),!1)}));for(var t=Object(k.createElement)("ul",{class:"augmentations-list"}),n=0;n{for(var e=c.getElementsByClassName("accordion-header"),t=0;t{for(var e=c.getElementsByClassName("accordion-header"),t=0;t{Object(P.removeChildrenFromElement)(c);var e=p.Player.sourceFiles.slice(),t=p.Player.augmentations.slice();e.sort((e,t)=>e.n-t.n),t.sort((e,t)=>e.name<=t.name?-1:1),N(c,e),R(c,t),_.Settings.OwnedAugmentationsOrder=g.OwnedAugmentationsOrderSetting.Alphabetically}});e.appendChild(u);const h=Object(k.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:()=>{Object(P.removeChildrenFromElement)(c),N(c,p.Player.sourceFiles),R(c,p.Player.augmentations),_.Settings.OwnedAugmentationsOrder=g.OwnedAugmentationsOrderSetting.AcquirementTime}});e.appendChild(h),_.Settings.OwnedAugmentationsOrder===g.OwnedAugmentationsOrderSetting.Alphabetically?u.click():h.click(),e.appendChild(c),e.appendChild(Object(k.createElement)("p",{display:"block",innerHTML:"

    Total Multipliers:
    Hacking Chance multiplier: "+Object(v.formatNumber)(100*p.Player.hacking_chance_mult,2)+"%
    Hacking Speed multiplier: "+Object(v.formatNumber)(100*p.Player.hacking_speed_mult,2)+"%
    Hacking Money multiplier: "+Object(v.formatNumber)(100*p.Player.hacking_money_mult,2)+"%
    Hacking Growth multiplier: "+Object(v.formatNumber)(100*p.Player.hacking_grow_mult,2)+"%

    Hacking Level multiplier: "+Object(v.formatNumber)(100*p.Player.hacking_mult,2)+"%
    Hacking Experience multiplier: "+Object(v.formatNumber)(100*p.Player.hacking_exp_mult,2)+"%

    Strength Level multiplier: "+Object(v.formatNumber)(100*p.Player.strength_mult,2)+"%
    Strength Experience multiplier: "+Object(v.formatNumber)(100*p.Player.strength_exp_mult,2)+"%

    Defense Level multiplier: "+Object(v.formatNumber)(100*p.Player.defense_mult,2)+"%
    Defense Experience multiplier: "+Object(v.formatNumber)(100*p.Player.defense_exp_mult,2)+"%

    Dexterity Level multiplier: "+Object(v.formatNumber)(100*p.Player.dexterity_mult,2)+"%
    Dexterity Experience multiplier: "+Object(v.formatNumber)(100*p.Player.dexterity_exp_mult,2)+"%

    Agility Level multiplier: "+Object(v.formatNumber)(100*p.Player.agility_mult,2)+"%
    Agility Experience multiplier: "+Object(v.formatNumber)(100*p.Player.agility_exp_mult,2)+"%

    Charisma Level multiplier: "+Object(v.formatNumber)(100*p.Player.charisma_mult,2)+"%
    Charisma Experience multiplier: "+Object(v.formatNumber)(100*p.Player.charisma_exp_mult,2)+"%

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

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


    "}))}function R(e,t){for(var n=0;nLevel "+t[n].lvl+" / "+o,panelText:a.info});e.appendChild(i[0])}}function I(e){return(e instanceof r.Augmentation?e.name:e)===o.AugmentationNames.NeuroFluxGovernor}},function(e,t,n){"use strict";n.d(t,"b",function(){return o}),n.d(t,"c",function(){return s}),n.d(t,"a",function(){return l});var r=n(0),a=n(39),i=n(6);function o(e,t,n){return`ERROR: bladeburner.${e}() failed due to an invalid action specified. `+`Type: ${t}, Name: ${n}. Note that for contracts and operations, the `+"name of the operation is case-sensitive."}function s(e,t){return`bladeburner.${e}() failed with exception: `+t}function l(e,t){const n=`${t}() failed because you do not `+"currently have access to the Bladeburner API. To access the Bladeburner APIyou must be employed at the Bladeburner division, AND you must either be in BitNode-7 or have Source-File 7.";if(!(r.Player.bladeburner instanceof a.a&&(7===r.Player.bitNodeN||r.Player.sourceFiles.some(e=>7===e.n))))throw Object(i.c)(e,n)}},function(module,__webpack_exports__,__webpack_require__){"use strict";(function($){__webpack_require__.d(__webpack_exports__,"b",function(){return postNetburnerText}),__webpack_require__.d(__webpack_exports__,"a",function(){return Terminal});var _Terminal_DirectoryHelpers__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(78),_Terminal_DirectoryHelpers__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(_Terminal_DirectoryHelpers__WEBPACK_IMPORTED_MODULE_0__),_Terminal_determineAllPossibilitiesForTabCompletion__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(272),_Terminal_determineAllPossibilitiesForTabCompletion__WEBPACK_IMPORTED_MODULE_1___default=__webpack_require__.n(_Terminal_determineAllPossibilitiesForTabCompletion__WEBPACK_IMPORTED_MODULE_1__),_Terminal_HelpText__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(180),_Terminal_HelpText__WEBPACK_IMPORTED_MODULE_2___default=__webpack_require__.n(_Terminal_HelpText__WEBPACK_IMPORTED_MODULE_2__),_Terminal_tabCompletion__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(271),_Terminal_tabCompletion__WEBPACK_IMPORTED_MODULE_3___default=__webpack_require__.n(_Terminal_tabCompletion__WEBPACK_IMPORTED_MODULE_3__),_Alias__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(63),_Alias__WEBPACK_IMPORTED_MODULE_4___default=__webpack_require__.n(_Alias__WEBPACK_IMPORTED_MODULE_4__),_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(20),_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_5___default=__webpack_require__.n(_BitNode_BitNodeMultipliers__WEBPACK_IMPORTED_MODULE_5__),_CodingContracts__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(50),_CodingContracts__WEBPACK_IMPORTED_MODULE_6___default=__webpack_require__.n(_CodingContracts__WEBPACK_IMPORTED_MODULE_6__),_Constants__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__(8),_Constants__WEBPACK_IMPORTED_MODULE_7___default=__webpack_require__.n(_Constants__WEBPACK_IMPORTED_MODULE_7__),_Programs_Programs__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__(30),_Programs_Programs__WEBPACK_IMPORTED_MODULE_8___default=__webpack_require__.n(_Programs_Programs__WEBPACK_IMPORTED_MODULE_8__),_DarkWeb_DarkWeb__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__(202),_DarkWeb_DarkWebItems__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__(100),_DarkWeb_DarkWebItems__WEBPACK_IMPORTED_MODULE_10___default=__webpack_require__.n(_DarkWeb_DarkWebItems__WEBPACK_IMPORTED_MODULE_10__),_engine__WEBPACK_IMPORTED_MODULE_11__=__webpack_require__(14),_Fconf_Fconf__WEBPACK_IMPORTED_MODULE_12__=__webpack_require__(129),_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__=__webpack_require__(34),_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13___default=__webpack_require__.n(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__),_Hacking__WEBPACK_IMPORTED_MODULE_14__=__webpack_require__(56),_Hacknet_HacknetServer__WEBPACK_IMPORTED_MODULE_15__=__webpack_require__(35),_Hacknet_HacknetServer__WEBPACK_IMPORTED_MODULE_15___default=__webpack_require__.n(_Hacknet_HacknetServer__WEBPACK_IMPORTED_MODULE_15__),_InteractiveTutorial__WEBPACK_IMPORTED_MODULE_16__=__webpack_require__(40),_Literature__WEBPACK_IMPORTED_MODULE_17__=__webpack_require__(130),_Message_Message__WEBPACK_IMPORTED_MODULE_18__=__webpack_require__(65),_Message_Message__WEBPACK_IMPORTED_MODULE_18___default=__webpack_require__.n(_Message_Message__WEBPACK_IMPORTED_MODULE_18__),_Message_MessageHelpers__WEBPACK_IMPORTED_MODULE_19__=__webpack_require__(71),_NetscriptWorker__WEBPACK_IMPORTED_MODULE_20__=__webpack_require__(42),_Player__WEBPACK_IMPORTED_MODULE_21__=__webpack_require__(0),_RedPill__WEBPACK_IMPORTED_MODULE_22__=__webpack_require__(84),_Script_RunningScript__WEBPACK_IMPORTED_MODULE_23__=__webpack_require__(138),_Script_RunningScript__WEBPACK_IMPORTED_MODULE_23___default=__webpack_require__.n(_Script_RunningScript__WEBPACK_IMPORTED_MODULE_23__),_Script_RunningScriptHelpers__WEBPACK_IMPORTED_MODULE_24__=__webpack_require__(168),_Script_RunningScriptHelpers__WEBPACK_IMPORTED_MODULE_24___default=__webpack_require__.n(_Script_RunningScriptHelpers__WEBPACK_IMPORTED_MODULE_24__),_Script_ScriptHelpers__WEBPACK_IMPORTED_MODULE_25__=__webpack_require__(67),_Script_ScriptHelpersTS__WEBPACK_IMPORTED_MODULE_26__=__webpack_require__(61),_Script_ScriptHelpersTS__WEBPACK_IMPORTED_MODULE_26___default=__webpack_require__.n(_Script_ScriptHelpersTS__WEBPACK_IMPORTED_MODULE_26__),_Server_AllServers__WEBPACK_IMPORTED_MODULE_27__=__webpack_require__(21),_Server_AllServers__WEBPACK_IMPORTED_MODULE_27___default=__webpack_require__.n(_Server_AllServers__WEBPACK_IMPORTED_MODULE_27__),_Server_Server__WEBPACK_IMPORTED_MODULE_28__=__webpack_require__(106),_Server_Server__WEBPACK_IMPORTED_MODULE_28___default=__webpack_require__.n(_Server_Server__WEBPACK_IMPORTED_MODULE_28__),_Server_ServerHelpers__WEBPACK_IMPORTED_MODULE_29__=__webpack_require__(22),_Server_ServerHelpers__WEBPACK_IMPORTED_MODULE_29___default=__webpack_require__.n(_Server_ServerHelpers__WEBPACK_IMPORTED_MODULE_29__),_Settings_Settings__WEBPACK_IMPORTED_MODULE_30__=__webpack_require__(19),_Settings_Settings__WEBPACK_IMPORTED_MODULE_30___default=__webpack_require__.n(_Settings_Settings__WEBPACK_IMPORTED_MODULE_30__),_Server_SpecialServerIps__WEBPACK_IMPORTED_MODULE_31__=__webpack_require__(38),_Server_SpecialServerIps__WEBPACK_IMPORTED_MODULE_31___default=__webpack_require__.n(_Server_SpecialServerIps__WEBPACK_IMPORTED_MODULE_31__),_TextFile__WEBPACK_IMPORTED_MODULE_32__=__webpack_require__(96),_TextFile__WEBPACK_IMPORTED_MODULE_32___default=__webpack_require__.n(_TextFile__WEBPACK_IMPORTED_MODULE_32__),_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_33__=__webpack_require__(68),_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_33___default=__webpack_require__.n(_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_33__),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__=__webpack_require__(12),_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34___default=__webpack_require__.n(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_35__=__webpack_require__(2),_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_35___default=__webpack_require__.n(_ui_numeralFormat__WEBPACK_IMPORTED_MODULE_35__),_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__=__webpack_require__(32),_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36___default=__webpack_require__.n(_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__),_utils_helpers_addOffset__WEBPACK_IMPORTED_MODULE_37__=__webpack_require__(89),_utils_helpers_addOffset__WEBPACK_IMPORTED_MODULE_37___default=__webpack_require__.n(_utils_helpers_addOffset__WEBPACK_IMPORTED_MODULE_37__),_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_38__=__webpack_require__(51),_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_38___default=__webpack_require__.n(_utils_helpers_isString__WEBPACK_IMPORTED_MODULE_38__),_utils_helpers_arrayToString__WEBPACK_IMPORTED_MODULE_39__=__webpack_require__(79),_utils_helpers_arrayToString__WEBPACK_IMPORTED_MODULE_39___default=__webpack_require__.n(_utils_helpers_arrayToString__WEBPACK_IMPORTED_MODULE_39__),_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_40__=__webpack_require__(148),_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_40___default=__webpack_require__.n(_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_40__),_utils_LogBox__WEBPACK_IMPORTED_MODULE_41__=__webpack_require__(120),_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_42__=__webpack_require__(46),_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_42___default=__webpack_require__.n(_utils_YesNoBox__WEBPACK_IMPORTED_MODULE_42__),_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_43__=__webpack_require__(7),_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_43___default=__webpack_require__.n(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_43__),autosize__WEBPACK_IMPORTED_MODULE_44__=__webpack_require__(267),autosize__WEBPACK_IMPORTED_MODULE_44___default=__webpack_require__.n(autosize__WEBPACK_IMPORTED_MODULE_44__),jszip__WEBPACK_IMPORTED_MODULE_45__=__webpack_require__(266),jszip__WEBPACK_IMPORTED_MODULE_45___default=__webpack_require__.n(jszip__WEBPACK_IMPORTED_MODULE_45__),file_saver__WEBPACK_IMPORTED_MODULE_46__=__webpack_require__(265),file_saver__WEBPACK_IMPORTED_MODULE_46___default=__webpack_require__.n(file_saver__WEBPACK_IMPORTED_MODULE_46__);function postNetburnerText(){Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_43__.post)("Bitburner v"+_Constants__WEBPACK_IMPORTED_MODULE_7__.CONSTANTS.Version)}function isNumber(e){return"string"==typeof e&&(!isNaN(e)&&!isNaN(parseFloat(e)))}$(document).keydown(function(e){if(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.Page.Terminal)){var t=document.getElementById("terminal-input-text-box");if(null==t||e.ctrlKey||e.shiftKey||Terminal.contractOpen||t.focus(),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.ENTER){e.preventDefault();const n=t.value,r=Terminal.currDir;Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_43__.post)("["+(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_TIMESTAMPS?Object(_utils_helpers_getTimestamp__WEBPACK_IMPORTED_MODULE_40__.getTimestamp)()+" ":"")+_Player__WEBPACK_IMPORTED_MODULE_21__.Player.getCurrentServer().hostname+` ~${r}]> ${n}`),n.length>0&&(Terminal.resetTerminalInput(),Terminal.executeCommands(n))}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.C&&e.ctrlKey&&(_engine__WEBPACK_IMPORTED_MODULE_11__.Engine._actionInProgress?(Object(_ui_postToTerminal__WEBPACK_IMPORTED_MODULE_43__.post)("Cancelling..."),_engine__WEBPACK_IMPORTED_MODULE_11__.Engine._actionInProgress=!1,Terminal.finishAction(!0)):_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_BASH_HOTKEYS&&Terminal.resetTerminalInput()),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.L&&e.ctrlKey&&(e.preventDefault(),Terminal.executeCommand("clear")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.UPARROW||_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.P&&e.ctrlKey){if(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.preventDefault(),null==t)return;var n=Terminal.commandHistoryIndex;if(0==(a=Terminal.commandHistory.length))return;(n<0||n>a)&&(Terminal.commandHistoryIndex=a),0!=n&&--Terminal.commandHistoryIndex;var r=Terminal.commandHistory[Terminal.commandHistoryIndex];t.value=r,Object(_utils_SetTimeoutRef__WEBPACK_IMPORTED_MODULE_33__.setTimeoutRef)(function(){t.selectionStart=t.selectionEnd=1e4},0)}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.DOWNARROW||_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.M&&e.ctrlKey){if(_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_BASH_HOTKEYS&&e.preventDefault(),null==t)return;var a;n=Terminal.commandHistoryIndex;if(0==(a=Terminal.commandHistory.length))return;if((n<0||n>a)&&(Terminal.commandHistoryIndex=a),n==a||n==a-1)Terminal.commandHistoryIndex=a,t.value="";else{++Terminal.commandHistoryIndex;r=Terminal.commandHistory[Terminal.commandHistoryIndex];t.value=r}}if(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.TAB){if(e.preventDefault(),null==t)return;let n=t.value;if(""==n)return;const r=n.lastIndexOf(";");-1!==r&&(n=n.slice(r+1));const a=(n=(n=n.trim()).replace(/\s\s+/g," ")).split(" ");let i=a.length-2;i<-1&&(i=0);const o=Object(_Terminal_determineAllPossibilitiesForTabCompletion__WEBPACK_IMPORTED_MODULE_1__.determineAllPossibilitiesForTabCompletion)(_Player__WEBPACK_IMPORTED_MODULE_21__.Player,n,i,Terminal.currDir);if(0==o.length)return;let s="",l="";if(0==a.length)return;1==a.length?l=a[0]:2==a.length?(l=a[0],s=a[1]):3==a.length?(l=a[0]+" "+a[1],s=a[2]):(s=a.pop(),l=a.join(" ")),Object(_Terminal_tabCompletion__WEBPACK_IMPORTED_MODULE_3__.tabCompletion)(l,s,o),t.focus()}_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.ENABLE_BASH_HOTKEYS&&(e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.A&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("home")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.E&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("end")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.B&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("prevchar")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.B&&e.altKey&&(e.preventDefault(),Terminal.moveTextCursor("prevword")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.F&&e.ctrlKey&&(e.preventDefault(),Terminal.moveTextCursor("nextchar")),e.keyCode===_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.F&&e.altKey&&(e.preventDefault(),Terminal.moveTextCursor("nextword")),e.keyCode!==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.H&&e.keyCode!==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.D||!e.ctrlKey||(Terminal.modifyInput("backspace"),e.preventDefault()))}});let terminalCtrlPressed=!1,shiftKeyPressed=!1;$(document).ready(function(){_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.Page.Terminal)&&$(".terminal-input").focus()}),$(document).keydown(function(e){if(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.Page.Terminal))if(e.which==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.CTRL)terminalCtrlPressed=!0;else if(e.shiftKey)shiftKeyPressed=!0;else if(terminalCtrlPressed||shiftKeyPressed||Terminal.contractOpen);else{var t=document.getElementById("terminal-input-text-box");null!=t&&t.focus(),terminalCtrlPressed=!1,shiftKeyPressed=!1}}),$(document).keyup(function(e){_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.routing.isOn(_ui_navigationTracking__WEBPACK_IMPORTED_MODULE_34__.Page.Terminal)&&(e.which==_utils_helpers_keyCodes__WEBPACK_IMPORTED_MODULE_36__.KEY.CTRL&&(terminalCtrlPressed=!1),e.shiftKey&&(shiftKeyPressed=!1))});let Terminal={hackFlag:!1,analyzeFlag:!1,actionStarted:!1,actionTime:0,commandHistory:[],commandHistoryIndex:0,contractOpen:!1,currDir:"/",resetTerminalInput:function(){const e=Terminal.currDir;_Fconf_FconfSettings__WEBPACK_IMPORTED_MODULE_13__.FconfSettings.WRAP_INPUT?(document.getElementById("terminal-input-td").innerHTML=`
    [${_Player__WEBPACK_IMPORTED_MODULE_21__.Player.getCurrentServer().hostname} ~${e}]$
    `+'",v.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var xe=s.documentElement,we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ke=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ae(){return!1}function _e(){try{return s.activeElement}catch(e){}}function Se(e,t,n,r,i,o){var s,a;if("object"==typeof t){for(a in"string"!=typeof n&&(r=r||n,n=void 0),t)Se(e,a,n,r,t[a],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ae;else if(!i)return e;return 1===o&&(s=i,(i=function(e){return k().off(e),s.apply(this,arguments)}).guid=s.guid||(s.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}k.event={global:{},add:function(e,t,n,r,i){var o,s,a,l,u,c,h,d,f,p,g,m=J.get(e);if(m)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(xe,i),n.guid||(n.guid=k.guid++),(l=m.events)||(l=m.events={}),(s=m.handle)||(s=m.handle=function(t){return void 0!==k&&k.event.triggered!==t.type?k.event.dispatch.apply(e,arguments):void 0}),u=(t=(t||"").match(j)||[""]).length;u--;)f=g=(a=ke.exec(t[u])||[])[1],p=(a[2]||"").split(".").sort(),f&&(h=k.event.special[f]||{},f=(i?h.delegateType:h.bindType)||f,h=k.event.special[f]||{},c=k.extend({type:f,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:p.join(".")},o),(d=l[f])||((d=l[f]=[]).delegateCount=0,h.setup&&!1!==h.setup.call(e,r,p,s)||e.addEventListener&&e.addEventListener(f,s)),h.add&&(h.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),k.event.global[f]=!0)},remove:function(e,t,n,r,i){var o,s,a,l,u,c,h,d,f,p,g,m=J.hasData(e)&&J.get(e);if(m&&(l=m.events)){for(u=(t=(t||"").match(j)||[""]).length;u--;)if(f=g=(a=ke.exec(t[u])||[])[1],p=(a[2]||"").split(".").sort(),f){for(h=k.event.special[f]||{},d=l[f=(r?h.delegateType:h.bindType)||f]||[],a=a[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=d.length;o--;)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,h.remove&&h.remove.call(e,c));s&&!d.length&&(h.teardown&&!1!==h.teardown.call(e,p,m.handle)||k.removeEvent(e,f,m.handle),delete l[f])}else for(f in l)k.event.remove(e,f+t[u],n,r,!0);k.isEmptyObject(l)&&J.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,s,a=k.event.fix(e),l=new Array(arguments.length),u=(J.get(this,"events")||{})[a.type]||[],c=k.event.special[a.type]||{};for(l[0]=a,t=1;t=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==e.type||!0!==u.disabled)){for(o=[],s={},n=0;n-1:k.find(i,this,null,[u]).length),s[i]&&o.push(r);o.length&&a.push({elem:u,handlers:o})}return u=this,l\x20\t\r\n\f]*)[^>]*)\/>/gi,De=/\s*$/g;function Le(e,t){return T(e,"table")&&T(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Me(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Pe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Re(e,t){var n,r,i,o,s,a,l,u;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),s=J.set(t,o),u=o.events))for(i in delete s.handle,s.events={},u)for(n=0,r=u[i].length;n1&&"string"==typeof p&&!v.checkClone&&Te.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),Ie(o,t,n,r)});if(d&&(o=(i=be(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(a=(s=k.map(me(i,"script"),Me)).length;h")},clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),l=k.contains(e.ownerDocument,e);if(!(v.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(s=me(a),r=0,i=(o=me(e)).length;r0&&ve(s,!l&&me(e,"script")),a},cleanData:function(e){for(var t,n,r,i=k.event.special,o=0;void 0!==(n=e[o]);o++)if(X(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?k.event.remove(n,r):k.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),k.fn.extend({detach:function(e){return je(this,e,!0)},remove:function(e){return je(this,e)},text:function(e){return V(this,function(e){return void 0===e?k.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ie(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Ie(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(k.cleanData(me(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return k.clone(this,e,t)})},html:function(e){return V(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!De.test(e)&&!ge[(fe.exec(e)||["",""])[1].toLowerCase()]){e=k.htmlPrefilter(e);try{for(;n=0&&(l+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-l-a-.5))),l}function Qe(e,t,n){var r=$e(e),i=ze(e,t,r),o="border-box"===k.css(e,"boxSizing",!1,r),s=o;if(Ne.test(i)){if(!n)return i;i="auto"}return s=s&&(v.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===k.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],s=!0),(i=parseFloat(i)||0)+Je(e,t,n||(o?"border":"content"),s,r,i)+"px"}function et(e,t,n,r,i){return new et.prototype.init(e,t,n,r,i)}k.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=ze(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=Y(t),l=Ve.test(t),u=e.style;if(l||(t=Xe(a)),s=k.cssHooks[t]||k.cssHooks[a],void 0===n)return s&&"get"in s&&void 0!==(i=s.get(e,!1,r))?i:u[t];"string"===(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=le(e,t,i),o="number"),null!=n&&n==n&&("number"===o&&(n+=i&&i[3]||(k.cssNumber[a]?"":"px")),v.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&void 0===(n=s.set(e,n,r))||(l?u.setProperty(t,n):u[t]=n))}},css:function(e,t,n,r){var i,o,s,a=Y(t);return Ve.test(t)||(t=Xe(a)),(s=k.cssHooks[t]||k.cssHooks[a])&&"get"in s&&(i=s.get(e,!0,n)),void 0===i&&(i=ze(e,t,r)),"normal"===i&&t in Ke&&(i=Ke[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),k.each(["height","width"],function(e,t){k.cssHooks[t]={get:function(e,n,r){if(n)return!Ue.test(k.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Qe(e,t,r):ae(e,Ge,function(){return Qe(e,t,r)})},set:function(e,n,r){var i,o=$e(e),s="border-box"===k.css(e,"boxSizing",!1,o),a=r&&Je(e,t,r,s,o);return s&&v.scrollboxSize()===o.position&&(a-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Je(e,t,"border",!1,o)-.5)),a&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=k.css(e,t)),Ze(0,n,a)}}}),k.cssHooks.marginLeft=He(v.reliableMarginLeft,function(e,t){if(t)return(parseFloat(ze(e,"marginLeft"))||e.getBoundingClientRect().left-ae(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),k.each({margin:"",padding:"",border:"Width"},function(e,t){k.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(k.cssHooks[e+t].set=Ze)}),k.fn.extend({css:function(e,t){return V(this,function(e,t,n){var r,i,o={},s=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;s1)}}),k.Tween=et,et.prototype={constructor:et,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||k.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(k.cssNumber[n]?"":"px")},cur:function(){var e=et.propHooks[this.prop];return e&&e.get?e.get(this):et.propHooks._default.get(this)},run:function(e){var t,n=et.propHooks[this.prop];return this.options.duration?this.pos=t=k.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):et.propHooks._default.set(this),this}},et.prototype.init.prototype=et.prototype,et.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=k.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){k.fx.step[e.prop]?k.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[k.cssProps[e.prop]]&&!k.cssHooks[e.prop]?e.elem[e.prop]=e.now:k.style(e.elem,e.prop,e.now+e.unit)}}},et.propHooks.scrollTop=et.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},k.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},k.fx=et.prototype.init,k.fx.step={};var tt,nt,rt=/^(?:toggle|show|hide)$/,it=/queueHooks$/;function ot(){nt&&(!1===s.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ot):n.setTimeout(ot,k.fx.interval),k.fx.tick())}function st(){return n.setTimeout(function(){tt=void 0}),tt=Date.now()}function at(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(ut.tweeners[t]||[]).concat(ut.tweeners["*"]),o=0,s=i.length;o1)},removeAttr:function(e){return this.each(function(){k.removeAttr(this,e)})}}),k.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?k.prop(e,t,n):(1===o&&k.isXMLDoc(e)||(i=k.attrHooks[t.toLowerCase()]||(k.expr.match.bool.test(t)?ct:void 0)),void 0!==n?null===n?void k.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=k.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!v.radioValue&&"radio"===t&&T(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(j);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),ct={set:function(e,t,n){return!1===t?k.removeAttr(e,n):e.setAttribute(n,n),n}},k.each(k.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||k.find.attr;ht[t]=function(e,t,r){var i,o,s=t.toLowerCase();return r||(o=ht[s],ht[s]=i,i=null!=n(e,t,r)?s:null,ht[s]=o),i}});var dt=/^(?:input|select|textarea|button)$/i,ft=/^(?:a|area)$/i;function pt(e){return(e.match(j)||[]).join(" ")}function gt(e){return e.getAttribute&&e.getAttribute("class")||""}function mt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(j)||[]}k.fn.extend({prop:function(e,t){return V(this,k.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[k.propFix[e]||e]})}}),k.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&k.isXMLDoc(e)||(t=k.propFix[t]||t,i=k.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=k.find.attr(e,"tabindex");return t?parseInt(t,10):dt.test(e.nodeName)||ft.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),v.optSelected||(k.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),k.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){k.propFix[this.toLowerCase()]=this}),k.fn.extend({addClass:function(e){var t,n,r,i,o,s,a,l=0;if(y(e))return this.each(function(t){k(this).addClass(e.call(this,t,gt(this)))});if((t=mt(e)).length)for(;n=this[l++];)if(i=gt(n),r=1===n.nodeType&&" "+pt(i)+" "){for(s=0;o=t[s++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(a=pt(r))&&n.setAttribute("class",a)}return this},removeClass:function(e){var t,n,r,i,o,s,a,l=0;if(y(e))return this.each(function(t){k(this).removeClass(e.call(this,t,gt(this)))});if(!arguments.length)return this.attr("class","");if((t=mt(e)).length)for(;n=this[l++];)if(i=gt(n),r=1===n.nodeType&&" "+pt(i)+" "){for(s=0;o=t[s++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(a=pt(r))&&n.setAttribute("class",a)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):y(e)?this.each(function(n){k(this).toggleClass(e.call(this,n,gt(this),t),t)}):this.each(function(){var t,i,o,s;if(r)for(i=0,o=k(this),s=mt(e);t=s[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=gt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+pt(gt(n))+" ").indexOf(t)>-1)return!0;return!1}});var vt=/\r/g;k.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=y(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,k(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=k.map(i,function(e){return null==e?"":e+""})),(t=k.valHooks[this.type]||k.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=k.valHooks[i.type]||k.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(vt,""):null==n?"":n:void 0}}),k.extend({valHooks:{option:{get:function(e){var t=k.find.attr(e,"value");return null!=t?t:pt(k.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,s="select-one"===e.type,a=s?null:[],l=s?o+1:i.length;for(r=o<0?l:s?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),k.each(["radio","checkbox"],function(){k.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=k.inArray(k(e).val(),t)>-1}},v.checkOn||(k.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),v.focusin="onfocusin"in n;var yt=/^(?:focusinfocus|focusoutblur)$/,bt=function(e){e.stopPropagation()};k.extend(k.event,{trigger:function(e,t,r,i){var o,a,l,u,c,h,d,f,g=[r||s],m=p.call(e,"type")?e.type:e,v=p.call(e,"namespace")?e.namespace.split("."):[];if(a=f=l=r=r||s,3!==r.nodeType&&8!==r.nodeType&&!yt.test(m+k.event.triggered)&&(m.indexOf(".")>-1&&(m=(v=m.split(".")).shift(),v.sort()),c=m.indexOf(":")<0&&"on"+m,(e=e[k.expando]?e:new k.Event(m,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=v.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:k.makeArray(t,[e]),d=k.event.special[m]||{},i||!d.trigger||!1!==d.trigger.apply(r,t))){if(!i&&!d.noBubble&&!b(r)){for(u=d.delegateType||m,yt.test(u+m)||(a=a.parentNode);a;a=a.parentNode)g.push(a),l=a;l===(r.ownerDocument||s)&&g.push(l.defaultView||l.parentWindow||n)}for(o=0;(a=g[o++])&&!e.isPropagationStopped();)f=a,e.type=o>1?u:d.bindType||m,(h=(J.get(a,"events")||{})[e.type]&&J.get(a,"handle"))&&h.apply(a,t),(h=c&&a[c])&&h.apply&&X(a)&&(e.result=h.apply(a,t),!1===e.result&&e.preventDefault());return e.type=m,i||e.isDefaultPrevented()||d._default&&!1!==d._default.apply(g.pop(),t)||!X(r)||c&&y(r[m])&&!b(r)&&((l=r[c])&&(r[c]=null),k.event.triggered=m,e.isPropagationStopped()&&f.addEventListener(m,bt),r[m](),e.isPropagationStopped()&&f.removeEventListener(m,bt),k.event.triggered=void 0,l&&(r[c]=l)),e.result}},simulate:function(e,t,n){var r=k.extend(new k.Event,n,{type:e,isSimulated:!0});k.event.trigger(r,null,t)}}),k.fn.extend({trigger:function(e,t){return this.each(function(){k.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return k.event.trigger(e,t,n,!0)}}),v.focusin||k.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){k.event.simulate(t,e.target,k.event.fix(e))};k.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var xt=n.location,wt=Date.now(),Ct=/\?/;k.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||k.error("Invalid XML: "+e),t};var kt=/\[\]$/,Et=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,_t=/^(?:input|select|textarea|keygen)/i;function St(e,t,n,r){var i;if(Array.isArray(t))k.each(t,function(t,i){n||kt.test(e)?r(e,i):St(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==C(t))r(e,t);else for(i in t)St(e+"["+i+"]",t[i],n,r)}k.param=function(e,t){var n,r=[],i=function(e,t){var n=y(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!k.isPlainObject(e))k.each(e,function(){i(this.name,this.value)});else for(n in e)St(n,e[n],t,i);return r.join("&")},k.fn.extend({serialize:function(){return k.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=k.prop(this,"elements");return e?k.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!k(this).is(":disabled")&&_t.test(this.nodeName)&&!At.test(e)&&(this.checked||!de.test(e))}).map(function(e,t){var n=k(this).val();return null==n?null:Array.isArray(n)?k.map(n,function(e){return{name:t.name,value:e.replace(Et,"\r\n")}}):{name:t.name,value:n.replace(Et,"\r\n")}}).get()}});var Ft=/%20/g,Dt=/#.*$/,Tt=/([?&])_=[^&]*/,Bt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Lt=/^(?:GET|HEAD)$/,Mt=/^\/\//,Pt={},Rt={},Ot="*/".concat("*"),It=s.createElement("a");function jt(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(j)||[];if(y(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Nt(e,t,n,r){var i={},o=e===Rt;function s(a){var l;return i[a]=!0,k.each(e[a]||[],function(e,a){var u=a(t,n,r);return"string"!=typeof u||o||i[u]?o?!(l=u):void 0:(t.dataTypes.unshift(u),s(u),!1)}),l}return s(t.dataTypes[0])||!i["*"]&&s("*")}function $t(e,t){var n,r,i=k.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&k.extend(!0,e,r),e}It.href=xt.href,k.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:xt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(xt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ot,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":k.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?$t($t(e,k.ajaxSettings),t):$t(k.ajaxSettings,e)},ajaxPrefilter:jt(Pt),ajaxTransport:jt(Rt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,a,l,u,c,h,d,f,p=k.ajaxSetup({},t),g=p.context||p,m=p.context&&(g.nodeType||g.jquery)?k(g):k.event,v=k.Deferred(),y=k.Callbacks("once memory"),b=p.statusCode||{},x={},w={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!a)for(a={};t=Bt.exec(o);)a[t[1].toLowerCase()]=t[2];t=a[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?o:null},setRequestHeader:function(e,t){return null==c&&(e=w[e.toLowerCase()]=w[e.toLowerCase()]||e,x[e]=t),this},overrideMimeType:function(e){return null==c&&(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||C;return r&&r.abort(t),A(0,t),this}};if(v.promise(E),p.url=((e||p.url||xt.href)+"").replace(Mt,xt.protocol+"//"),p.type=t.method||t.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(j)||[""],null==p.crossDomain){u=s.createElement("a");try{u.href=p.url,u.href=u.href,p.crossDomain=It.protocol+"//"+It.host!=u.protocol+"//"+u.host}catch(e){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=k.param(p.data,p.traditional)),Nt(Pt,p,t,E),c)return E;for(d in(h=k.event&&p.global)&&0==k.active++&&k.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Lt.test(p.type),i=p.url.replace(Dt,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(Ft,"+")):(f=p.url.slice(i.length),p.data&&(p.processData||"string"==typeof p.data)&&(i+=(Ct.test(i)?"&":"?")+p.data,delete p.data),!1===p.cache&&(i=i.replace(Tt,"$1"),f=(Ct.test(i)?"&":"?")+"_="+wt+++f),p.url=i+f),p.ifModified&&(k.lastModified[i]&&E.setRequestHeader("If-Modified-Since",k.lastModified[i]),k.etag[i]&&E.setRequestHeader("If-None-Match",k.etag[i])),(p.data&&p.hasContent&&!1!==p.contentType||t.contentType)&&E.setRequestHeader("Content-Type",p.contentType),E.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Ot+"; q=0.01":""):p.accepts["*"]),p.headers)E.setRequestHeader(d,p.headers[d]);if(p.beforeSend&&(!1===p.beforeSend.call(g,E,p)||c))return E.abort();if(C="abort",y.add(p.complete),E.done(p.success),E.fail(p.error),r=Nt(Rt,p,t,E)){if(E.readyState=1,h&&m.trigger("ajaxSend",[E,p]),c)return E;p.async&&p.timeout>0&&(l=n.setTimeout(function(){E.abort("timeout")},p.timeout));try{c=!1,r.send(x,A)}catch(e){if(c)throw e;A(-1,e)}}else A(-1,"No Transport");function A(e,t,s,a){var u,d,f,x,w,C=t;c||(c=!0,l&&n.clearTimeout(l),r=void 0,o=a||"",E.readyState=e>0?4:0,u=e>=200&&e<300||304===e,s&&(x=function(e,t,n){for(var r,i,o,s,a=e.contents,l=e.dataTypes;"*"===l[0];)l.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in a)if(a[i]&&a[i].test(r)){l.unshift(i);break}if(l[0]in n)o=l[0];else{for(i in n){if(!l[0]||e.converters[i+" "+l[0]]){o=i;break}s||(s=i)}o=o||s}if(o)return o!==l[0]&&l.unshift(o),n[o]}(p,E,s)),x=function(e,t,n,r){var i,o,s,a,l,u={},c=e.dataTypes.slice();if(c[1])for(s in e.converters)u[s.toLowerCase()]=e.converters[s];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(!(s=u[l+" "+o]||u["* "+o]))for(i in u)if((a=i.split(" "))[1]===o&&(s=u[l+" "+a[0]]||u["* "+a[0]])){!0===s?s=u[i]:!0!==u[i]&&(o=a[0],c.unshift(a[1]));break}if(!0!==s)if(s&&e.throws)t=s(t);else try{t=s(t)}catch(e){return{state:"parsererror",error:s?e:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}(p,x,E,u),u?(p.ifModified&&((w=E.getResponseHeader("Last-Modified"))&&(k.lastModified[i]=w),(w=E.getResponseHeader("etag"))&&(k.etag[i]=w)),204===e||"HEAD"===p.type?C="nocontent":304===e?C="notmodified":(C=x.state,d=x.data,u=!(f=x.error))):(f=C,!e&&C||(C="error",e<0&&(e=0))),E.status=e,E.statusText=(t||C)+"",u?v.resolveWith(g,[d,C,E]):v.rejectWith(g,[E,C,f]),E.statusCode(b),b=void 0,h&&m.trigger(u?"ajaxSuccess":"ajaxError",[E,p,u?d:f]),y.fireWith(g,[E,C]),h&&(m.trigger("ajaxComplete",[E,p]),--k.active||k.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return k.get(e,t,n,"json")},getScript:function(e,t){return k.get(e,void 0,t,"script")}}),k.each(["get","post"],function(e,t){k[t]=function(e,n,r,i){return y(n)&&(i=i||r,r=n,n=void 0),k.ajax(k.extend({url:e,type:t,dataType:i,data:n,success:r},k.isPlainObject(e)&&e))}}),k._evalUrl=function(e){return k.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},k.fn.extend({wrapAll:function(e){var t;return this[0]&&(y(e)&&(e=e.call(this[0])),t=k(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return y(e)?this.each(function(t){k(this).wrapInner(e.call(this,t))}):this.each(function(){var t=k(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=y(e);return this.each(function(n){k(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){k(this).replaceWith(this.childNodes)}),this}}),k.expr.pseudos.hidden=function(e){return!k.expr.pseudos.visible(e)},k.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},k.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Wt={0:200,1223:204},zt=k.ajaxSettings.xhr();v.cors=!!zt&&"withCredentials"in zt,v.ajax=zt=!!zt,k.ajaxTransport(function(e){var t,r;if(v.cors||zt&&!e.crossDomain)return{send:function(i,o){var s,a=e.xhr();if(a.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(s in e.xhrFields)a[s]=e.xhrFields[s];for(s in e.mimeType&&a.overrideMimeType&&a.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)a.setRequestHeader(s,i[s]);t=function(e){return function(){t&&(t=r=a.onload=a.onerror=a.onabort=a.ontimeout=a.onreadystatechange=null,"abort"===e?a.abort():"error"===e?"number"!=typeof a.status?o(0,"error"):o(a.status,a.statusText):o(Wt[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=t(),r=a.onerror=a.ontimeout=t("error"),void 0!==a.onabort?a.onabort=r:a.onreadystatechange=function(){4===a.readyState&&n.setTimeout(function(){t&&r()})},t=t("abort");try{a.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),k.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),k.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return k.globalEval(e),e}}}),k.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),k.ajaxTransport("script",function(e){var t,n;if(e.crossDomain)return{send:function(r,i){t=k("