From 50cf362b3bd93255325bfa741a2911dedd33cedd Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Fri, 1 Oct 2021 16:22:33 -0400 Subject: [PATCH] v0.55.0 --- css/_mixins.scss | 61 - css/_reset.scss | 15 - css/_theme.scss | 18 - css/augmentations.scss | 24 - css/bladeburner.scss | 135 - css/buttons.scss | 112 - css/casino.scss | 24 - css/characteroverview.scss | 12 - css/companymanagement.scss | 168 - css/dev-menu.css | 32 - css/gameoptions.scss | 19 - css/gang.scss | 46 - css/grid.min.css | 3413 -------------------- css/hacknetnodes.scss | 69 - css/infiltration.scss | 61 - css/interactivetutorial.scss | 90 - css/loader.scss | 111 - css/mainmenu.scss | 137 - css/menupages.scss | 128 - css/milestones.scss | 3 - css/missions.scss | 119 - css/popupboxes.scss | 246 -- css/redpill.scss | 34 - css/resleeving.scss | 28 - css/scripteditor.scss | 92 - css/sleeves.scss | 25 - css/stockmarket.scss | 96 - css/styles.scss | 528 --- css/tooltips.scss | 129 - css/treant.css | 68 - css/workinprogress.scss | 51 - dist/vendor.bundle.js | 22 +- doc/source/changelog.rst | 22 + doc/source/conf.py | 4 +- index.html | 13 +- main.bundle.js | 4 +- main.bundle.js.map | 2 +- src/Augmentation/ui/AugmentationsRoot.tsx | 12 +- src/Constants.ts | 52 +- src/DevMenu.tsx | 3 +- src/DevMenu/ui/Augmentations.tsx | 2 +- src/DevMenu/ui/Bladeburner.tsx | 2 +- src/DevMenu/ui/CodingContracts.tsx | 3 +- src/DevMenu/ui/Companies.tsx | 2 +- src/DevMenu/ui/Corporation.tsx | 2 +- src/DevMenu/ui/Factions.tsx | 2 +- src/DevMenu/ui/Gang.tsx | 2 +- src/DevMenu/ui/General.tsx | 3 +- src/DevMenu/ui/Programs.tsx | 2 +- src/DevMenu/ui/Servers.tsx | 2 +- src/DevMenu/ui/Sleeves.tsx | 2 +- src/DevMenu/ui/SourceFiles.tsx | 2 +- src/DevMenu/ui/Stats.tsx | 2 +- src/DevMenu/ui/StockMarket.tsx | 2 +- src/DevMenu/ui/TimeSkip.tsx | 3 +- src/Locations/ui/City.tsx | 6 +- src/PersonObjects/Sleeve/Sleeve.ts | 1 + src/PersonObjects/Sleeve/ui/SleeveRoot.tsx | 2 +- src/ScriptEditor/ui/Root.tsx | 2 +- src/engineStyle.ts | 32 - src/index.html | 11 + src/index.tsx | 1 - src/ui/React/Theme.tsx | 5 + src/ui/React/WorldMap.tsx | 4 +- 64 files changed, 120 insertions(+), 6205 deletions(-) delete mode 100644 css/_mixins.scss delete mode 100644 css/_reset.scss delete mode 100644 css/_theme.scss delete mode 100644 css/augmentations.scss delete mode 100644 css/bladeburner.scss delete mode 100644 css/buttons.scss delete mode 100644 css/casino.scss delete mode 100644 css/characteroverview.scss delete mode 100644 css/companymanagement.scss delete mode 100644 css/dev-menu.css delete mode 100644 css/gameoptions.scss delete mode 100644 css/gang.scss delete mode 100644 css/grid.min.css delete mode 100644 css/hacknetnodes.scss delete mode 100644 css/infiltration.scss delete mode 100644 css/interactivetutorial.scss delete mode 100644 css/loader.scss delete mode 100644 css/mainmenu.scss delete mode 100644 css/menupages.scss delete mode 100644 css/milestones.scss delete mode 100644 css/missions.scss delete mode 100644 css/popupboxes.scss delete mode 100644 css/redpill.scss delete mode 100644 css/resleeving.scss delete mode 100644 css/scripteditor.scss delete mode 100644 css/sleeves.scss delete mode 100644 css/stockmarket.scss delete mode 100644 css/styles.scss delete mode 100644 css/tooltips.scss delete mode 100644 css/treant.css delete mode 100644 css/workinprogress.scss delete mode 100644 src/engineStyle.ts diff --git a/css/_mixins.scss b/css/_mixins.scss deleted file mode 100644 index ab256a69e..000000000 --- a/css/_mixins.scss +++ /dev/null @@ -1,61 +0,0 @@ -@mixin animation($property) { - -webkit-animation: $property; - -moz-animation: $property; - -ms-animation: $property; - -o-animation: $property; - animation: $property; -} - -@mixin borderRadius($property) { - -webkit-border-radius: $property; - -moz-border-radius: $property; - border-radius: $property; -} - -@mixin boxShadow($value) { - -webkit-box-shadow: $value; - -moz-box-shadow: $value; - box-shadow: $value; -} - -@mixin keyframes($animationName) { - @-webkit-keyframes #{$animationName} { - $browser: "-webkit-" !global; - @content; - } - - @-moz-keyframes #{$animationName} { - $browser: "-moz-" !global; - @content; - } - - @-ms-keyframes #{$animationName} { - $browser: "-ms-" !global; - @content; - } - - @-o-keyframes #{$animationName} { - $browser: "-o-" !global; - @content; - } - - @keyframes #{$animationName} { - $browser: "" !global; - @content; - } -} - -@mixin transform($property) { - -webkit-transform: $property; - -moz-transform: $property; - -ms-transform: $property; - -o-transform: $property; - transform: $property; -} - -@mixin userSelect($value) { - -webkit-user-select: $value; - -moz-user-select: $value; - -ms-user-select: $value; - user-select: $value; -} diff --git a/css/_reset.scss b/css/_reset.scss deleted file mode 100644 index 145f51196..000000000 --- a/css/_reset.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import "theme"; - -* { - font-size: $defaultFontSize; - font-family: $fontFamily; -} - -*, -*:before, -*:after { - margin: 0; - padding: 0; - box-sizing: border-box; - vertical-align: middle; -} diff --git a/css/_theme.scss b/css/_theme.scss deleted file mode 100644 index fc44765cd..000000000 --- a/css/_theme.scss +++ /dev/null @@ -1,18 +0,0 @@ -$fontFamily: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, - "Times New Roman"; -$defaultFontSize: 16px; - -/* COLORS */ -$hacker-green: #adff2f; -$success-green: #3adb76; -$alert-red: #ff2929; -$money-gold: #ffd700; -$light-yellow: #faffdf; - -/* Attributes */ -$my-stat-hp-color: #dd3434; -$my-stat-money-color: $money-gold; -$my-stat-hack-color: $hacker-green; -$my-stat-physical: $light-yellow; -$my-stat-cha-color: #a671d1; -$my-stat-int-color: #6495ed; diff --git a/css/augmentations.scss b/css/augmentations.scss deleted file mode 100644 index 53193ae1e..000000000 --- a/css/augmentations.scss +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 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-content { - > p { - font-size: $defaultFontSize * 0.875; - } -} - -.augmentations-list { - button, - div { - color: var(--my-font-color); - text-decoration: none; - } - - button { - padding: 4px; - } -} diff --git a/css/bladeburner.scss b/css/bladeburner.scss deleted file mode 100644 index 73ab7824f..000000000 --- a/css/bladeburner.scss +++ /dev/null @@ -1,135 +0,0 @@ -@import "theme"; - -.bladeburner-container { - a, - div, - p, - pre, - td { - font-size: $defaultFontSize * 0.8125; - } -} - -.bladeburner-action { - border: 1px solid #fff; - margin: 7px; - padding: 7px; - white-space: pre-wrap; - - pre { - white-space: pre-wrap; - } -} - -/* Whatever action is currently active */ -.bladeburner-active-action { - border: 4px solid #fff; -} - -/* Action & Skills panel navigation button */ -%bladeburner-nav-button { - border: 1px solid #fff; - margin: 2px; - padding: 2px; - color: #fff; -} - -.bladeburner-nav-button { - @extend %bladeburner-nav-button; - - &:hover { - background-color: #3d4044; - } -} - -.bladeburner-nav-button-inactive { - @extend %bladeburner-nav-button; - - text-decoration: none; - background-color: #555; - cursor: default; - pointer-events: none; -} - -/* Checkbox for (de)selecting autoleveling */ -.bbcheckbox { - position: relative; - display: inline; - label { - width: 20px; - height: 20px; - cursor: pointer; - position: absolute; - top: 0; - left: 0; - background: black; - border-width: 1px; - border-color: white; - border-style: solid; - &:after { - content: ""; - width: 9px; - height: 5px; - position: absolute; - top: 5px; - left: 5px; - border: 3px solid white; - border-top: none; - border-right: none; - opacity: 0; - transform: rotate(-45deg); - } - } - input[type="checkbox"] { - margin: 3px; - visibility: hidden; - &:checked + label:after { - opacity: 1; - } - } -} - -/* Bladeburner Console */ -.bladeburner-console-div { - display: inline-block; - width: 40%; - border: 1px solid #fff; - overflow: auto; - height: 100%; - position: absolute; -} - -.bladeburner-console-table { - height: auto; - overflow: auto; - table-layout: fixed; - width: 100%; -} - -.bladeburner-console-input-row { - transition: height 1s; - width: 100%; -} - -.bladeburner-console-input-cell { - display: flex; -} - -.bladeburner-console-input { - display: inline-block; - padding: 0 !important; - margin: 0 !important; - border: 0; - background-color: var(--my-background-color); - font-size: $defaultFontSize * 0.8125; - outline: none; - color: var(--my-font-color); - flex: 1 1 auto; -} - -.bladeburner-console-line { - word-wrap: break-word; - hyphens: auto; - -webkit-hyphens: auto; - -moz-hyphens: auto; -} diff --git a/css/buttons.scss b/css/buttons.scss deleted file mode 100644 index 4b86a4461..000000000 --- a/css/buttons.scss +++ /dev/null @@ -1,112 +0,0 @@ -@import "mixins"; -@import "theme"; -@import "styles"; - -/** - * Styling for all buttons - * - * Includes \n \n \n \n \n \n {props.script.logs.map(\n (line: string, i: number): JSX.Element => (\n \n {line}\n
\n
\n ),\n )}\n
\n \n );\n}\n","/**\n * Hacknet Node Class\n *\n * Hacknet Nodes are specialized machines that passively earn the player money over time.\n * They can be upgraded to increase their production\n */\nimport { IHacknetNode } from \"./IHacknetNode\";\n\nimport { CONSTANTS } from \"../Constants\";\n\nimport {\n calculateMoneyGainRate,\n calculateLevelUpgradeCost,\n calculateCoreUpgradeCost,\n calculateRamUpgradeCost,\n} from \"./formulas/HacknetNodes\";\nimport { HacknetNodeConstants } from \"./data/Constants\";\n\nimport { dialogBoxCreate } from \"../ui/React/DialogBox\";\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../utils/JSONReviver\";\n\nexport class HacknetNode implements IHacknetNode {\n // Node's number of cores\n cores = 1;\n\n // Node's Level\n level = 1;\n\n // Node's production per second\n moneyGainRatePerSecond = 0;\n\n // Identifier for Node. Includes the full \"name\" (hacknet-node-N)\n name: string;\n\n // How long this Node has existed, in seconds\n onlineTimeSeconds = 0;\n\n // Node's RAM (GB)\n ram = 1;\n\n // Total money earned by this Node\n totalMoneyGenerated = 0;\n\n constructor(name = \"\", prodMult = 1) {\n this.name = name;\n\n this.updateMoneyGainRate(prodMult);\n }\n\n // Get the cost to upgrade this Node's number of cores\n calculateCoreUpgradeCost(levels = 1, costMult: number): number {\n return calculateCoreUpgradeCost(this.cores, levels, costMult);\n }\n\n // Get the cost to upgrade this Node's level\n calculateLevelUpgradeCost(levels = 1, costMult: number): number {\n return calculateLevelUpgradeCost(this.level, levels, costMult);\n }\n\n // Get the cost to upgrade this Node's RAM\n calculateRamUpgradeCost(levels = 1, costMult: number): number {\n return calculateRamUpgradeCost(this.ram, levels, costMult);\n }\n\n // Process this Hacknet Node in the game loop.\n // Returns the amount of money generated\n process(numCycles = 1): number {\n const seconds = (numCycles * CONSTANTS.MilliPerCycle) / 1000;\n let gain = this.moneyGainRatePerSecond * seconds;\n if (isNaN(gain)) {\n console.error(`Hacknet Node ${this.name} calculated earnings of NaN`);\n gain = 0;\n }\n\n this.totalMoneyGenerated += gain;\n this.onlineTimeSeconds += seconds;\n\n return gain;\n }\n\n // Upgrade this Node's number of cores, if possible\n // Returns a boolean indicating whether new cores were successfully bought\n upgradeCore(levels = 1, prodMult: number): void {\n this.cores = Math.min(HacknetNodeConstants.MaxCores, Math.round(this.cores + levels));\n this.updateMoneyGainRate(prodMult);\n }\n\n // Upgrade this Node's level, if possible\n // Returns a boolean indicating whether the level was successfully updated\n upgradeLevel(levels = 1, prodMult: number): void {\n this.level = Math.min(HacknetNodeConstants.MaxLevel, Math.round(this.level + levels));\n this.updateMoneyGainRate(prodMult);\n }\n\n // Upgrade this Node's RAM, if possible\n // Returns a boolean indicating whether the RAM was successfully upgraded\n upgradeRam(levels = 1, prodMult: number): void {\n for (let i = 0; i < levels; ++i) {\n this.ram *= 2; // Ram is always doubled\n }\n this.ram = Math.round(this.ram); // Handle any floating point precision issues\n this.updateMoneyGainRate(prodMult);\n }\n\n // Re-calculate this Node's production and update the moneyGainRatePerSecond prop\n updateMoneyGainRate(prodMult: number): void {\n this.moneyGainRatePerSecond = calculateMoneyGainRate(this.level, this.ram, this.cores, prodMult);\n if (isNaN(this.moneyGainRatePerSecond)) {\n this.moneyGainRatePerSecond = 0;\n dialogBoxCreate(\"Error in calculating Hacknet Node production. Please report to game developer\");\n }\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"HacknetNode\", this);\n }\n\n /**\n * Initiatizes a HacknetNode object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): HacknetNode {\n return Generic_fromJSON(HacknetNode, value.data);\n }\n}\n\nReviver.constructors.HacknetNode = HacknetNode;\n","/**\n * Map of all Hash Upgrades\n * Key = Hash name, Value = HashUpgrade object\n */\nimport { HashUpgrade, IConstructorParams } from \"./HashUpgrade\";\nimport { HashUpgradesMetadata } from \"./data/HashUpgradesMetadata\";\nimport { IMap } from \"../types\";\n\nexport const HashUpgrades: IMap = {};\n\nfunction createHashUpgrade(p: IConstructorParams): void {\n HashUpgrades[p.name] = new HashUpgrade(p);\n}\n\nfor (const metadata of HashUpgradesMetadata) {\n createHashUpgrade(metadata);\n}\n","/**\n * Returns the input array as a comma separated string.\n *\n * Does several things that Array.toString() doesn't do\n * - Adds brackets around the array\n * - Adds quotation marks around strings\n */\nexport function arrayToString(a: T[]): string {\n const vals: any[] = [];\n for (let i = 0; i < a.length; ++i) {\n let elem: any = a[i];\n if (Array.isArray(elem)) {\n elem = arrayToString(elem);\n } else if (typeof elem === \"string\") {\n elem = `\"${elem}\"`;\n }\n vals.push(elem);\n }\n\n return `[${vals.join(\", \")}]`;\n}\n","import { BitNodeMultipliers } from \"../BitNode/BitNodeMultipliers\";\nimport { CONSTANTS } from \"../Constants\";\n\nexport function getStockMarketAccountCost(): number {\n return CONSTANTS.WSEAccountCost;\n}\n\nexport function getStockMarketTixApiCost(): number {\n return CONSTANTS.TIXAPICost;\n}\n\nexport function getStockMarket4SDataCost(): number {\n return CONSTANTS.MarketData4SCost * BitNodeMultipliers.FourSigmaMarketDataCost;\n}\n\nexport function getStockMarket4STixApiCost(): number {\n return CONSTANTS.MarketDataTixApi4SCost * BitNodeMultipliers.FourSigmaMarketDataApiCost;\n}\n","/**\n * React Component for a button that's used to apply for a job\n */\nimport * as React from \"react\";\n\nimport { Company } from \"../../Company/Company\";\nimport { CompanyPosition } from \"../../Company/CompanyPosition\";\nimport { getJobRequirementText } from \"../../Company/GetJobRequirementText\";\n\nimport { use } from \"../../ui/Context\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\ntype IProps = {\n company: Company;\n entryPosType: CompanyPosition;\n onClick: (e: React.MouseEvent) => void;\n text: string;\n};\n\nexport function ApplyToJobButton(props: IProps): React.ReactElement {\n const player = use.Player();\n\n function getJobRequirementTooltip(): string {\n const pos = player.getNextCompanyPosition(props.company, props.entryPosType);\n if (pos == null) {\n return \"\";\n }\n\n if (!props.company.hasPosition(pos)) {\n return \"\";\n }\n\n return getJobRequirementText(props.company, pos, true);\n }\n\n return (\n <>\n }>\n \n \n
\n \n );\n}\n","/**\n * Event emitter that triggers when scripts are started/stopped\n */\nimport { EventEmitter } from \"../utils/EventEmitter\";\n\nexport const WorkerScriptStartStopEventEmitter = new EventEmitter<[]>();\n","/*\nThe game cannot block every possible exploits. Specially since one of them is\nthat you can just edit your save file and that's impragmatic to prevent.\n\nSo instead we have source file minus 1. It is not obtained by destroying a\nBitNode but instead it is awarded when the player finds innovative ways to break\nthe game, this serves 2 purpose, [one] the developpers don't have to spend time\ntrying to implement anti-cheat measures and [two] finding ways to break a\nhacking game is very much in the spirit of the game.\nSource-File minus 1 is extremely weak because it can be fully level up quickly.\n*/\n\nexport enum Exploit {\n UndocumentedFunctionCall = \"UndocumentedFunctionCall\",\n Unclickable = \"Unclickable\",\n PrototypeTampering = \"PrototypeTampering\",\n Bypass = \"Bypass\",\n // To the players reading this. Yes you're supposed to add EditSaveFile by\n // editing your save file, yes you could add them all, no we don't care\n // that's not the point.\n EditSaveFile = \"EditSaveFile\",\n}\n\nconst names: {\n [key: string]: string;\n} = {\n UndocumentedFunctionCall: \"by looking beyond the documentation.\",\n EditSaveFile: \"by editing your save file.\",\n PrototypeTampering: \"by tampering with Numbers prototype.\",\n Unclickable: \"by clicking the unclickable.\",\n Bypass: \"by circumventing the ram cost of document.\",\n};\n\nexport function ExploitName(exploit: string): string {\n return names[exploit];\n}\n\nexport function sanitizeExploits(exploits: Exploit[]): Exploit[] {\n return exploits.filter((e: Exploit) => Object.keys(Exploit).includes(e));\n}\n","import { DarkWebItem } from \"./DarkWebItem\";\nimport { IMap } from \"../types\";\nimport { Programs } from \"../Programs/Programs\";\n\nexport const DarkWebItems: IMap = {\n BruteSSHProgram: new DarkWebItem(Programs.BruteSSHProgram.name, 500e3, \"Opens up SSH Ports\"),\n FTPCrackProgram: new DarkWebItem(Programs.FTPCrackProgram.name, 1500e3, \"Opens up FTP Ports\"),\n RelaySMTPProgram: new DarkWebItem(Programs.RelaySMTPProgram.name, 5e6, \"Opens up SMTP Ports\"),\n HTTPWormProgram: new DarkWebItem(Programs.HTTPWormProgram.name, 30e6, \"Opens up HTTP Ports\"),\n SQLInjectProgram: new DarkWebItem(Programs.SQLInjectProgram.name, 250e6, \"Opens up SQL Ports\"),\n DeepscanV1: new DarkWebItem(Programs.DeepscanV1.name, 500000, \"Enables 'scan-analyze' with a depth up to 5\"),\n DeepscanV2: new DarkWebItem(Programs.DeepscanV2.name, 25e6, \"Enables 'scan-analyze' with a depth up to 10\"),\n AutolinkProgram: new DarkWebItem(Programs.AutoLink.name, 1e6, \"Enables direct connect via 'scan-analyze'\"),\n ServerProfilerProgram: new DarkWebItem(\n Programs.ServerProfiler.name,\n 1e6,\n \"Displays hacking and Netscript-related information about a server\",\n ),\n};\n","/**\n * Adds a random offset to a number within a certain percentage\n * @example\n * // Returns between 95-105\n * addOffset(100, 5);\n * @example\n * // Returns between 63-77\n * addOffSet(70, 10);\n * @param midpoint The number to be the midpoint of the offset range\n * @param percentage The percentage (in a range of 0-100) to offset\n */\nexport function addOffset(midpoint: number, percentage: number): number {\n const maxPercent = 100;\n if (percentage < 0 || percentage > maxPercent) {\n return midpoint;\n }\n\n const offset: number = midpoint * (percentage / maxPercent);\n\n // Double the range to account for both sides of the midpoint.\n // tslint:disable-next-line:no-magic-numbers\n return midpoint + (Math.random() * (offset * 2) - offset);\n}\n","export enum RamCalculationErrorCode {\n SyntaxError = -1,\n ImportError = -2,\n URLImportError = -3,\n}\n","export const GangConstants: {\n GangRespectToReputationRatio: number;\n MaximumGangMembers: number;\n CyclesPerTerritoryAndPowerUpdate: number;\n AscensionMultiplierRatio: number;\n Names: string[];\n} = {\n // Respect is divided by this to get rep gain\n GangRespectToReputationRatio: 25,\n MaximumGangMembers: 12,\n CyclesPerTerritoryAndPowerUpdate: 100,\n // Portion of upgrade multiplier that is kept after ascending\n AscensionMultiplierRatio: 0.15,\n // Names of possible Gangs\n Names: [\n \"Slum Snakes\",\n \"Tetrads\",\n \"The Syndicate\",\n \"The Dark Army\",\n \"Speakers for the Dead\",\n \"NiteSec\",\n \"The Black Hand\",\n ],\n};\n","import { getRandomByte } from \"./helpers/getRandomByte\";\n\n/**\n * Generate a random IP address\n * Does not check to see if the IP already exists in the game\n */\nexport function createRandomIp(): string {\n const ip: string = getRandomByte(99) + \".\" + getRandomByte(9) + \".\" + getRandomByte(9) + \".\" + getRandomByte(9);\n\n return ip;\n}\n","/**\n * Does a shallow compare of two arrays to determine if they are equal.\n * @param a1 The first array\n * @param a2 The second array\n */\nexport function compareArrays(a1: T[], a2: T[]): boolean {\n if (a1.length !== a2.length) {\n return false;\n }\n\n for (let i = 0; i < a1.length; ++i) {\n if (Array.isArray(a1[i])) {\n // If the other element is not an array, then these cannot be equal\n if (!Array.isArray(a2[i])) {\n return false;\n }\n\n const elem1 = a1[i] as any;\n const elem2 = a2[i] as any;\n if (!compareArrays(elem1, elem2)) {\n return false;\n }\n } else if (a1[i] !== a2[i]) {\n return false;\n }\n }\n\n return true;\n}\n","import * as React from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n favor: {\n color: theme.colors.rep,\n },\n }),\n);\n\nexport function Favor({ favor }: { favor: number | string }): React.ReactElement {\n const classes = useStyles();\n return {typeof favor === \"number\" ? numeralWrapper.formatFavor(favor) : favor};\n}\n","import React from \"react\";\nimport { createTheme, ThemeProvider, Theme, StyledEngineProvider } from \"@mui/material/styles\";\nimport { EventEmitter } from \"../../utils/EventEmitter\";\nimport { Settings } from \"../../Settings/Settings\";\n\nexport const ThemeEvents = new EventEmitter<[]>();\n\ndeclare module \"@mui/material/styles\" {\n interface Theme {\n colors: {\n hp: React.CSSProperties[\"color\"];\n money: React.CSSProperties[\"color\"];\n hack: React.CSSProperties[\"color\"];\n combat: React.CSSProperties[\"color\"];\n cha: React.CSSProperties[\"color\"];\n int: React.CSSProperties[\"color\"];\n rep: React.CSSProperties[\"color\"];\n };\n }\n interface ThemeOptions {\n colors: {\n hp: React.CSSProperties[\"color\"];\n money: React.CSSProperties[\"color\"];\n hack: React.CSSProperties[\"color\"];\n combat: React.CSSProperties[\"color\"];\n cha: React.CSSProperties[\"color\"];\n int: React.CSSProperties[\"color\"];\n rep: React.CSSProperties[\"color\"];\n };\n }\n}\n\nlet theme: Theme;\n\nexport function refreshTheme(): void {\n theme = createTheme({\n colors: {\n hp: Settings.theme.hp,\n money: Settings.theme.money,\n hack: Settings.theme.hack,\n combat: Settings.theme.combat,\n cha: Settings.theme.cha,\n int: Settings.theme.int,\n rep: Settings.theme.rep,\n },\n palette: {\n primary: {\n light: Settings.theme.primarylight,\n main: Settings.theme.primary,\n dark: Settings.theme.primarydark,\n },\n secondary: {\n light: Settings.theme.secondarylight,\n main: Settings.theme.secondary,\n dark: Settings.theme.secondarydark,\n },\n error: {\n light: Settings.theme.errorlight,\n main: Settings.theme.error,\n dark: Settings.theme.errordark,\n },\n info: {\n light: Settings.theme.infolight,\n main: Settings.theme.info,\n dark: Settings.theme.infodark,\n },\n warning: {\n light: Settings.theme.warninglight,\n main: Settings.theme.warning,\n dark: Settings.theme.warningdark,\n },\n background: {\n default: Settings.theme.black,\n paper: Settings.theme.well,\n },\n action: {\n disabled: Settings.theme.disabled,\n },\n },\n typography: {\n fontFamily: \"monospace\",\n button: {\n textTransform: \"none\",\n },\n },\n components: {\n MuiInputBase: {\n styleOverrides: {\n root: {\n backgroundColor: Settings.theme.well,\n color: Settings.theme.primary,\n },\n input: {\n \"&::placeholder\": {\n userSelect: \"none\",\n color: Settings.theme.primarydark,\n },\n },\n },\n },\n\n MuiInput: {\n styleOverrides: {\n root: {\n backgroundColor: Settings.theme.well,\n borderBottomColor: \"#fff\",\n },\n underline: {\n \"&:hover\": {\n borderBottomColor: Settings.theme.primarydark,\n },\n \"&:before\": {\n borderBottomColor: Settings.theme.primary,\n },\n \"&:after\": {\n borderBottomColor: Settings.theme.primarylight,\n },\n },\n },\n },\n\n MuiInputLabel: {\n styleOverrides: {\n root: {\n color: Settings.theme.primarydark, // why is this switched?\n userSelect: \"none\",\n \"&:before\": {\n color: Settings.theme.primarylight,\n },\n },\n },\n },\n MuiButton: {\n styleOverrides: {\n root: {\n backgroundColor: \"#333\",\n border: \"1px solid \" + Settings.theme.well,\n // color: Settings.theme.primary,\n \"&:hover\": {\n backgroundColor: Settings.theme.black,\n },\n\n borderRadius: 0,\n },\n },\n },\n MuiSelect: {\n styleOverrides: {\n icon: {\n color: Settings.theme.primary,\n },\n },\n defaultProps: {\n variant: \"standard\",\n },\n },\n MuiTextField: {\n defaultProps: {\n variant: \"standard\",\n },\n },\n MuiMenu: {\n styleOverrides: {\n list: {\n backgroundColor: Settings.theme.well,\n },\n },\n },\n MuiMenuItem: {\n styleOverrides: {\n root: {\n color: Settings.theme.primary,\n },\n },\n },\n MuiAccordionSummary: {\n styleOverrides: {\n root: {\n backgroundColor: \"#111\",\n },\n },\n },\n MuiAccordionDetails: {\n styleOverrides: {\n root: {\n backgroundColor: Settings.theme.black,\n },\n },\n },\n MuiIconButton: {\n styleOverrides: {\n root: {\n color: Settings.theme.primary,\n },\n },\n },\n MuiTooltip: {\n styleOverrides: {\n tooltip: {\n fontSize: \"1em\",\n color: Settings.theme.primary,\n backgroundColor: Settings.theme.well,\n borderRadius: 0,\n border: \"2px solid white\",\n maxWidth: \"100vh\",\n },\n },\n defaultProps: {\n disableInteractive: true,\n },\n },\n MuiSlider: {\n styleOverrides: {\n valueLabel: {\n color: Settings.theme.primary,\n backgroundColor: Settings.theme.well,\n },\n },\n },\n MuiDrawer: {\n styleOverrides: {\n paper: {\n \"&::-webkit-scrollbar\": {\n // webkit\n display: \"none\",\n },\n scrollbarWidth: \"none\", // firefox\n backgroundColor: Settings.theme.black,\n },\n paperAnchorDockedLeft: {\n borderRight: \"1px solid \" + Settings.theme.welllight,\n },\n },\n },\n MuiDivider: {\n styleOverrides: {\n root: {\n backgroundColor: Settings.theme.welllight,\n },\n },\n },\n MuiFormControlLabel: {\n styleOverrides: {\n root: {\n color: Settings.theme.primary,\n },\n },\n },\n MuiSwitch: {\n styleOverrides: {\n switchBase: {\n color: Settings.theme.primarydark,\n },\n track: {\n backgroundColor: Settings.theme.welllight,\n },\n },\n },\n MuiPaper: {\n styleOverrides: {\n root: {\n borderRadius: 0,\n backgroundColor: Settings.theme.black,\n border: \"1px solid \" + Settings.theme.welllight,\n },\n },\n },\n MuiTablePagination: {\n styleOverrides: {\n select: {\n color: Settings.theme.primary,\n },\n },\n },\n MuiTab: {\n styleOverrides: {\n textColorPrimary: {\n color: Settings.theme.secondary,\n \"&.Mui-selected\": {\n color: Settings.theme.primary,\n },\n },\n },\n },\n },\n });\n}\nrefreshTheme();\n\ninterface IProps {\n children: JSX.Element[] | JSX.Element;\n}\n\nexport const TTheme = ({ children }: IProps): React.ReactElement => (\n \n {children}\n \n);\n","import { Player } from \"../Player\";\nimport { getRandomInt } from \"../utils/helpers/getRandomInt\";\nimport { addOffset } from \"../utils/helpers/addOffset\";\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../utils/JSONReviver\";\nimport { BladeburnerConstants } from \"./data/Constants\";\nimport { IBladeburner } from \"./IBladeburner\";\nimport { IAction, ISuccessChanceParams } from \"./IAction\";\n\nclass StatsMultiplier {\n [key: string]: number;\n\n hack = 0;\n str = 0;\n def = 0;\n dex = 0;\n agi = 0;\n cha = 0;\n int = 0;\n}\n\nexport interface IActionParams {\n name?: string;\n level?: number;\n maxLevel?: number;\n autoLevel?: boolean;\n baseDifficulty?: number;\n difficultyFac?: number;\n rewardFac?: number;\n successes?: number;\n failures?: number;\n rankGain?: number;\n rankLoss?: number;\n hpLoss?: number;\n hpLost?: number;\n isStealth?: boolean;\n isKill?: boolean;\n count?: number;\n weights?: StatsMultiplier;\n decays?: StatsMultiplier;\n teamCount?: number;\n}\n\nexport class Action implements IAction {\n name = \"\";\n\n // Difficulty scales with level. See getDifficulty() method\n level = 1;\n maxLevel = 1;\n autoLevel = true;\n baseDifficulty = 100;\n difficultyFac = 1.01;\n\n // Rank increase/decrease is affected by this exponent\n rewardFac = 1.02;\n\n successes = 0;\n failures = 0;\n\n // All of these scale with level/difficulty\n rankGain = 0;\n rankLoss = 0;\n hpLoss = 0;\n hpLost = 0;\n\n // Action Category. Current categories are stealth and kill\n isStealth = false;\n isKill = false;\n\n /**\n * Number of this contract remaining, and its growth rate\n * Growth rate is an integer and the count will increase by that integer every \"cycle\"\n */\n count: number = getRandomInt(1e3, 25e3);\n\n // Weighting of each stat in determining action success rate\n weights: StatsMultiplier = {\n hack: 1 / 7,\n str: 1 / 7,\n def: 1 / 7,\n dex: 1 / 7,\n agi: 1 / 7,\n cha: 1 / 7,\n int: 1 / 7,\n };\n // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1)\n decays: StatsMultiplier = {\n hack: 0.9,\n str: 0.9,\n def: 0.9,\n dex: 0.9,\n agi: 0.9,\n cha: 0.9,\n int: 0.9,\n };\n teamCount = 0;\n\n // Base Class for Contracts, Operations, and BlackOps\n constructor(params: IActionParams | null = null) {\n // | null = null\n if (params && params.name) this.name = params.name;\n\n if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);\n if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac;\n\n if (params && params.rewardFac) this.rewardFac = params.rewardFac;\n if (params && params.rankGain) this.rankGain = params.rankGain;\n if (params && params.rankLoss) this.rankLoss = params.rankLoss;\n if (params && params.hpLoss) this.hpLoss = params.hpLoss;\n\n if (params && params.isStealth) this.isStealth = params.isStealth;\n if (params && params.isKill) this.isKill = params.isKill;\n\n if (params && params.count) this.count = params.count;\n\n if (params && params.weights) this.weights = params.weights;\n if (params && params.decays) this.decays = params.decays;\n\n // Check to make sure weights are summed properly\n let sum = 0;\n for (const weight in this.weights) {\n if (this.weights.hasOwnProperty(weight)) {\n sum += this.weights[weight];\n }\n }\n if (sum - 1 >= 10 * Number.EPSILON) {\n throw new Error(\n \"Invalid weights when constructing Action \" +\n this.name +\n \". The weights should sum up to 1. They sum up to :\" +\n 1,\n );\n }\n\n for (const decay in this.decays) {\n if (this.decays.hasOwnProperty(decay)) {\n if (this.decays[decay] > 1) {\n throw new Error(\n \"Invalid decays when constructing \" + \"Action \" + this.name + \". \" + \"Decay value cannot be greater than 1\",\n );\n }\n }\n }\n }\n\n getDifficulty(): number {\n const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level - 1);\n if (isNaN(difficulty)) {\n throw new Error(\"Calculated NaN in Action.getDifficulty()\");\n }\n return difficulty;\n }\n\n /**\n * Tests for success. Should be called when an action has completed\n * @param inst {Bladeburner} - Bladeburner instance\n */\n attempt(inst: IBladeburner): boolean {\n return Math.random() < this.getSuccessChance(inst);\n }\n\n // To be implemented by subtypes\n getActionTimePenalty(): number {\n return 1;\n }\n\n getActionTime(inst: IBladeburner): number {\n const difficulty = this.getDifficulty();\n let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor;\n const skillFac = inst.skillMultipliers.actionTime; // Always < 1\n\n const effAgility = Player.agility * inst.skillMultipliers.effAgi;\n const effDexterity = Player.dexterity * inst.skillMultipliers.effDex;\n const statFac =\n 0.5 *\n (Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) +\n Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) +\n effAgility / BladeburnerConstants.EffAgiLinearFactor +\n effDexterity / BladeburnerConstants.EffDexLinearFactor); // Always > 1\n\n baseTime = Math.max(1, (baseTime * skillFac) / statFac);\n\n return Math.ceil(baseTime * this.getActionTimePenalty());\n }\n\n // For actions that have teams. To be implemented by subtypes.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n getTeamSuccessBonus(inst: IBladeburner): number {\n return 1;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n getActionTypeSkillSuccessBonus(inst: IBladeburner): number {\n return 1;\n }\n\n getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number {\n const city = inst.getCurrentCity();\n if (params.est) {\n return Math.pow(city.popEst / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);\n } else {\n return Math.pow(city.pop / BladeburnerConstants.PopulationThreshold, BladeburnerConstants.PopulationExponent);\n }\n }\n\n getChaosDifficultyBonus(inst: IBladeburner /*, params: ISuccessChanceParams*/): number {\n const city = inst.getCurrentCity();\n if (city.chaos > BladeburnerConstants.ChaosThreshold) {\n const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold);\n const mult = Math.pow(diff, 0.1);\n return mult;\n }\n\n return 1;\n }\n\n getEstSuccessChance(inst: IBladeburner): number[] {\n function clamp(x: number): number {\n return Math.max(0, Math.min(x, 1));\n }\n const est = this.getSuccessChance(inst, { est: true });\n const real = this.getSuccessChance(inst);\n const diff = Math.abs(real - est);\n let low = real - diff;\n let high = real + diff;\n const city = inst.getCurrentCity();\n const r = city.pop / city.popEst;\n\n if (r < 1) low *= r;\n else high *= r;\n return [clamp(low), clamp(high)];\n }\n\n /**\n * @inst - Bladeburner Object\n * @params - options:\n * est (bool): Get success chance estimate instead of real success chance\n */\n getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams = { est: false }): number {\n if (inst == null) {\n throw new Error(\"Invalid Bladeburner instance passed into Action.getSuccessChance\");\n }\n let difficulty = this.getDifficulty();\n let competence = 0;\n for (const stat in this.weights) {\n if (this.weights.hasOwnProperty(stat)) {\n const playerStatLvl = Player.queryStatFromString(stat);\n const key = \"eff\" + stat.charAt(0).toUpperCase() + stat.slice(1);\n let effMultiplier = inst.skillMultipliers[key];\n if (effMultiplier == null) {\n console.error(`Failed to find Bladeburner Skill multiplier for: ${stat}`);\n effMultiplier = 1;\n }\n competence += this.weights[stat] * Math.pow(effMultiplier * playerStatLvl, this.decays[stat]);\n }\n }\n competence *= Player.getIntelligenceBonus(0.75);\n competence *= inst.calculateStaminaPenalty();\n\n competence *= this.getTeamSuccessBonus(inst);\n\n competence *= this.getChaosCompetencePenalty(inst, params);\n difficulty *= this.getChaosDifficultyBonus(inst);\n\n if (this.name == \"Raid\" && inst.getCurrentCity().comms <= 0) {\n return 0;\n }\n\n // Factor skill multipliers into success chance\n competence *= inst.skillMultipliers.successChanceAll;\n competence *= this.getActionTypeSkillSuccessBonus(inst);\n if (this.isStealth) {\n competence *= inst.skillMultipliers.successChanceStealth;\n }\n if (this.isKill) {\n competence *= inst.skillMultipliers.successChanceKill;\n }\n\n // Augmentation multiplier\n competence *= Player.bladeburner_success_chance_mult;\n\n if (isNaN(competence)) {\n throw new Error(\"Competence calculated as NaN in Action.getSuccessChance()\");\n }\n return Math.min(1, competence / difficulty);\n }\n\n getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number {\n return Math.ceil(0.5 * this.maxLevel * (2 * baseSuccessesPerLevel + (this.maxLevel - 1)));\n }\n\n setMaxLevel(baseSuccessesPerLevel: number): void {\n if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) {\n ++this.maxLevel;\n }\n }\n\n toJSON(): any {\n return Generic_toJSON(\"Action\", this);\n }\n\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Action {\n return Generic_fromJSON(Action, value.data);\n }\n}\n\nReviver.constructors.Action = Action;\n","import { loadAliases, loadGlobalAliases, Aliases, GlobalAliases } from \"./Alias\";\nimport { Companies, loadCompanies } from \"./Company/Companies\";\nimport { CONSTANTS } from \"./Constants\";\nimport { Factions, loadFactions } from \"./Faction/Factions\";\nimport { loadAllGangs, AllGangs } from \"./Gang/AllGangs\";\nimport { loadMessages, initMessages, Messages } from \"./Message/MessageHelpers\";\nimport { Player, loadPlayer } from \"./Player\";\nimport { AllServers, loadAllServers } from \"./Server/AllServers\";\nimport { Settings } from \"./Settings/Settings\";\nimport { loadSpecialServerIps, SpecialServerIps } from \"./Server/SpecialServerIps\";\nimport { SourceFileFlags } from \"./SourceFile/SourceFileFlags\";\nimport { loadStockMarket, StockMarket } from \"./StockMarket/StockMarket\";\n\nimport { GameSavedEvents } from \"./ui/React/Snackbar\";\n\nimport { setTimeoutRef } from \"./utils/SetTimeoutRef\";\nimport * as ExportBonus from \"./ExportBonus\";\n\nimport { dialogBoxCreate } from \"./ui/React/DialogBox\";\nimport { Reviver, Generic_toJSON, Generic_fromJSON } from \"./utils/JSONReviver\";\nimport { save } from \"./db\";\n\n/* SaveObject.js\n * Defines the object used to save/load games\n */\n\nclass BitburnerSaveObject {\n PlayerSave = \"\";\n AllServersSave = \"\";\n CompaniesSave = \"\";\n FactionsSave = \"\";\n SpecialServerIpsSave = \"\";\n AliasesSave = \"\";\n GlobalAliasesSave = \"\";\n MessagesSave = \"\";\n StockMarketSave = \"\";\n SettingsSave = \"\";\n VersionSave = \"\";\n AllGangsSave = \"\";\n LastExportBonus = \"\";\n\n getSaveString(): string {\n this.PlayerSave = JSON.stringify(Player);\n\n // Delete all logs from all running scripts\n const TempAllServers = JSON.parse(JSON.stringify(AllServers), Reviver);\n for (const ip in TempAllServers) {\n const server = TempAllServers[ip];\n if (server == null) {\n continue;\n }\n for (let i = 0; i < server.runningScripts.length; ++i) {\n const runningScriptObj = server.runningScripts[i];\n runningScriptObj.logs.length = 0;\n runningScriptObj.logs = [];\n }\n }\n\n this.AllServersSave = JSON.stringify(TempAllServers);\n this.CompaniesSave = JSON.stringify(Companies);\n this.FactionsSave = JSON.stringify(Factions);\n this.SpecialServerIpsSave = JSON.stringify(SpecialServerIps);\n this.AliasesSave = JSON.stringify(Aliases);\n this.GlobalAliasesSave = JSON.stringify(GlobalAliases);\n this.MessagesSave = JSON.stringify(Messages);\n this.StockMarketSave = JSON.stringify(StockMarket);\n this.SettingsSave = JSON.stringify(Settings);\n this.VersionSave = JSON.stringify(CONSTANTS.Version);\n this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);\n if (Player.inGang()) {\n this.AllGangsSave = JSON.stringify(AllGangs);\n }\n const saveString = btoa(unescape(encodeURIComponent(JSON.stringify(this))));\n\n return saveString;\n }\n\n saveGame(): void {\n const saveString = this.getSaveString();\n\n save(saveString)\n .then(() => GameSavedEvents.emit())\n .catch((err) => console.error(err));\n }\n\n exportGame(): void {\n const saveString = this.getSaveString();\n\n // Save file name is based on current timestamp and BitNode\n const epochTime = Math.round(Date.now() / 1000);\n const bn = Player.bitNodeN;\n const filename = `bitburnerSave_BN${bn}x${SourceFileFlags[bn]}_${epochTime}.json`;\n const file = new Blob([saveString], { type: \"text/plain\" });\n if (window.navigator.msSaveOrOpenBlob) {\n // IE10+\n window.navigator.msSaveOrOpenBlob(file, filename);\n } else {\n // Others\n const a = document.createElement(\"a\"),\n url = URL.createObjectURL(file);\n a.href = url;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n setTimeoutRef(function () {\n document.body.removeChild(a);\n window.URL.revokeObjectURL(url);\n }, 0);\n }\n }\n\n toJSON(): any {\n return Generic_toJSON(\"BitburnerSaveObject\", this);\n }\n\n static fromJSON(value: { data: any }): BitburnerSaveObject {\n return Generic_fromJSON(BitburnerSaveObject, value.data);\n }\n}\n\n// Makes necessary changes to the loaded/imported data to ensure\n// the game stills works with new versions\nfunction evaluateVersionCompatibility(ver: string): void {\n // We have to do this because ts won't let us otherwise\n const anyPlayer = Player as any;\n // This version refactored the Company/job-related code\n if (ver <= \"0.41.2\") {\n // Player's company position is now a string\n if (anyPlayer.companyPosition != null && typeof anyPlayer.companyPosition !== \"string\") {\n anyPlayer.companyPosition = anyPlayer.companyPosition.data.positionName;\n if (anyPlayer.companyPosition == null) {\n anyPlayer.companyPosition = \"\";\n }\n }\n\n // The \"companyName\" property of all Companies is renamed to \"name\"\n for (const companyName in Companies) {\n const company: any = Companies[companyName];\n if (company.name == 0 && company.companyName != null) {\n company.name = company.companyName;\n }\n\n if (company.companyPositions instanceof Array) {\n const pos: any = {};\n\n for (let i = 0; i < company.companyPositions.length; ++i) {\n pos[company.companyPositions[i]] = true;\n }\n company.companyPositions = pos;\n }\n }\n }\n\n // This version allowed players to hold multiple jobs\n if (ver < \"0.43.0\") {\n if (anyPlayer.companyName !== \"\" && anyPlayer.companyPosition != null && anyPlayer.companyPosition !== \"\") {\n anyPlayer.jobs[anyPlayer.companyName] = anyPlayer.companyPosition;\n }\n\n delete anyPlayer.companyPosition;\n }\n}\n\nfunction loadGame(saveString: string): boolean {\n if (!saveString) return false;\n saveString = decodeURIComponent(escape(atob(saveString)));\n\n const saveObj = JSON.parse(saveString, Reviver);\n\n loadPlayer(saveObj.PlayerSave);\n loadAllServers(saveObj.AllServersSave);\n loadCompanies(saveObj.CompaniesSave);\n loadFactions(saveObj.FactionsSave);\n loadSpecialServerIps(saveObj.SpecialServerIpsSave);\n\n if (saveObj.hasOwnProperty(\"AliasesSave\")) {\n try {\n loadAliases(saveObj.AliasesSave);\n } catch (e) {\n console.warn(`Could not load Aliases from save`);\n loadAliases(\"\");\n }\n } else {\n console.warn(`Save file did not contain an Aliases property`);\n loadAliases(\"\");\n }\n if (saveObj.hasOwnProperty(\"GlobalAliasesSave\")) {\n try {\n loadGlobalAliases(saveObj.GlobalAliasesSave);\n } catch (e) {\n console.warn(`Could not load GlobalAliases from save`);\n loadGlobalAliases(\"\");\n }\n } else {\n console.warn(`Save file did not contain a GlobalAliases property`);\n loadGlobalAliases(\"\");\n }\n if (saveObj.hasOwnProperty(\"MessagesSave\")) {\n try {\n loadMessages(saveObj.MessagesSave);\n } catch (e) {\n console.warn(`Could not load Messages from save`);\n initMessages();\n }\n } else {\n console.warn(`Save file did not contain a Messages property`);\n initMessages();\n }\n if (saveObj.hasOwnProperty(\"StockMarketSave\")) {\n try {\n loadStockMarket(saveObj.StockMarketSave);\n } catch (e) {\n loadStockMarket(\"\");\n }\n } else {\n loadStockMarket(\"\");\n }\n if (saveObj.hasOwnProperty(\"SettingsSave\")) {\n try {\n Settings.load(saveObj.SettingsSave);\n } catch (e) {\n console.error(\"ERROR: Failed to parse Settings. Re-initing default values\");\n Settings.init();\n }\n } else {\n Settings.init();\n }\n if (saveObj.hasOwnProperty(\"LastExportBonus\")) {\n try {\n ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus));\n } catch (err) {\n ExportBonus.setLastExportBonus(new Date().getTime());\n console.error(\"ERROR: Failed to parse last export bonus Settings \" + err);\n }\n }\n if (saveObj.hasOwnProperty(\"VersionSave\")) {\n try {\n const ver = JSON.parse(saveObj.VersionSave, Reviver);\n evaluateVersionCompatibility(ver);\n\n if (window.location.href.toLowerCase().includes(\"bitburner-beta\")) {\n // Beta branch, always show changes\n createBetaUpdateText();\n } else if (ver != CONSTANTS.Version) {\n createNewUpdateText();\n }\n } catch (e) {\n createNewUpdateText();\n }\n } else {\n createNewUpdateText();\n }\n if (Player.inGang() && saveObj.hasOwnProperty(\"AllGangsSave\")) {\n try {\n loadAllGangs(saveObj.AllGangsSave);\n } catch (e) {\n console.error(\"ERROR: Failed to parse AllGangsSave: \" + e);\n }\n }\n\n return true;\n}\n\nfunction createNewUpdateText(): void {\n dialogBoxCreate(\n \"New update!
\" +\n \"Please report any bugs/issues through the github repository \" +\n \"or the Bitburner subreddit (reddit.com/r/bitburner).

\" +\n CONSTANTS.LatestUpdate,\n );\n}\n\nfunction createBetaUpdateText(): void {\n dialogBoxCreate(\n \"You are playing on the beta environment! This branch of the game \" +\n \"features the latest developments in the game. This version may be unstable.
\" +\n \"Please report any bugs/issues through the github repository (https://github.com/danielyxie/bitburner/issues) \" +\n \"or the Bitburner subreddit (reddit.com/r/bitburner).

\" +\n CONSTANTS.LatestUpdate,\n );\n}\n\nReviver.constructors.BitburnerSaveObject = BitburnerSaveObject;\n\nexport { saveObject, loadGame };\n\nconst saveObject = new BitburnerSaveObject();\n","/**\n * Sleeves are bodies that contain the player's cloned consciousness.\n * The player can use these bodies to perform different tasks synchronously.\n *\n * Each sleeve is its own individual, meaning it has its own stats/exp\n *\n * Sleeves are unlocked in BitNode-10.\n */\nimport { SleeveTaskType } from \"./SleeveTaskTypesEnum\";\n\nimport { IPlayer } from \"../IPlayer\";\nimport { Person, ITaskTracker, createTaskTracker } from \"../Person\";\n\nimport { Augmentation } from \"../../Augmentation/Augmentation\";\n\nimport { BitNodeMultipliers } from \"../../BitNode/BitNodeMultipliers\";\n\nimport { Crime } from \"../../Crime/Crime\";\nimport { Crimes } from \"../../Crime/Crimes\";\n\nimport { Companies } from \"../../Company/Companies\";\nimport { Company } from \"../../Company/Company\";\nimport { CompanyPosition } from \"../../Company/CompanyPosition\";\nimport { CompanyPositions } from \"../../Company/CompanyPositions\";\n\nimport { CONSTANTS } from \"../../Constants\";\n\nimport { Faction } from \"../../Faction/Faction\";\nimport { Factions } from \"../../Faction/Factions\";\nimport { FactionWorkType } from \"../../Faction/FactionWorkTypeEnum\";\n\nimport { CityName } from \"../../Locations/data/CityNames\";\nimport { LocationName } from \"../../Locations/data/LocationNames\";\n\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../../utils/JSONReviver\";\n\nexport class Sleeve extends Person {\n /**\n * Stores the name of the class that the player is currently taking\n */\n className = \"\";\n\n /**\n * Stores the type of crime the sleeve is currently attempting\n * Must match the name of a Crime object\n */\n crimeType = \"\";\n\n /**\n * Enum value for current task\n */\n currentTask: SleeveTaskType = SleeveTaskType.Idle;\n\n /**\n * Contains details about the sleeve's current task. The info stored\n * in this depends on the task type\n *\n * Faction/Company Work: Name of Faction/Company\n * Crime: Money earned if successful\n * Class/Gym: Name of university/gym\n */\n currentTaskLocation = \"\";\n\n /**\n * Maximum amount of time (in milliseconds) that can be spent on current task.\n */\n currentTaskMaxTime = 0;\n\n /**\n * Milliseconds spent on current task\n */\n currentTaskTime = 0;\n\n /**\n * Keeps track of experience earned for other sleeves\n */\n earningsForSleeves: ITaskTracker = createTaskTracker();\n\n /**\n * Keeps track of experience + money earned for player\n */\n earningsForPlayer: ITaskTracker = createTaskTracker();\n\n /**\n * Keeps track of experienced earned in the current task/action\n */\n earningsForTask: ITaskTracker = createTaskTracker();\n\n /**\n * Keeps track of what type of work sleeve is doing for faction, if applicable\n */\n factionWorkType: FactionWorkType = FactionWorkType.None;\n\n /**\n * Records experience gain rate for the current task\n */\n gainRatesForTask: ITaskTracker = createTaskTracker();\n\n /**\n * String that stores what stat the sleeve is training at the gym\n */\n gymStatType = \"\";\n\n /**\n * Keeps track of events/notifications for this sleeve\n */\n logs: string[] = [];\n\n /**\n * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs\n */\n memory = 1;\n\n /**\n * Sleeve shock. Number between 0 and 100\n * Trauma/shock that comes with being in a sleeve. Experience earned\n * is multipled by shock%. This gets applied before synchronization\n *\n * Reputation earned is also multiplied by shock%\n */\n shock = 1;\n\n /**\n * Stored number of game \"loop\" cycles\n */\n storedCycles = 0;\n\n /**\n * Synchronization. Number between 0 and 100\n * When experience is earned by sleeve, both the player and the sleeve get\n * sync% of the experience earned. Other sleeves get sync^2% of exp\n */\n sync = 1;\n\n constructor(p: IPlayer | null = null) {\n super();\n if (p != null) {\n this.shockRecovery(p);\n }\n }\n\n /**\n * Commit crimes\n */\n commitCrime(p: IPlayer, crimeKey: string): boolean {\n const crime: Crime | null = Crimes[crimeKey];\n if (!(crime instanceof Crime)) {\n return false;\n }\n\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n this.gainRatesForTask.hack = crime.hacking_exp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain;\n this.gainRatesForTask.str = crime.strength_exp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain;\n this.gainRatesForTask.def = crime.defense_exp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain;\n this.gainRatesForTask.dex = crime.dexterity_exp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain;\n this.gainRatesForTask.agi = crime.agility_exp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain;\n this.gainRatesForTask.cha = crime.charisma_exp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain;\n this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;\n\n this.currentTaskLocation = String(this.gainRatesForTask.money);\n\n this.crimeType = crimeKey;\n this.currentTaskMaxTime = crime.time;\n this.currentTask = SleeveTaskType.Crime;\n return true;\n }\n\n /**\n * Called to stop the current task\n */\n finishTask(p: IPlayer): ITaskTracker {\n let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves\n\n if (this.currentTask === SleeveTaskType.Crime) {\n // For crimes, all experience and money is gained at the end\n if (this.currentTaskTime >= this.currentTaskMaxTime) {\n const crime: Crime | null = Crimes[this.crimeType];\n if (!(crime instanceof Crime)) {\n console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);\n this.resetTaskStatus();\n return retValue;\n }\n if (Math.random() < crime.successRate(this)) {\n // Success\n const successGainRates: ITaskTracker = createTaskTracker();\n\n const keysForIteration: (keyof ITaskTracker)[] = Object.keys(successGainRates) as (keyof ITaskTracker)[];\n for (let i = 0; i < keysForIteration.length; ++i) {\n const key = keysForIteration[i];\n successGainRates[key] = this.gainRatesForTask[key] * 2;\n }\n retValue = this.gainExperience(p, successGainRates);\n this.gainMoney(p, this.gainRatesForTask);\n\n p.karma -= crime.karma * (this.sync / 100);\n } else {\n retValue = this.gainExperience(p, this.gainRatesForTask);\n }\n\n // Do not reset task to IDLE\n this.currentTaskTime = 0;\n return retValue;\n }\n } else {\n // For other crimes... I dont think anything else needs to be done\n }\n\n this.resetTaskStatus();\n\n return retValue;\n }\n\n /**\n * Earn experience for any stats (supports multiple)\n * This function also handles experience propogating to Player and other sleeves\n */\n gainExperience(p: IPlayer, exp: ITaskTracker, numCycles = 1, fromOtherSleeve = false): ITaskTracker {\n // If the experience is coming from another sleeve, it is not multiplied by anything.\n // Also the player does not earn anything\n if (fromOtherSleeve) {\n if (exp.hack > 0) {\n this.hacking_exp += exp.hack;\n }\n\n if (exp.str > 0) {\n this.strength_exp += exp.str;\n }\n\n if (exp.def > 0) {\n this.defense_exp += exp.def;\n }\n\n if (exp.dex > 0) {\n this.dexterity_exp += exp.dex;\n }\n\n if (exp.agi > 0) {\n this.agility_exp += exp.agi;\n }\n\n if (exp.cha > 0) {\n this.charisma_exp += exp.cha;\n }\n\n return createTaskTracker();\n }\n\n // Experience is first multiplied by shock. Then 'synchronization'\n // is accounted for\n\n const multFac = (this.shock / 100) * (this.sync / 100) * numCycles;\n const pHackExp = exp.hack * multFac;\n const pStrExp = exp.str * multFac;\n const pDefExp = exp.def * multFac;\n const pDexExp = exp.dex * multFac;\n const pAgiExp = exp.agi * multFac;\n const pChaExp = exp.cha * multFac;\n\n // Experience is gained by both this sleeve and player\n if (pHackExp > 0) {\n this.hacking_exp += pHackExp;\n p.gainHackingExp(pHackExp);\n this.earningsForPlayer.hack += pHackExp;\n this.earningsForTask.hack += pHackExp;\n }\n\n if (pStrExp > 0) {\n this.strength_exp += pStrExp;\n p.gainStrengthExp(pStrExp);\n this.earningsForPlayer.str += pStrExp;\n this.earningsForTask.str += pStrExp;\n }\n\n if (pDefExp > 0) {\n this.defense_exp += pDefExp;\n p.gainDefenseExp(pDefExp);\n this.earningsForPlayer.def += pDefExp;\n this.earningsForTask.def += pDefExp;\n }\n\n if (pDexExp > 0) {\n this.dexterity_exp += pDexExp;\n p.gainDexterityExp(pDexExp);\n this.earningsForPlayer.dex += pDexExp;\n this.earningsForTask.dex += pDexExp;\n }\n\n if (pAgiExp > 0) {\n this.agility_exp += pAgiExp;\n p.gainAgilityExp(pAgiExp);\n this.earningsForPlayer.agi += pAgiExp;\n this.earningsForTask.agi += pAgiExp;\n }\n\n if (pChaExp > 0) {\n this.charisma_exp += pChaExp;\n p.gainCharismaExp(pChaExp);\n this.earningsForPlayer.cha += pChaExp;\n this.earningsForTask.cha += pChaExp;\n }\n\n // Record earnings for other sleeves\n this.earningsForSleeves.hack += pHackExp * (this.sync / 100);\n this.earningsForSleeves.str += pStrExp * (this.sync / 100);\n this.earningsForSleeves.def += pDefExp * (this.sync / 100);\n this.earningsForSleeves.dex += pDexExp * (this.sync / 100);\n this.earningsForSleeves.agi += pAgiExp * (this.sync / 100);\n this.earningsForSleeves.cha += pChaExp * (this.sync / 100);\n\n // Return the experience to be gained by other sleeves\n return {\n hack: pHackExp * (this.sync / 100),\n str: pStrExp * (this.sync / 100),\n def: pDefExp * (this.sync / 100),\n dex: pDexExp * (this.sync / 100),\n agi: pAgiExp * (this.sync / 100),\n cha: pChaExp * (this.sync / 100),\n money: 0,\n };\n }\n\n /**\n * Earn money for player\n */\n gainMoney(p: IPlayer, task: ITaskTracker, numCycles = 1): void {\n const gain: number = task.money * numCycles;\n this.earningsForTask.money += gain;\n this.earningsForPlayer.money += gain;\n p.gainMoney(gain);\n p.recordMoneySource(gain, \"sleeves\");\n }\n\n /**\n * Returns the cost of upgrading this sleeve's memory by a certain amount\n */\n getMemoryUpgradeCost(n: number): number {\n const amt = Math.round(n);\n if (amt < 0) {\n return 0;\n }\n\n if (this.memory + amt > 100) {\n return this.getMemoryUpgradeCost(100 - this.memory);\n }\n\n const mult = 1.02;\n const baseCost = 1e12;\n let currCost = 0;\n let currMemory = this.memory - 1;\n for (let i = 0; i < n; ++i) {\n currCost += Math.pow(mult, currMemory);\n ++currMemory;\n }\n\n return currCost * baseCost;\n }\n\n /**\n * Gets reputation gain for the current task\n * Only applicable when working for company or faction\n */\n getRepGain(p: IPlayer): number {\n if (this.currentTask === SleeveTaskType.Faction) {\n let favorMult = 1;\n const fac: Faction | null = Factions[this.currentTaskLocation];\n if (fac != null) {\n favorMult = 1 + fac.favor / 100;\n }\n\n switch (this.factionWorkType) {\n case FactionWorkType.Hacking:\n return this.getFactionHackingWorkRepGain() * (this.shock / 100) * favorMult;\n case FactionWorkType.Field:\n return this.getFactionFieldWorkRepGain() * (this.shock / 100) * favorMult;\n case FactionWorkType.Security:\n return this.getFactionSecurityWorkRepGain() * (this.shock / 100) * favorMult;\n default:\n console.warn(`Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`);\n return 0;\n }\n } else if (this.currentTask === SleeveTaskType.Company) {\n const companyName: string = this.currentTaskLocation;\n const company: Company | null = Companies[companyName];\n if (company == null) {\n console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`);\n return 0;\n }\n\n const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];\n if (companyPosition == null) {\n console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`);\n return 0;\n }\n\n const jobPerformance: number = companyPosition.calculateJobPerformance(\n this.hacking_skill,\n this.strength,\n this.defense,\n this.dexterity,\n this.agility,\n this.charisma,\n );\n const favorMult = 1 + company.favor / 100;\n\n return jobPerformance * this.company_rep_mult * favorMult;\n } else {\n console.warn(`Sleeve.getRepGain() called for invalid task type: ${this.currentTask}`);\n return 0;\n }\n }\n\n installAugmentation(aug: Augmentation): void {\n this.hacking_exp = 0;\n this.strength_exp = 0;\n this.defense_exp = 0;\n this.dexterity_exp = 0;\n this.agility_exp = 0;\n this.charisma_exp = 0;\n this.applyAugmentation(aug);\n this.augmentations.push({ name: aug.name, level: 1 });\n this.updateStatLevels();\n }\n\n log(entry: string): void {\n const MaxLogSize = 50;\n this.logs.push(entry);\n if (this.logs.length > MaxLogSize) {\n this.logs.shift();\n }\n }\n\n /**\n * Called on every sleeve for a Source File prestige\n */\n prestige(p: IPlayer): void {\n // Reset exp\n this.hacking_exp = 0;\n this.strength_exp = 0;\n this.defense_exp = 0;\n this.dexterity_exp = 0;\n this.agility_exp = 0;\n this.charisma_exp = 0;\n\n // Reset task-related stuff\n this.resetTaskStatus();\n this.earningsForSleeves = createTaskTracker();\n this.earningsForPlayer = createTaskTracker();\n this.shockRecovery(p);\n\n // Reset augs and multipliers\n this.augmentations = [];\n this.resetMultipliers();\n\n // Reset sleeve-related stats\n this.shock = 1;\n this.storedCycles = 0;\n this.sync = Math.max(this.memory, 1);\n\n this.logs = [];\n }\n\n /**\n * Process loop\n * Returns an object containing the amount of experience that should be\n * transferred to all other sleeves\n */\n process(p: IPlayer, numCycles = 1): ITaskTracker | null {\n // Only process once every second (5 cycles)\n const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle;\n this.storedCycles += numCycles;\n if (this.storedCycles < CyclesPerSecond) {\n return null;\n }\n\n let time = this.storedCycles * CONSTANTS.MilliPerCycle;\n let cyclesUsed = this.storedCycles;\n cyclesUsed = Math.min(cyclesUsed, 15);\n if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) {\n time = this.currentTaskMaxTime - this.currentTaskTime;\n cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle);\n\n if (time < 0 || cyclesUsed < 0) {\n console.warn(`Sleeve.process() calculated negative cycle usage`);\n time = 0;\n cyclesUsed = 0;\n }\n }\n this.currentTaskTime += time;\n\n // Shock gradually goes towards 100\n this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed);\n\n let retValue: ITaskTracker = createTaskTracker();\n switch (this.currentTask) {\n case SleeveTaskType.Idle:\n break;\n case SleeveTaskType.Class:\n case SleeveTaskType.Gym:\n this.updateTaskGainRates(p);\n retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);\n this.gainMoney(p, this.gainRatesForTask, cyclesUsed);\n break;\n case SleeveTaskType.Faction: {\n retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);\n this.gainMoney(p, this.gainRatesForTask, cyclesUsed);\n\n // Gain faction reputation\n const fac: Faction = Factions[this.currentTaskLocation];\n if (!(fac instanceof Faction)) {\n console.error(`Invalid faction for Sleeve task: ${this.currentTaskLocation}`);\n break;\n }\n\n fac.playerReputation += this.getRepGain(p) * cyclesUsed;\n break;\n }\n case SleeveTaskType.Company: {\n retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);\n this.gainMoney(p, this.gainRatesForTask, cyclesUsed);\n\n const company: Company = Companies[this.currentTaskLocation];\n if (!(company instanceof Company)) {\n console.error(`Invalid company for Sleeve task: ${this.currentTaskLocation}`);\n break;\n }\n\n company.playerReputation += this.getRepGain(p) * cyclesUsed;\n break;\n }\n case SleeveTaskType.Recovery:\n this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed);\n break;\n case SleeveTaskType.Synchro:\n this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed);\n if (this.sync >= 100) this.resetTaskStatus();\n break;\n default:\n break;\n }\n\n if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {\n if (this.currentTask === SleeveTaskType.Crime) {\n retValue = this.finishTask(p);\n } else {\n this.finishTask(p);\n }\n }\n\n this.updateStatLevels();\n\n this.storedCycles -= cyclesUsed;\n\n return retValue;\n }\n\n /**\n * Resets all parameters used to keep information about the current task\n */\n resetTaskStatus(): void {\n this.earningsForTask = createTaskTracker();\n this.gainRatesForTask = createTaskTracker();\n this.currentTask = SleeveTaskType.Idle;\n this.currentTaskTime = 0;\n this.currentTaskMaxTime = 0;\n this.factionWorkType = FactionWorkType.None;\n this.crimeType = \"\";\n this.currentTaskLocation = \"\";\n this.gymStatType = \"\";\n this.className = \"\";\n }\n\n shockRecovery(p: IPlayer): boolean {\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n this.currentTask = SleeveTaskType.Recovery;\n return true;\n }\n\n synchronize(p: IPlayer): boolean {\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n this.currentTask = SleeveTaskType.Synchro;\n return true;\n }\n\n /**\n * Take a course at a university\n */\n takeUniversityCourse(p: IPlayer, universityName: string, className: string): boolean {\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n // Set exp/money multipliers based on which university.\n // Also check that the sleeve is in the right city\n let costMult = 1;\n switch (universityName.toLowerCase()) {\n case LocationName.AevumSummitUniversity.toLowerCase():\n if (this.city !== CityName.Aevum) {\n return false;\n }\n this.currentTaskLocation = LocationName.AevumSummitUniversity;\n costMult = 4;\n break;\n case LocationName.Sector12RothmanUniversity.toLowerCase():\n if (this.city !== CityName.Sector12) {\n return false;\n }\n this.currentTaskLocation = LocationName.Sector12RothmanUniversity;\n costMult = 3;\n break;\n case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():\n if (this.city !== CityName.Volhaven) {\n return false;\n }\n this.currentTaskLocation = LocationName.VolhavenZBInstituteOfTechnology;\n costMult = 5;\n break;\n default:\n return false;\n }\n\n // Set experience/money gains based on class\n switch (className.toLowerCase()) {\n case \"study computer science\":\n break;\n case \"data structures\":\n this.gainRatesForTask.money = -1 * (CONSTANTS.ClassDataStructuresBaseCost * costMult);\n break;\n case \"networks\":\n this.gainRatesForTask.money = -1 * (CONSTANTS.ClassNetworksBaseCost * costMult);\n break;\n case \"algorithms\":\n this.gainRatesForTask.money = -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult);\n break;\n case \"management\":\n this.gainRatesForTask.money = -1 * (CONSTANTS.ClassManagementBaseCost * costMult);\n break;\n case \"leadership\":\n this.gainRatesForTask.money = -1 * (CONSTANTS.ClassLeadershipBaseCost * costMult);\n break;\n default:\n return false;\n }\n\n this.className = className;\n this.currentTask = SleeveTaskType.Class;\n return true;\n }\n\n /**\n * Travel to another City. Costs money from player\n */\n travel(p: IPlayer, newCity: CityName): boolean {\n p.loseMoney(CONSTANTS.TravelCost);\n this.city = newCity;\n\n return true;\n }\n\n tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {\n if (!p.canAfford(aug.startingCost)) {\n return false;\n }\n\n // Verify that this sleeve does not already have that augmentation.\n if (this.augmentations.some((a) => a.name === aug.name)) {\n return false;\n }\n\n p.loseMoney(aug.startingCost);\n this.installAugmentation(aug);\n return true;\n }\n\n updateTaskGainRates(p: IPlayer): void {\n if (this.currentTask === SleeveTaskType.Class) {\n let expMult = 1;\n switch (this.currentTaskLocation.toLowerCase()) {\n case LocationName.AevumSummitUniversity.toLowerCase():\n expMult = 3;\n break;\n case LocationName.Sector12RothmanUniversity.toLowerCase():\n expMult = 2;\n break;\n case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():\n expMult = 4;\n break;\n default:\n return;\n }\n\n const totalExpMult = expMult * p.hashManager.getStudyMult();\n switch (this.className.toLowerCase()) {\n case \"study computer science\":\n this.gainRatesForTask.hack =\n CONSTANTS.ClassStudyComputerScienceBaseExp * totalExpMult * this.hacking_exp_mult;\n break;\n case \"data structures\":\n this.gainRatesForTask.hack = CONSTANTS.ClassDataStructuresBaseExp * totalExpMult * this.hacking_exp_mult;\n break;\n case \"networks\":\n this.gainRatesForTask.hack = CONSTANTS.ClassNetworksBaseExp * totalExpMult * this.hacking_exp_mult;\n break;\n case \"algorithms\":\n this.gainRatesForTask.hack = CONSTANTS.ClassAlgorithmsBaseExp * totalExpMult * this.hacking_exp_mult;\n break;\n case \"management\":\n this.gainRatesForTask.cha = CONSTANTS.ClassManagementBaseExp * totalExpMult * this.charisma_exp_mult;\n break;\n case \"leadership\":\n this.gainRatesForTask.cha = CONSTANTS.ClassLeadershipBaseExp * totalExpMult * this.charisma_exp_mult;\n break;\n default:\n break;\n }\n\n return;\n }\n\n if (this.currentTask === SleeveTaskType.Gym) {\n // Get gym exp multiplier\n let expMult = 1;\n switch (this.currentTaskLocation.toLowerCase()) {\n case LocationName.AevumCrushFitnessGym.toLowerCase():\n expMult = 2;\n break;\n case LocationName.AevumSnapFitnessGym.toLowerCase():\n expMult = 5;\n break;\n case LocationName.Sector12IronGym.toLowerCase():\n expMult = 1;\n break;\n case LocationName.Sector12PowerhouseGym.toLowerCase():\n expMult = 10;\n break;\n case LocationName.VolhavenMilleniumFitnessGym:\n expMult = 4;\n break;\n default:\n return;\n }\n\n // Set stat gain rate\n const baseGymExp = 1;\n const totalExpMultiplier = p.hashManager.getTrainingMult() * expMult;\n const sanitizedStat: string = this.gymStatType.toLowerCase();\n if (sanitizedStat.includes(\"str\")) {\n this.gainRatesForTask.str = baseGymExp * totalExpMultiplier * this.strength_exp_mult;\n } else if (sanitizedStat.includes(\"def\")) {\n this.gainRatesForTask.def = baseGymExp * totalExpMultiplier * this.defense_exp_mult;\n } else if (sanitizedStat.includes(\"dex\")) {\n this.gainRatesForTask.dex = baseGymExp * totalExpMultiplier * this.dexterity_exp_mult;\n } else if (sanitizedStat.includes(\"agi\")) {\n this.gainRatesForTask.agi = baseGymExp * totalExpMultiplier * this.agility_exp_mult;\n }\n\n return;\n }\n\n console.warn(`Sleeve.updateTaskGainRates() called for unexpected task type ${this.currentTask}`);\n }\n\n upgradeMemory(n: number): void {\n if (n < 0) {\n console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);\n return;\n }\n\n this.memory = Math.min(100, Math.round(this.memory + n));\n }\n\n /**\n * Start work for one of the player's companies\n * Returns boolean indicating success\n */\n workForCompany(p: IPlayer, companyName: string): boolean {\n if (!(Companies[companyName] instanceof Company) || p.jobs[companyName] == null) {\n return false;\n }\n\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n const company: Company | null = Companies[companyName];\n const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];\n if (company == null) {\n return false;\n }\n if (companyPosition == null) {\n return false;\n }\n this.gainRatesForTask.money =\n companyPosition.baseSalary *\n company.salaryMultiplier *\n this.work_money_mult *\n BitNodeMultipliers.CompanyWorkMoney;\n this.gainRatesForTask.hack =\n companyPosition.hackingExpGain *\n company.expMultiplier *\n this.hacking_exp_mult *\n BitNodeMultipliers.CompanyWorkExpGain;\n this.gainRatesForTask.str =\n companyPosition.strengthExpGain *\n company.expMultiplier *\n this.strength_exp_mult *\n BitNodeMultipliers.CompanyWorkExpGain;\n this.gainRatesForTask.def =\n companyPosition.defenseExpGain *\n company.expMultiplier *\n this.defense_exp_mult *\n BitNodeMultipliers.CompanyWorkExpGain;\n this.gainRatesForTask.dex =\n companyPosition.dexterityExpGain *\n company.expMultiplier *\n this.dexterity_exp_mult *\n BitNodeMultipliers.CompanyWorkExpGain;\n this.gainRatesForTask.agi =\n companyPosition.agilityExpGain *\n company.expMultiplier *\n this.agility_exp_mult *\n BitNodeMultipliers.CompanyWorkExpGain;\n this.gainRatesForTask.cha =\n companyPosition.charismaExpGain *\n company.expMultiplier *\n this.charisma_exp_mult *\n BitNodeMultipliers.CompanyWorkExpGain;\n\n this.currentTaskLocation = companyName;\n this.currentTask = SleeveTaskType.Company;\n this.currentTaskMaxTime = CONSTANTS.MillisecondsPer8Hours;\n\n return true;\n }\n\n /**\n * Start work for one of the player's factions\n * Returns boolean indicating success\n */\n workForFaction(p: IPlayer, factionName: string, workType: string): boolean {\n if (factionName === \"\") {\n return false;\n }\n if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) {\n return false;\n }\n\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n const factionInfo = Factions[factionName].getInfo();\n\n // Set type of work (hacking/field/security), and the experience gains\n const sanitizedWorkType: string = workType.toLowerCase();\n if (sanitizedWorkType.includes(\"hack\")) {\n if (!factionInfo.offerHackingWork) {\n return false;\n }\n this.factionWorkType = FactionWorkType.Hacking;\n this.gainRatesForTask.hack = 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n } else if (sanitizedWorkType.includes(\"field\")) {\n if (!factionInfo.offerFieldWork) {\n return false;\n }\n this.factionWorkType = FactionWorkType.Field;\n this.gainRatesForTask.hack = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.str = 0.1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.def = 0.1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.dex = 0.1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.agi = 0.1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.cha = 0.1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n } else if (sanitizedWorkType.includes(\"security\")) {\n if (!factionInfo.offerSecurityWork) {\n return false;\n }\n this.factionWorkType = FactionWorkType.Security;\n this.gainRatesForTask.hack = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.str = 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.def = 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.dex = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n this.gainRatesForTask.agi = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;\n } else {\n return false;\n }\n\n this.currentTaskLocation = factionName;\n this.currentTask = SleeveTaskType.Faction;\n this.currentTaskMaxTime = CONSTANTS.MillisecondsPer20Hours;\n\n return true;\n }\n\n /**\n * Begin a gym workout task\n */\n workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean {\n if (this.currentTask !== SleeveTaskType.Idle) {\n this.finishTask(p);\n } else {\n this.resetTaskStatus();\n }\n\n // Set exp/money multipliers based on which university.\n // Also check that the sleeve is in the right city\n let costMult = 1;\n switch (gymName.toLowerCase()) {\n case LocationName.AevumCrushFitnessGym.toLowerCase():\n if (this.city != CityName.Aevum) {\n return false;\n }\n this.currentTaskLocation = LocationName.AevumCrushFitnessGym;\n costMult = 3;\n break;\n case LocationName.AevumSnapFitnessGym.toLowerCase():\n if (this.city != CityName.Aevum) {\n return false;\n }\n this.currentTaskLocation = LocationName.AevumSnapFitnessGym;\n costMult = 10;\n break;\n case LocationName.Sector12IronGym.toLowerCase():\n if (this.city != CityName.Sector12) {\n return false;\n }\n this.currentTaskLocation = LocationName.Sector12IronGym;\n costMult = 1;\n break;\n case LocationName.Sector12PowerhouseGym.toLowerCase():\n if (this.city != CityName.Sector12) {\n return false;\n }\n this.currentTaskLocation = LocationName.Sector12PowerhouseGym;\n costMult = 20;\n break;\n case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():\n if (this.city != CityName.Volhaven) {\n return false;\n }\n this.currentTaskLocation = LocationName.VolhavenMilleniumFitnessGym;\n costMult = 7;\n break;\n default:\n return false;\n }\n\n // Set experience/money gains based on class\n const sanitizedStat: string = stat.toLowerCase();\n\n // Set cost\n this.gainRatesForTask.money = -1 * (CONSTANTS.ClassGymBaseCost * costMult);\n\n // Validate \"stat\" argument\n if (\n !sanitizedStat.includes(\"str\") &&\n !sanitizedStat.includes(\"def\") &&\n !sanitizedStat.includes(\"dex\") &&\n !sanitizedStat.includes(\"agi\")\n ) {\n return false;\n }\n\n this.gymStatType = stat;\n this.currentTask = SleeveTaskType.Gym;\n\n return true;\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"Sleeve\", this);\n }\n\n /**\n * Initiatizes a Sleeve object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Sleeve {\n return Generic_fromJSON(Sleeve, value.data);\n }\n}\n\nReviver.constructors.Sleeve = Sleeve;\n","import { TextFile } from \"../TextFile\";\nimport { Script } from \"../Script/Script\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { IRouter } from \"../ui/Router\";\nimport { Settings } from \"../Settings/Settings\";\nimport { getTimestamp } from \"../utils/helpers/getTimestamp\";\n\nexport class Output {\n text: string;\n color: \"inherit\" | \"initial\" | \"primary\" | \"secondary\" | \"error\" | \"textPrimary\" | \"textSecondary\" | undefined;\n constructor(\n text: string,\n color: \"inherit\" | \"initial\" | \"primary\" | \"secondary\" | \"error\" | \"textPrimary\" | \"textSecondary\" | undefined,\n ) {\n if (Settings.EnableTimestamps) text = \"[\" + getTimestamp() + \"] \" + text;\n this.text = text;\n this.color = color;\n }\n}\n\nexport class Link {\n hostname: string;\n dashes: string;\n constructor(dashes: string, hostname: string) {\n if (Settings.EnableTimestamps) dashes = \"[\" + getTimestamp() + \"] \" + dashes;\n this.hostname = hostname;\n this.dashes = dashes;\n }\n}\n\nexport class TTimer {\n time: number;\n timeLeft: number;\n action: \"h\" | \"b\" | \"a\";\n\n constructor(time: number, action: \"h\" | \"b\" | \"a\") {\n this.time = time;\n this.timeLeft = time;\n this.action = action;\n }\n}\n\nexport interface ITerminal {\n action: TTimer | null;\n\n commandHistory: string[];\n commandHistoryIndex: number;\n\n outputHistory: (Output | Link)[];\n\n // True if a Coding Contract prompt is opened\n contractOpen: boolean;\n\n // Full Path of current directory\n // Excludes the trailing forward slash\n currDir: string;\n\n print(s: string): void;\n error(s: string): void;\n\n clear(): void;\n startAnalyze(): void;\n startBackdoor(player: IPlayer): void;\n startHack(player: IPlayer): void;\n finishHack(router: IRouter, player: IPlayer, cancelled?: boolean): void;\n finishBackdoor(router: IRouter, player: IPlayer, cancelled?: boolean): void;\n finishAnalyze(player: IPlayer, cancelled?: boolean): void;\n finishAction(router: IRouter, player: IPlayer, cancelled?: boolean): void;\n getFilepath(filename: string): string;\n getFile(player: IPlayer, filename: string): Script | TextFile | string | null;\n getScript(player: IPlayer, filename: string): Script | null;\n getTextFile(player: IPlayer, filename: string): TextFile | null;\n getLitFile(player: IPlayer, filename: string): string | null;\n cwd(): string;\n setcwd(dir: string): void;\n runContract(player: IPlayer, name: string): void;\n executeScanAnalyzeCommand(player: IPlayer, depth?: number, all?: boolean): void;\n connectToServer(player: IPlayer, server: string): void;\n executeCommand(router: IRouter, player: IPlayer, command: string): void;\n executeCommands(router: IRouter, player: IPlayer, commands: string): void;\n // If there was any changes, will return true, once.\n process(router: IRouter, player: IPlayer, cycles: number): void;\n prestige(): void;\n getProgressText(): string;\n}\n","import { EventEmitter } from \"../utils/EventEmitter\";\nexport const TerminalEvents = new EventEmitter<[]>();\nexport const TerminalClearEvents = new EventEmitter<[]>();\n","/**\n * The worker agent for running a script instance. Each running script instance\n * has its own underlying WorkerScript object.\n *\n * Note that these objects are not saved and re-loaded when the game is refreshed.\n * Instead, whenever the game is opened, WorkerScripts are re-created from\n * RunningScript objects\n */\nimport { Environment } from \"./Environment\";\nimport { RamCostConstants } from \"./RamCostGenerator\";\n\nimport { RunningScript } from \"../Script/RunningScript\";\nimport { Script } from \"../Script/Script\";\nimport { AllServers } from \"../Server/AllServers\";\nimport { BaseServer } from \"../Server/BaseServer\";\nimport { IMap } from \"../types\";\n\nexport class WorkerScript {\n /**\n * Script's arguments\n */\n args: any[];\n\n /**\n * Copy of the script's code\n */\n code = \"\";\n\n /**\n * Holds the timeoutID (numeric value) for whenever this script is blocked by a\n * timed Netscript function. i.e. Holds the return value of setTimeout()\n */\n delay: number | null = null;\n\n /**\n * Holds the Promise resolve() function for when the script is \"blocked\" by an async op\n */\n delayResolve?: () => void;\n\n /**\n * Stores names of all functions that have logging disabled\n */\n disableLogs: IMap = {};\n\n /**\n * Used for dynamic RAM calculation. Stores names of all functions that have\n * already been checked by this script.\n * TODO: Could probably just combine this with loadedFns?\n */\n dynamicLoadedFns: IMap = {};\n\n /**\n * Tracks dynamic RAM usage\n */\n dynamicRamUsage: number = RamCostConstants.ScriptBaseRamCost;\n\n /**\n * Netscript Environment for this script\n */\n env: Environment;\n\n /**\n * Status message in case of script error. Currently unused I think\n */\n errorMessage = \"\";\n\n /**\n * Used for static RAM calculation. Stores names of all functions that have\n * already been checked by this script\n */\n loadedFns: IMap = {};\n\n /**\n * Filename of script\n */\n name: string;\n\n /**\n * Script's output/return value. Currently not used or implemented\n */\n output = \"\";\n\n /**\n * Process ID. Must be an integer. Used for efficient script\n * killing and removal.\n */\n pid: number;\n\n /**\n * Script's Static RAM usage. Equivalent to underlying script's RAM usage\n */\n ramUsage = 0;\n\n /**\n * Whether or not this workerScript is currently running\n */\n running = false;\n\n /**\n * Reference to underlying RunningScript object\n */\n scriptRef: RunningScript;\n\n /**\n * IP Address on which this script is running\n */\n serverIp: string;\n\n constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => any) {\n this.name = runningScriptObj.filename;\n this.serverIp = runningScriptObj.server;\n\n const sanitizedPid = Math.round(pid);\n if (typeof sanitizedPid !== \"number\" || isNaN(sanitizedPid)) {\n throw new Error(`Invalid PID when constructing WorkerScript: ${pid}`);\n }\n this.pid = sanitizedPid;\n runningScriptObj.pid = sanitizedPid;\n\n // Get the underlying script's code\n const server = AllServers[this.serverIp];\n if (server == null) {\n throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`);\n }\n let found = false;\n for (let i = 0; i < server.scripts.length; ++i) {\n if (server.scripts[i].filename === this.name) {\n found = true;\n this.code = server.scripts[i].code;\n }\n }\n if (!found) {\n throw new Error(`WorkerScript constructed with invalid script filename: ${this.name}`);\n }\n\n this.env = new Environment(null);\n if (typeof nsFuncsGenerator === \"function\") {\n this.env.vars = nsFuncsGenerator(this);\n }\n this.env.set(\"args\", runningScriptObj.args.slice());\n\n this.scriptRef = runningScriptObj;\n this.args = runningScriptObj.args.slice();\n }\n\n /**\n * Returns the Server on which this script is running\n */\n getServer(): BaseServer {\n const server = AllServers[this.serverIp];\n if (server == null) throw new Error(`Script ${this.name} pid ${this.pid} is running on non-existent server?`);\n return server;\n }\n\n /**\n * Returns the Script object for the underlying script.\n * Returns null if it cannot be found (which would be a bug)\n */\n getScript(): Script | null {\n const server = this.getServer();\n for (let i = 0; i < server.scripts.length; ++i) {\n if (server.scripts[i].filename === this.name) {\n return server.scripts[i];\n }\n }\n\n console.error(\n \"Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong\",\n );\n return null;\n }\n\n /**\n * Returns the script with the specified filename on the specified server,\n * or null if it cannot be found\n */\n getScriptOnServer(fn: string, server: BaseServer): Script | null {\n if (server == null) {\n server = this.getServer();\n }\n\n for (let i = 0; i < server.scripts.length; ++i) {\n if (server.scripts[i].filename === fn) {\n return server.scripts[i];\n }\n }\n\n return null;\n }\n\n shouldLog(fn: string): boolean {\n return this.disableLogs[fn] == null;\n }\n\n log(func: string, txt: string): void {\n if (this.shouldLog(func)) {\n if (func && txt) {\n this.scriptRef.log(`${func}: ${txt}`);\n } else if (func) {\n this.scriptRef.log(func);\n } else {\n this.scriptRef.log(txt);\n }\n }\n }\n\n print(txt: string): void {\n this.scriptRef.log(txt);\n }\n}\n","export class DarkWebItem {\n program: string;\n price: number;\n description: string;\n\n constructor(program: string, price: number, description: string) {\n this.program = program;\n this.price = price;\n this.description = description;\n }\n}\n","/**\n * Helper functions for determine whether Limit and Stop orders should\n * be executed (and executing them)\n */\nimport { buyStock, sellStock, shortStock, sellShort } from \"./BuyingAndSelling\";\nimport { IOrderBook } from \"./IOrderBook\";\nimport { IStockMarket } from \"./IStockMarket\";\nimport { Order } from \"./Order\";\nimport { Stock } from \"./Stock\";\n\nimport { OrderTypes } from \"./data/OrderTypes\";\nimport { PositionTypes } from \"./data/PositionTypes\";\n\nimport { IMap } from \"../types\";\n\nimport { numeralWrapper } from \"../ui/numeralFormat\";\nimport { Money } from \"../ui/React/Money\";\n\nimport { dialogBoxCreate } from \"../ui/React/DialogBox\";\n\nimport * as React from \"react\";\n\nexport interface IProcessOrderRefs {\n stockMarket: IStockMarket;\n symbolToStockMap: IMap;\n}\n\n/**\n * Search for all orders of a specific type and execute them if appropriate\n * @param {Stock} stock - Stock for which orders should be processed\n * @param {OrderTypes} orderType - Type of order to check (Limit/Stop buy/sell)\n * @param {PositionTypes} posType - Long or short\n * @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function\n */\nexport function processOrders(\n stock: Stock,\n orderType: OrderTypes,\n posType: PositionTypes,\n refs: IProcessOrderRefs,\n): void {\n const orderBook = refs.stockMarket[\"Orders\"];\n if (orderBook == null) {\n const orders: IOrderBook = {};\n for (const name in refs.stockMarket) {\n const stock = refs.stockMarket[name];\n if (!(stock instanceof Stock)) {\n continue;\n }\n orders[stock.symbol] = [];\n }\n refs.stockMarket[\"Orders\"] = orders;\n return; // Newly created, so no orders to process\n }\n let stockOrders = orderBook[stock.symbol];\n if (stockOrders == null || !(stockOrders.constructor === Array)) {\n console.error(`Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`);\n stockOrders = [];\n return;\n }\n\n for (const order of stockOrders) {\n if (order.type === orderType && order.pos === posType) {\n switch (order.type) {\n case OrderTypes.LimitBuy:\n if (order.pos === PositionTypes.Long && stock.price <= order.price) {\n executeOrder(/*66*/ order, refs);\n } else if (order.pos === PositionTypes.Short && stock.price >= order.price) {\n executeOrder(/*66*/ order, refs);\n }\n break;\n case OrderTypes.LimitSell:\n if (order.pos === PositionTypes.Long && stock.price >= order.price) {\n executeOrder(/*66*/ order, refs);\n } else if (order.pos === PositionTypes.Short && stock.price <= order.price) {\n executeOrder(/*66*/ order, refs);\n }\n break;\n case OrderTypes.StopBuy:\n if (order.pos === PositionTypes.Long && stock.price >= order.price) {\n executeOrder(/*66*/ order, refs);\n } else if (order.pos === PositionTypes.Short && stock.price <= order.price) {\n executeOrder(/*66*/ order, refs);\n }\n break;\n case OrderTypes.StopSell:\n if (order.pos === PositionTypes.Long && stock.price <= order.price) {\n executeOrder(/*66*/ order, refs);\n } else if (order.pos === PositionTypes.Short && stock.price >= order.price) {\n executeOrder(/*66*/ order, refs);\n }\n break;\n default:\n console.warn(`Invalid order type: ${order.type}`);\n return;\n }\n }\n }\n}\n\n/**\n * Execute a Stop or Limit Order.\n * @param {Order} order - Order being executed\n * @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function\n */\nfunction executeOrder(order: Order, refs: IProcessOrderRefs): void {\n const stock = refs.symbolToStockMap[order.stockSymbol];\n if (!(stock instanceof Stock)) {\n console.error(`Could not find stock for this order: ${order.stockSymbol}`);\n return;\n }\n const stockMarket = refs.stockMarket;\n const orderBook = stockMarket[\"Orders\"];\n const stockOrders = orderBook[stock.symbol];\n\n // When orders are executed, the buying and selling functions shouldn't\n // emit popup dialog boxes. This options object configures the functions for that\n const opts = {\n suppressDialog: true,\n };\n\n let res = true;\n let isBuy = false;\n switch (order.type) {\n case OrderTypes.LimitBuy:\n case OrderTypes.StopBuy:\n isBuy = true;\n if (order.pos === PositionTypes.Long) {\n res = buyStock(stock, order.shares, null, opts) && res;\n } else if (order.pos === PositionTypes.Short) {\n res = shortStock(stock, order.shares, null, opts) && res;\n }\n break;\n case OrderTypes.LimitSell:\n case OrderTypes.StopSell:\n if (order.pos === PositionTypes.Long) {\n res = sellStock(stock, order.shares, null, opts) && res;\n } else if (order.pos === PositionTypes.Short) {\n res = sellShort(stock, order.shares, null, opts) && res;\n }\n break;\n default:\n console.warn(`Invalid order type: ${order.type}`);\n return;\n }\n\n // Position type, for logging/message purposes\n const pos = order.pos === PositionTypes.Long ? \"Long\" : \"Short\";\n\n if (res) {\n for (let i = 0; i < stockOrders.length; ++i) {\n if (order == stockOrders[i]) {\n stockOrders.splice(i, 1);\n dialogBoxCreate(\n <>\n {order.type} for {stock.symbol} @ ({pos}) was filled (\n {numeralWrapper.formatShares(Math.round(order.shares))} shares)\n ,\n );\n return;\n }\n }\n\n console.error(\"Could not find the following Order in Order Book: \");\n console.error(order);\n } else {\n if (isBuy) {\n dialogBoxCreate(\n <>\n Failed to execute {order.type} for {stock.symbol} @ ({pos}). This is most likely\n because you do not have enough money or the order would exceed the stock's maximum number of shares\n ,\n );\n }\n }\n}\n","import { CorporationState } from \"./CorporationState\";\nimport { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from \"./data/CorporationUnlockUpgrades\";\nimport { CorporationUpgrade, CorporationUpgrades } from \"./data/CorporationUpgrades\";\nimport { Warehouse } from \"./Warehouse\";\nimport { CorporationConstants } from \"./data/Constants\";\nimport { Industry } from \"./Industry\";\n\nimport { BitNodeMultipliers } from \"../BitNode/BitNodeMultipliers\";\nimport { showLiterature } from \"../Literature/LiteratureHelpers\";\nimport { LiteratureNames } from \"../Literature/data/LiteratureNames\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\n\nimport { dialogBoxCreate } from \"../ui/React/DialogBox\";\nimport { Reviver, Generic_toJSON, Generic_fromJSON } from \"../utils/JSONReviver\";\nimport { isString } from \"../utils/helpers/isString\";\n\n// UI Related Imports\n\nimport Decimal from \"decimal.js\";\n\ninterface IParams {\n name?: string;\n}\n\nexport class Corporation {\n name = \"The Corporation\";\n\n //A division/business sector is represented by the object:\n divisions: Industry[] = [];\n\n //Financial stats\n funds = new Decimal(150e9);\n revenue = new Decimal(0);\n expenses = new Decimal(0);\n fundingRound = 0;\n public = false; //Publicly traded\n totalShares = CorporationConstants.INITIALSHARES; // Total existing shares\n numShares = CorporationConstants.INITIALSHARES; // Total shares owned by player\n shareSalesUntilPriceUpdate = CorporationConstants.SHARESPERPRICEUPDATE;\n shareSaleCooldown = 0; // Game cycles until player can sell shares again\n issueNewSharesCooldown = 0; // Game cycles until player can issue shares again\n dividendPercentage = 0;\n dividendTaxPercentage = 50;\n issuedShares = 0;\n sharePrice = 0;\n storedCycles = 0;\n\n unlockUpgrades: number[];\n upgrades: number[];\n upgradeMultipliers: number[];\n\n state = new CorporationState();\n\n constructor(params: IParams = {}) {\n this.name = params.name ? params.name : \"The Corporation\";\n const numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length;\n const numUpgrades = Object.keys(CorporationUpgrades).length;\n this.unlockUpgrades = Array(numUnlockUpgrades).fill(0);\n this.upgrades = Array(numUpgrades).fill(0);\n this.upgradeMultipliers = Array(numUpgrades).fill(1);\n }\n\n addFunds(amt: number): void {\n if (!isFinite(amt)) {\n console.error(\"Trying to add invalid amount of funds. Report to a developper.\");\n return;\n }\n this.funds = this.funds.plus(amt);\n }\n\n getState(): string {\n return this.state.getState();\n }\n\n storeCycles(numCycles = 1): void {\n this.storedCycles += numCycles;\n }\n\n process(player: IPlayer): void {\n if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) {\n const state = this.getState();\n const marketCycles = 1;\n const gameCycles = marketCycles * CorporationConstants.CyclesPerIndustryStateCycle;\n this.storedCycles -= gameCycles;\n\n this.divisions.forEach((ind) => {\n ind.process(marketCycles, state, this);\n });\n\n // Process cooldowns\n if (this.shareSaleCooldown > 0) {\n this.shareSaleCooldown -= gameCycles;\n }\n if (this.issueNewSharesCooldown > 0) {\n this.issueNewSharesCooldown -= gameCycles;\n }\n\n //At the start of a new cycle, calculate profits from previous cycle\n if (state === \"START\") {\n this.revenue = new Decimal(0);\n this.expenses = new Decimal(0);\n this.divisions.forEach((ind) => {\n if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) {\n return;\n }\n if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) {\n return;\n }\n this.revenue = this.revenue.plus(ind.lastCycleRevenue);\n this.expenses = this.expenses.plus(ind.lastCycleExpenses);\n });\n const profit = this.revenue.minus(this.expenses);\n const cycleProfit = profit.times(marketCycles * CorporationConstants.SecsPerMarketCycle);\n if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {\n dialogBoxCreate(\n \"There was an error calculating your Corporations funds and they got reset to 0. \" +\n \"This is a bug. Please report to game developer.

\" +\n \"(Your funds have been set to $150b for the inconvenience)\",\n );\n this.funds = new Decimal(150e9);\n }\n\n // Process dividends\n if (this.dividendPercentage > 0 && cycleProfit > 0) {\n // Validate input again, just to be safe\n if (\n isNaN(this.dividendPercentage) ||\n this.dividendPercentage < 0 ||\n this.dividendPercentage > CorporationConstants.DividendMaxPercentage * 100\n ) {\n console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`);\n } else {\n const totalDividends = (this.dividendPercentage / 100) * cycleProfit;\n const retainedEarnings = cycleProfit - totalDividends;\n const dividendsPerShare = totalDividends / this.totalShares;\n const profit = this.numShares * dividendsPerShare * (1 - this.dividendTaxPercentage / 100);\n player.gainMoney(profit);\n player.recordMoneySource(profit, \"corporation\");\n this.addFunds(retainedEarnings);\n }\n } else {\n this.addFunds(cycleProfit);\n }\n\n this.updateSharePrice();\n }\n\n this.state.nextState();\n }\n }\n\n determineValuation(): number {\n let val,\n profit = this.revenue.minus(this.expenses).toNumber();\n if (this.public) {\n // Account for dividends\n if (this.dividendPercentage > 0) {\n profit *= (100 - this.dividendPercentage) / 100;\n }\n\n val = this.funds.toNumber() + profit * 85e3;\n val *= Math.pow(1.1, this.divisions.length);\n val = Math.max(val, 0);\n } else {\n val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation\n if (profit > 0) {\n val += profit * 315e3;\n val *= Math.pow(1.1, this.divisions.length);\n } else {\n val = 10e9 * Math.pow(1.1, this.divisions.length);\n }\n val -= val % 1e6; //Round down to nearest millionth\n }\n return val * BitNodeMultipliers.CorporationValuation;\n }\n\n getTargetSharePrice(): number {\n // Note: totalShares - numShares is not the same as issuedShares because\n // issuedShares does not account for private investors\n return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1);\n }\n\n updateSharePrice(): void {\n const targetPrice = this.getTargetSharePrice();\n if (this.sharePrice <= targetPrice) {\n this.sharePrice *= 1 + Math.random() * 0.01;\n } else {\n this.sharePrice *= 1 - Math.random() * 0.01;\n }\n if (this.sharePrice <= 0.01) {\n this.sharePrice = 0.01;\n }\n }\n\n immediatelyUpdateSharePrice(): void {\n this.sharePrice = this.getTargetSharePrice();\n }\n\n // Calculates how much money will be made and what the resulting stock price\n // will be when the player sells his/her shares\n // @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property]\n calculateShareSale(numShares: number): [number, number, number] {\n let sharesTracker = numShares;\n let sharesUntilUpdate = this.shareSalesUntilPriceUpdate;\n let sharePrice = this.sharePrice;\n let sharesSold = 0;\n let profit = 0;\n\n const maxIterations = Math.ceil(numShares / CorporationConstants.SHARESPERPRICEUPDATE);\n if (isNaN(maxIterations) || maxIterations > 10e6) {\n console.error(\n `Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`,\n );\n return [0, 0, 0];\n }\n\n for (let i = 0; i < maxIterations; ++i) {\n if (sharesTracker < sharesUntilUpdate) {\n profit += sharePrice * sharesTracker;\n sharesUntilUpdate -= sharesTracker;\n break;\n } else {\n profit += sharePrice * sharesUntilUpdate;\n sharesUntilUpdate = CorporationConstants.SHARESPERPRICEUPDATE;\n sharesTracker -= sharesUntilUpdate;\n sharesSold += sharesUntilUpdate;\n\n // Calculate what new share price would be\n sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares));\n }\n }\n\n return [profit, sharePrice, sharesUntilUpdate];\n }\n\n convertCooldownToString(cd: number): string {\n // The cooldown value is based on game cycles. Convert to a simple string\n const seconds = cd / 5;\n\n const SecondsPerMinute = 60;\n const SecondsPerHour = 3600;\n\n if (seconds > SecondsPerHour) {\n return `${Math.floor(seconds / SecondsPerHour)} hour(s)`;\n } else if (seconds > SecondsPerMinute) {\n return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`;\n } else {\n return `${Math.floor(seconds)} second(s)`;\n }\n }\n\n //One time upgrades that unlock new features\n unlock(upgrade: CorporationUnlockUpgrade): void {\n const upgN = upgrade[0],\n price = upgrade[1];\n while (this.unlockUpgrades.length <= upgN) {\n this.unlockUpgrades.push(0);\n }\n if (this.funds.lt(price)) {\n dialogBoxCreate(\"You don't have enough funds to unlock this!\");\n return;\n }\n this.unlockUpgrades[upgN] = 1;\n this.funds = this.funds.minus(price);\n\n // Apply effects for one-time upgrades\n if (upgN === 5) {\n this.dividendTaxPercentage -= 5;\n } else if (upgN === 6) {\n this.dividendTaxPercentage -= 10;\n }\n }\n\n //Levelable upgrades\n upgrade(upgrade: CorporationUpgrade): void {\n const upgN = upgrade[0],\n basePrice = upgrade[1],\n priceMult = upgrade[2],\n upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive)\n while (this.upgrades.length <= upgN) {\n this.upgrades.push(0);\n }\n while (this.upgradeMultipliers.length <= upgN) {\n this.upgradeMultipliers.push(1);\n }\n const totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]);\n if (this.funds.lt(totalCost)) {\n dialogBoxCreate(\"You don't have enough funds to purchase this!\");\n return;\n }\n ++this.upgrades[upgN];\n this.funds = this.funds.minus(totalCost);\n\n //Increase upgrade multiplier\n this.upgradeMultipliers[upgN] = 1 + this.upgrades[upgN] * upgradeAmt;\n\n //If storage size is being updated, update values in Warehouse objects\n if (upgN === 1) {\n for (let i = 0; i < this.divisions.length; ++i) {\n const industry = this.divisions[i];\n for (const city in industry.warehouses) {\n const warehouse = industry.warehouses[city];\n if (warehouse === 0) continue;\n if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) {\n warehouse.updateSize(this, industry);\n }\n }\n }\n }\n }\n\n getProductionMultiplier(): number {\n const mult = this.upgradeMultipliers[0];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getStorageMultiplier(): number {\n const mult = this.upgradeMultipliers[1];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getDreamSenseGain(): number {\n const gain = this.upgradeMultipliers[2] - 1;\n return gain <= 0 ? 0 : gain;\n }\n\n getAdvertisingMultiplier(): number {\n const mult = this.upgradeMultipliers[3];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getEmployeeCreMultiplier(): number {\n const mult = this.upgradeMultipliers[4];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getEmployeeChaMultiplier(): number {\n const mult = this.upgradeMultipliers[5];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getEmployeeIntMultiplier(): number {\n const mult = this.upgradeMultipliers[6];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getEmployeeEffMultiplier(): number {\n const mult = this.upgradeMultipliers[7];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getSalesMultiplier(): number {\n const mult = this.upgradeMultipliers[8];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n getScientificResearchMultiplier(): number {\n const mult = this.upgradeMultipliers[9];\n if (isNaN(mult) || mult < 1) {\n return 1;\n } else {\n return mult;\n }\n }\n\n // Adds the Corporation Handbook (Starter Guide) to the player's home computer.\n // This is a lit file that gives introductory info to the player\n // This occurs when the player clicks the \"Getting Started Guide\" button on the overview panel\n getStarterGuide(player: IPlayer): void {\n // Check if player already has Corporation Handbook\n const homeComp = player.getHomeComputer();\n let hasHandbook = false;\n const handbookFn = LiteratureNames.CorporationManagementHandbook;\n for (let i = 0; i < homeComp.messages.length; ++i) {\n if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) {\n hasHandbook = true;\n break;\n }\n }\n\n if (!hasHandbook) {\n homeComp.messages.push(handbookFn);\n }\n showLiterature(handbookFn);\n return;\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"Corporation\", this);\n }\n\n /**\n * Initiatizes a Corporation object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Corporation {\n return Generic_fromJSON(Corporation, value.data);\n }\n}\n\nReviver.constructors.Corporation = Corporation;\n","/**\n * Options specific to creating an anchor (\"\") element.\n */\ninterface ICreateElementAnchorOptions {\n href?: string;\n target?: string;\n text?: string;\n}\n\n/**\n * Options specific to creating an input (\"\") element.\n */\ninterface ICreateElementInputOptions {\n checked?: boolean;\n max?: string;\n maxLength?: number;\n min?: string;\n name?: string;\n pattern?: string;\n placeholder?: string;\n step?: string;\n type?: string;\n value?: string;\n}\n\n/**\n * Options specific to creating a label (\"killing all scripts\n \n \n )}\n \n );\n}\n","/**\n * Checks that a variable is a valid number. A valid number\n * must be a \"number\" type and cannot be NaN\n */\nexport function isValidNumber(n: number): boolean {\n return typeof n === \"number\" && !isNaN(n);\n}\n","import { getRandomInt } from \"../../utils/helpers/getRandomInt\";\n\nexport const Growths: {\n [key: string]: (() => number) | undefined;\n [\"Tracking\"]: () => number;\n [\"Bounty Hunter\"]: () => number;\n [\"Retirement\"]: () => number;\n [\"Investigation\"]: () => number;\n [\"Undercover Operation\"]: () => number;\n [\"Sting Operation\"]: () => number;\n [\"Raid\"]: () => number;\n [\"Stealth Retirement Operation\"]: () => number;\n [\"Assassination\"]: () => number;\n} = {\n Tracking: () => getRandomInt(5, 75) / 10,\n \"Bounty Hunter\": () => getRandomInt(5, 75) / 10,\n Retirement: () => getRandomInt(5, 75) / 10,\n Investigation: () => getRandomInt(10, 40) / 10,\n \"Undercover Operation\": () => getRandomInt(10, 40) / 10,\n \"Sting Operation\": () => getRandomInt(3, 40) / 10,\n Raid: () => getRandomInt(2, 40) / 10,\n \"Stealth Retirement Operation\": () => getRandomInt(1, 20) / 10,\n Assassination: () => getRandomInt(1, 20) / 10,\n};\n","/**\n * Represents a Limit or Buy Order on the stock market. Does not represent\n * a Market Order since those are just executed immediately\n */\nimport { OrderTypes } from \"./data/OrderTypes\";\nimport { PositionTypes } from \"./data/PositionTypes\";\n\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../utils/JSONReviver\";\n\nexport class Order {\n readonly pos: PositionTypes;\n readonly price: number;\n shares: number;\n readonly stockSymbol: string;\n readonly type: OrderTypes;\n\n constructor(\n stockSymbol = \"\",\n shares = 0,\n price = 0,\n typ: OrderTypes = OrderTypes.LimitBuy,\n pos: PositionTypes = PositionTypes.Long,\n ) {\n // Validate arguments\n let invalidArgs = false;\n if (typeof shares !== \"number\" || typeof price !== \"number\") {\n invalidArgs = true;\n }\n if (isNaN(shares) || isNaN(price)) {\n invalidArgs = true;\n }\n if (typeof stockSymbol !== \"string\") {\n invalidArgs = true;\n }\n if (invalidArgs) {\n throw new Error(`Invalid constructor paramters for Order`);\n }\n\n this.stockSymbol = stockSymbol;\n this.shares = shares;\n this.price = price;\n this.type = typ;\n this.pos = pos;\n }\n\n /**\n * Serialize the Order to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"Order\", this);\n }\n\n /**\n * Initializes a Order from a JSON save state\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Order {\n return Generic_fromJSON(Order, value.data);\n }\n}\n\nReviver.constructors.Order = Order;\n","/**\n * Class representing a visitable location in the world\n */\nimport { CityName } from \"./data/CityNames\";\nimport { LocationName } from \"./data/LocationNames\";\nimport { LocationType } from \"./LocationTypeEnum\";\n\ninterface IInfiltrationMetadata {\n maxClearanceLevel: number;\n startingSecurityLevel: number;\n}\n\nexport interface IConstructorParams {\n city?: CityName | null;\n costMult?: number;\n expMult?: number;\n infiltrationData?: IInfiltrationMetadata;\n name?: LocationName;\n types?: LocationType[];\n techVendorMaxRam?: number;\n techVendorMinRam?: number;\n}\n\nexport class Location {\n /**\n * Name of city this location is in. If this property is null, it means this i\n * is a generic location that is available in all cities\n */\n city: CityName | null = null;\n\n /**\n * Cost multiplier that influences how expensive a gym/university is\n */\n costMult = 0;\n\n /**\n * Exp multiplier that influences how effective a gym/university is\n */\n expMult = 0;\n\n /**\n * Companies can be infiltrated. This contains the data required for that\n * infiltration event\n */\n infiltrationData?: IInfiltrationMetadata;\n\n /**\n * Identifier for location\n */\n name: LocationName = LocationName.Void;\n\n /**\n * List of what type(s) this location is. A location can be multiple types\n * (e.g. company and tech vendor)\n */\n types: LocationType[] = [];\n\n /**\n * Tech vendors allow you to purchase servers.\n * This property defines the max RAM server you can purchase from this vendor\n */\n techVendorMaxRam = 0;\n\n /**\n * Tech vendors allow you to purchase servers.\n * This property defines the max RAM server you can purchase from this vendor\n */\n techVendorMinRam = 0;\n\n constructor(p: IConstructorParams) {\n if (p.city) {\n this.city = p.city;\n }\n if (p.costMult) {\n this.costMult = p.costMult;\n }\n if (p.expMult) {\n this.expMult = p.expMult;\n }\n if (p.infiltrationData) {\n this.infiltrationData = p.infiltrationData;\n }\n if (p.name) {\n this.name = p.name;\n }\n if (p.types) {\n this.types = p.types;\n }\n if (p.techVendorMaxRam) {\n this.techVendorMaxRam = p.techVendorMaxRam;\n }\n if (p.techVendorMinRam) {\n this.techVendorMinRam = p.techVendorMinRam;\n }\n }\n}\n","/**\n * Creates a dropdown (select HTML element) with server hostnames as options\n *\n * Configurable to only contain certain types of servers\n */\nimport React from \"react\";\nimport { AllServers } from \"../../Server/AllServers\";\nimport { Server } from \"../../Server/Server\";\n\nimport { HacknetServer } from \"../../Hacknet/HacknetServer\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport MenuItem from \"@mui/material/MenuItem\";\n\n// TODO make this an enum when this gets converted to TypeScript\nexport const ServerType = {\n All: 0,\n Foreign: 1, // Hackable, non-owned servers\n Owned: 2, // Home Computer, Purchased Servers, and Hacknet Servers\n Purchased: 3, // Everything from Owned except home computer\n};\n\ninterface IProps {\n serverType: number;\n onChange: (event: SelectChangeEvent) => void;\n value: string;\n}\n\nexport function ServerDropdown(props: IProps): React.ReactElement {\n /**\n * Checks if the server should be shown in the dropdown menu, based on the\n * 'serverType' property\n */\n function isValidServer(s: Server | HacknetServer): boolean {\n const purchased = s instanceof Server && s.purchasedByPlayer;\n const type = props.serverType;\n switch (type) {\n case ServerType.All:\n return true;\n case ServerType.Foreign:\n return s.hostname !== \"home\" && !purchased;\n case ServerType.Owned:\n return purchased || s instanceof HacknetServer || s.hostname === \"home\";\n case ServerType.Purchased:\n return purchased || s instanceof HacknetServer;\n default:\n console.warn(`Invalid ServerType specified for ServerDropdown component: ${type}`);\n return false;\n }\n }\n\n const servers = [];\n for (const serverName in AllServers) {\n const server = AllServers[serverName];\n if (isValidServer(server)) {\n servers.push(\n \n {server.hostname}\n ,\n );\n }\n }\n\n return (\n \n );\n}\n","/**\n * React Component for displaying a location's UI\n *\n * This is a \"router\" component of sorts, meaning it deduces the type of\n * location that is being rendered and then creates the proper component(s) for that.\n */\nimport * as React from \"react\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\nimport { CompanyLocation } from \"./CompanyLocation\";\nimport { GymLocation } from \"./GymLocation\";\nimport { HospitalLocation } from \"./HospitalLocation\";\nimport { SlumsLocation } from \"./SlumsLocation\";\nimport { SpecialLocation } from \"./SpecialLocation\";\nimport { TechVendorLocation } from \"./TechVendorLocation\";\nimport { TravelAgencyRoot } from \"./TravelAgencyRoot\";\nimport { UniversityLocation } from \"./UniversityLocation\";\nimport { CasinoLocation } from \"./CasinoLocation\";\n\nimport { Location } from \"../Location\";\nimport { LocationType } from \"../LocationTypeEnum\";\n\nimport { Settings } from \"../../Settings/Settings\";\n\nimport { SpecialServerIps } from \"../../Server/SpecialServerIps\";\nimport { getServer, isBackdoorInstalled } from \"../../Server/ServerHelpers\";\n\nimport { CorruptableText } from \"../../ui/React/CorruptableText\";\nimport { use } from \"../../ui/Context\";\n\ntype IProps = {\n loc: Location;\n};\n\nexport function GenericLocation({ loc }: IProps): React.ReactElement {\n const router = use.Router();\n const player = use.Player();\n /**\n * Determine what needs to be rendered for this location based on the locations\n * type. Returns an array of React components that should be rendered\n */\n function getLocationSpecificContent(): React.ReactNode[] {\n const content: React.ReactNode[] = [];\n\n if (loc.types.includes(LocationType.Company)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.Gym)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.Hospital)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.Slums)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.Special)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.TechVendor)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.TravelAgency)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.University)) {\n content.push();\n }\n\n if (loc.types.includes(LocationType.Casino)) {\n content.push();\n }\n\n return content;\n }\n\n const locContent: React.ReactNode[] = getLocationSpecificContent();\n const ip = SpecialServerIps.getIp(loc.name);\n const server = getServer(ip);\n const backdoorInstalled = server !== null && isBackdoorInstalled(server);\n\n return (\n <>\n \n \n {backdoorInstalled && !Settings.DisableTextEffects ? : loc.name}\n \n {locContent}\n \n );\n}\n","import React, { FC } from \"react\";\nimport { Card, Suit } from \"./Card\";\n\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport Paper from \"@mui/material/Paper\";\n\ntype Props = {\n card: Card;\n hidden?: boolean;\n};\n\nconst useStyles = makeStyles(() =>\n createStyles({\n card: {\n padding: \"10px\",\n border: \"solid 1px #808080\",\n backgroundColor: \"white\",\n display: \"inline-block\",\n borderRadius: \"10px\",\n fontSize: \"18.5px\",\n textAlign: \"center\",\n margin: \"3px\",\n fontWeight: \"bold\",\n },\n red: {\n color: \"red\",\n },\n\n black: {\n color: \"black\",\n },\n value: {\n fontSize: \"20px\",\n fontFamily: \"sans-serif\",\n },\n }),\n);\n\nexport const ReactCard: FC = ({ card, hidden }) => {\n const classes = useStyles();\n let suit: React.ReactNode;\n switch (card.suit) {\n case Suit.Clubs:\n suit = ;\n break;\n case Suit.Diamonds:\n suit = ;\n break;\n case Suit.Hearts:\n suit = ;\n break;\n case Suit.Spades:\n suit = ;\n break;\n default:\n throw new Error(`MissingCaseException: ${card.suit}`);\n }\n return (\n \n <>\n {hidden ? \" - \" : card.formatValue()}\n {hidden ? \" - \" : suit}\n \n \n );\n};\n","export type Position = {\n row: number;\n column: number;\n};\n\nclass PositionTracker {\n positions: Map;\n\n constructor() {\n this.positions = new Map();\n }\n\n saveCursor(filename: string, pos: Position): void {\n this.positions.set(filename, pos);\n }\n\n getCursor(filename: string): Position {\n const position = this.positions.get(filename);\n if (!position) {\n return {\n row: -1,\n column: -1,\n };\n }\n return position;\n }\n}\n\nexport const CursorPositions: PositionTracker = new PositionTracker();\n","export const libSource = `interface NS {\n args: string[];\n /**\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n * Example documentation for scan.\n */\n scan(ip: string, hostnames: boolean): string[];\n hack(ip: string, threads: number, stock: boolean): Promise;\n hackAnalyzeThreads(ip: string, hackAmount: number): number;\n hackAnalyzePercent(ip: string): number;\n hackChance(ip: string): number;\n sleep(time: number): Promise;\n grow(ip: string, threads: number, stock: boolean): Promise;\n growthAnalyze(ip: string, growth: number): number;\n weaken(ip: string, threads: boolean): Promise;\n print(...args: any[]): void;\n tprint(...args: any[]): void;\n clearLog(): void;\n disableLog(fn: string): void;\n enableLog(fn: string): void;\n isLogEnabled(fn: string): boolean;\n getScriptLogs(fn: string, ip: string, ...scriptArgs: any[]): string[];\n tail(fn: string, ip: string, ...scriptArgs: any[]): void;\n nuke(ip: string): boolean;\n brutessh(ip: string): boolean;\n ftpcrack(ip: string): boolean;\n relaysmtp(ip: string): boolean;\n httpworm(ip: string): boolean;\n sqlinject(ip: string): boolean;\n run(scriptname: string, threads: number): number;\n exec(scriptname: string, ip: string, threads: number): number;\n spawn(scriptname: string, threads: number): void;\n kill(filename: string, ip: string, ...scriptArgs: any[]): boolean;\n killall(ip: string): boolean;\n exit(): void;\n scp(scriptname: string, ip1: string, ip2: string): boolean;\n ls(ip: string, grep: string): string[];\n ps(ip: string): {filename: string, threads: number, args: string[], pid: number}[];\n hasRootAccess(ip: string): boolean;\n getIp(): string;\n getHostname(): string;\n getHackingLevel(): number;\n getHackingMultipliers(): number;\n getHacknetMultipliers(): number;\n getBitNodeMultipliers(): number;\n getServer(ip: string): any;\n getServerMoneyAvailable(ip: string): number;\n getServerSecurityLevel(ip: string): number;\n getServerBaseSecurityLevel(ip: string): number;\n getServerMinSecurityLevel(ip: string): number;\n getServerRequiredHackingLevel(ip: string): number;\n getServerMaxMoney(ip: string): number;\n getServerGrowth(ip: string): number;\n getServerNumPortsRequired(ip: string): number;\n getServerRam(ip: string): number[];\n getServerMaxRam(ip: string): number;\n getServerUsedRam(ip: string): number;\n serverExists(ip: string): boolean;\n fileExists(filename: string, ip: string): boolean;\n isRunning(fn: string, ip: string, ...scriptArgs: any[]): boolean;\n getStockSymbols(): string[];\n getStockPrice(symbol: string): number;\n getStockAskPrice(symbol: string): number;\n getStockBidPrice(symbol: string): number;\n getStockPosition(symbol: string): number;\n getStockMaxShares(symbol: string): number;\n getStockPurchaseCost(symbol: string, shares: number, posType: string): number;\n getStockSaleGain(symbol: string, shares: number, posType: string): number;\n buyStock(symbol: string, shares: number): number;\n sellStock(symbol: string, shares: number): number;\n shortStock(symbol: string, shares: number): number;\n sellShort(symbol: string, shares: number): number;\n placeOrder(symbol: string, shares: number, price: number, type: string, pos: string): boolean;\n cancelOrder(symbol: string, shares: number, price: number, type: string, pos: string): boolean;\n getOrders(): any;\n getStockVolatility(symbol: string): number;\n getStockForecast(symbol: string): number;\n getPurchasedServerLimit(): number;\n getPurchasedServerMaxRam(): number;\n getPurchasedServerCost(ram: number): number;\n purchaseServer(hostname: string, ram: number): string;\n deleteServer(hostname: string): boolean;\n getPurchasedServers(hostname: string): string[];\n write(port: number, data: string, mode: string): boolean;\n tryWrite(port: number, data: string): boolean;\n read(port: number): any;\n peek(port: number): any;\n clear(port: number): number;\n getPortHandle(port: number): any; // netscript port\n rm(fn: string, ip: string): boolean;\n scriptRunning(scriptname: string, ip: string): boolean;\n scriptKill(scriptname: string, ip: string): boolean;\n getScriptName(): string;\n getScriptRam(scriptname: string, ip: string): number;\n getRunningScript(fn: string, ip: string): any; // running script\n getHackTime(ip: string): number;\n getGrowTime(ip: string): number;\n getWeakenTime(ip: string): number;\n getScriptIncome(scriptname: string, ip: string): number;\n getScriptExpGain(scriptname: string, ip: string): number;\n nFormat(n: number, format: string): string;\n tFormat(milliseconds: number, milliPrecision: boolean): string;\n getTimeSinceLastAug(): number;\n prompt(txt: string): Promise;\n getFavorToDonate(): number;\n universityCourse(universityName: string, className: string): boolean;\n gymWorkout(gymName: string, stat: string): boolean;\n travelToCity(cityname: string): boolean;\n purchaseTor(): boolean;\n purchaseProgram(programName: string): boolean;\n getCurrentServer(): any; // server object\n connect(hostname: string): boolean;\n manualHack(): Promise;\n installBackdoor(): Promise;\n getStats(): any; // complex type\n getCharacterInformation(): any; // complex type\n getPlayer(): any; // complex type\n hospitalize(): number;\n isBusy(): boolean;\n stopAction(): boolean;\n upgradeHomeRam(): number;\n getUpgradeHomeRamCost(): number;\n workForCompany(companyName: string): boolean;\n applyToCompany(companyName: string, field: string): boolean;\n getCompanyRep(companyName: string): number;\n getCompanyFavor(companyName: string): number;\n getCompanyFavorGain(companyName: string): number;\n checkFactionInvitations(): string[];\n joinFaction(name: string): boolean;\n workForFaction(name: string, type: string): boolean;\n getFactionRep(name: string): number;\n getFactionFavor(name: string): number;\n getFactionFavorGain(name: string): number;\n donateToFaction(name: string, amt: number): boolean;\n createProgram(name: string): boolean;\n commitCrime(crimeRoughName: string): number;\n getCrimeChance(crimeRoughName: string): boolean;\n getCrimeStats(crimeRoughName: string): any; // complex type\n getOwnedAugmentations(purchased: boolean): string[];\n getOwnedSourceFiles(): any; // complex type\n getAugmentationsFromFaction(facname: string): string[];\n getAugmentationCost(name: string): number;\n getAugmentationPrereq(name: string): string[];\n getAugmentationPrice(name: string): number;\n getAugmentationRepReq(name: string): number;\n getAugmentationStats(name: string): any; // complex type\n purchaseAugmentation(faction: string, name: string): boolean;\n softReset(cbScript: string): void;\n installAugmentations(cbScript: string): void;\n exploit(): void;\n bypass(doc: any): void;\n flags(data: any): any;\n}`;\n","import { Milestone } from \"./Milestone\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { Factions } from \"../Faction/Factions\";\nimport { Faction } from \"../Faction/Faction\";\nimport { GetServerByHostname } from \"../Server/ServerHelpers\";\n\nfunction allFactionAugs(p: IPlayer, f: Faction): boolean {\n const factionAugs = f.augmentations.slice().filter((aug) => aug !== \"NeuroFlux Governor\");\n for (const factionAug of factionAugs) {\n if (\n !p.augmentations.some((aug) => {\n return aug.name == factionAug;\n })\n )\n return false;\n }\n return true;\n}\n\nexport const Milestones: Milestone[] = [\n {\n title: \"Gain root access on CSEC\",\n fulfilled: (): boolean => {\n const server = GetServerByHostname(\"CSEC\");\n if (!server || !server.hasOwnProperty(\"hasAdminRights\")) return false;\n return (server as any).hasAdminRights;\n },\n },\n {\n title: \"Install the backdoor on CSEC\",\n fulfilled: (): boolean => {\n const server = GetServerByHostname(\"CSEC\");\n if (!server || !server.hasOwnProperty(\"backdoorInstalled\")) return false;\n return (server as any).backdoorInstalled;\n },\n },\n {\n title: \"Join the faction hinted at in csec-test.msg\",\n fulfilled: (p: IPlayer): boolean => {\n return p.factions.includes(\"CyberSec\");\n },\n },\n {\n title: \"Install all the Augmentations from CyberSec\",\n fulfilled: (p: IPlayer): boolean => {\n return allFactionAugs(p, Factions[\"CyberSec\"]);\n },\n },\n {\n title: \"Join the faction hinted at in nitesec-test.msg\",\n fulfilled: (p: IPlayer): boolean => {\n return p.factions.includes(\"NiteSec\");\n },\n },\n {\n title: \"Install all the Augmentations from NiteSec\",\n fulfilled: (p: IPlayer): boolean => {\n return allFactionAugs(p, Factions[\"NiteSec\"]);\n },\n },\n {\n title: \"Join the faction hinted at in j3.msg\",\n fulfilled: (p: IPlayer): boolean => {\n return p.factions.includes(\"The Black Hand\");\n },\n },\n {\n title: \"Install all the Augmentations from The Black Hand\",\n fulfilled: (p: IPlayer): boolean => {\n return allFactionAugs(p, Factions[\"The Black Hand\"]);\n },\n },\n {\n title: \"Join the faction hinted at in 19dfj3l1nd.msg\",\n fulfilled: (p: IPlayer): boolean => {\n return p.factions.includes(\"BitRunners\");\n },\n },\n {\n title: \"Install all the Augmentations from BitRunners\",\n fulfilled: (p: IPlayer): boolean => {\n return allFactionAugs(p, Factions[\"BitRunners\"]);\n },\n },\n {\n title: \"Complete fl1ght.exe\",\n fulfilled: (p: IPlayer): boolean => {\n // technically wrong but whatever\n return p.factions.includes(\"Daedalus\");\n },\n },\n {\n title: \"Install the special Augmentation from Daedalus\",\n fulfilled: (p: IPlayer): boolean => {\n return p.augmentations.some((aug) => aug.name == \"The Red Pill\");\n },\n },\n {\n title: \"Install the final backdoor and free yourself.\",\n fulfilled: (): boolean => {\n return false;\n },\n },\n];\n","import { CONSTANTS } from \"../../Constants\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nexport function repFromDonation(amt: number, player: IPlayer): number {\n return (amt / CONSTANTS.DonateMoneyToRepDivisor) * player.faction_rep_mult;\n}\n","import { StockSymbols } from \"./StockSymbols\";\n\nexport const TickerHeaderFormatData = {\n longestName: 0,\n longestSymbol: 0,\n};\n\nfor (const key in StockSymbols) {\n TickerHeaderFormatData.longestName = Math.max(key.length, TickerHeaderFormatData.longestName);\n TickerHeaderFormatData.longestSymbol = Math.max(StockSymbols[key].length, TickerHeaderFormatData.longestSymbol);\n}\n","import { substituteAliases } from \"../Alias\";\n// Helper function that checks if an argument (which is a string) is a valid number\nfunction isNumber(str: string): boolean {\n if (typeof str != \"string\") {\n return false;\n } // Only process strings\n return !isNaN(str as unknown as number) && !isNaN(parseFloat(str));\n}\nexport function ParseCommands(commands: string): string[] {\n // Sanitize input\n commands = commands.trim();\n // Replace all extra whitespace in command with a single space\n commands = commands.replace(/\\s\\s+/g, \" \");\n\n const match = commands.match(/(?:'[^']*'|\"[^\"]*\"|[^;\"])*/g);\n if (!match) return [];\n // Split commands and execute sequentially\n const allCommands = match\n .map(substituteAliases)\n .map((c) => c.match(/(?:'[^']*'|\"[^\"]*\"|[^;\"])*/g))\n .flat();\n\n const out: string[] = [];\n for (const c of allCommands) {\n if (c === null) continue;\n if (c.match(/^\\s*$/)) {\n continue;\n } // Don't run commands that only have whitespace\n out.push(c.trim());\n }\n return out;\n}\n\nexport function ParseCommand(command: string): (string | number)[] {\n // This will be used to keep track of whether we're in a quote. This is for situations\n // like the alias command:\n // alias run=\"run NUKE.exe\"\n // We want the run=\"run NUKE.exe\" to be parsed as a single command, so this flag\n // will keep track of whether we have a quote in\n let inQuote = ``;\n\n // Returns an array with the command and its arguments in each index\n // Properly handles quotation marks (e.g. `run foo.script \"the sun\"` will return [run, foo.script, the sun])\n const args = [];\n let start = 0,\n i = 0;\n let prevChar = \"\"; // Previous character\n while (i < command.length) {\n let escaped = false; // Check for escaped quotation marks\n if (i >= 1) {\n prevChar = command.charAt(i - 1);\n if (prevChar === \"\\\\\") {\n escaped = true;\n }\n }\n\n const c = command.charAt(i);\n if (c === '\"') {\n // Double quotes\n if (!escaped && prevChar === \" \") {\n const endQuote = command.indexOf('\"', i + 1);\n if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === \" \")) {\n args.push(command.substr(i + 1, endQuote - i - 1));\n if (endQuote === command.length - 1) {\n start = i = endQuote + 1;\n } else {\n start = i = endQuote + 2; // Skip the space\n }\n continue;\n }\n } else {\n if (inQuote === ``) {\n inQuote = `\"`;\n } else if (inQuote === `\"`) {\n inQuote = ``;\n }\n }\n } else if (c === \"'\") {\n // Single quotes, same thing as above\n if (!escaped && prevChar === \" \") {\n const endQuote = command.indexOf(\"'\", i + 1);\n if (endQuote !== -1 && (endQuote === command.length - 1 || command.charAt(endQuote + 1) === \" \")) {\n args.push(command.substr(i + 1, endQuote - i - 1));\n if (endQuote === command.length - 1) {\n start = i = endQuote + 1;\n } else {\n start = i = endQuote + 2; // Skip the space\n }\n continue;\n }\n } else {\n if (inQuote === ``) {\n inQuote = `'`;\n } else if (inQuote === `'`) {\n inQuote = ``;\n }\n }\n } else if (c === \" \" && inQuote === ``) {\n const arg = command.substr(start, i - start);\n\n // If this is a number, convert it from a string to number\n if (isNumber(arg)) {\n args.push(parseFloat(arg));\n } else {\n args.push(arg);\n }\n\n start = i + 1;\n }\n ++i;\n }\n\n // Add the last argument\n if (start !== i) {\n const arg = command.substr(start, i - start);\n\n // If this is a number, convert it from string to number\n if (isNumber(arg)) {\n args.push(parseFloat(arg));\n } else {\n args.push(arg);\n }\n }\n\n return args;\n}\n","/* tslint:disable:max-line-length completed-docs variable-name*/\nimport { IMap } from \"../types\";\n\nexport const TerminalHelpText: string[] = [\n \"Type 'help name' to learn more about the command \",\n \"\",\n 'alias [-g] [name=\"value\"] Create or display Terminal aliases',\n \"analyze Get information about the current machine \",\n \"backdoor Install a backdoor on the current machine \",\n \"buy [-l/program] Purchase a program through the Dark Web\",\n \"cat [file] Display a .msg, .lit, or .txt file\",\n \"cd [dir] Change to a new directory\",\n \"check [script] [args...] Print a script's logs to Terminal\",\n \"clear Clear all text on the terminal \",\n \"cls See 'clear' command \",\n \"connect [ip/hostname] Connects to a remote server\",\n \"download [script/text file] Downloads scripts or text files to your computer\",\n \"expr [math expression] Evaluate a mathematical expression\",\n \"free Check the machine's memory (RAM) usage\",\n \"hack Hack the current machine\",\n \"help [command] Display this help text, or the help text for a command\",\n \"home Connect to home computer\",\n \"hostname Displays the hostname of the machine\",\n \"ifconfig Displays the IP address of the machine\",\n \"kill [script/pid] [args...] Stops the specified script on the current server \",\n \"killall Stops all running scripts on the current machine\",\n \"ls [dir] [| grep pattern] Displays all files on the machine\",\n \"lscpu Displays the number of CPU cores on the machine\",\n \"mem [script] [-t] [n] Displays the amount of RAM required to run the script\",\n \"mv [src] [dest] Move/rename a text or script file\",\n \"nano [file] Text editor - Open up and edit a script or text file\",\n \"ps Display all scripts that are currently running\",\n \"rm [file] Delete a file from the server\",\n \"run [name] [-t n] [--tail] [args...] Execute a program or script\",\n \"scan Prints all immediately-available network connections\",\n \"scan-analyze [d] [-a] Prints info for all servers up to d nodes away\",\n \"scp [file] [server] Copies a file to a destination server\",\n \"sudov Shows whether you have root access on this computer\",\n \"tail [script] [args...] Displays dynamic logs for the specified script\",\n \"top Displays all running scripts and their RAM usage\",\n \"unalias [alias name] Deletes the specified alias\",\n \"wget [url] [target file] Retrieves code/text from a web server\",\n];\n\nexport const HelpTexts: IMap = {\n alias: [\n 'alias [-g] [name=\"value\"] ',\n \" \",\n \"Create or display aliases. An alias enables a replacement of a word with another string. \",\n \"It can be used to abbreviate a commonly used command, or commonly used parts of a command. The NAME \",\n \"of an alias defines the word that will be replaced, while the VALUE defines what it will be replaced by. For example, \",\n \"you could create the alias 'nuke' for the Terminal command 'run NUKE.exe' using the following: \",\n \" \",\n 'alias nuke=\"run NUKE.exe\"',\n \" \",\n \"Then, to run the NUKE.exe program you would just have to enter 'nuke' in Terminal rather than the full command. \",\n \"It is important to note that 'default' aliases will only be substituted for the first word of a Terminal command. For \",\n \"example, if the following alias was set: \",\n \" \",\n 'alias worm=\"HTTPWorm.exe\"',\n \" \",\n \"and then you tried to run the following terminal command: \",\n \" \",\n \"run worm\",\n \" \",\n \"This would fail because the worm alias is not the first word of a Terminal command. To allow an alias to be substituted \",\n \"anywhere in a Terminal command, rather than just the first word, you must set it to be a global alias using the -g flag: \",\n \" \",\n 'alias -g worm=\"HTTPWorm.exe\"',\n \" \",\n \"Now, the 'worm' alias will be substituted anytime it shows up as an individual word in a Terminal command. \",\n \" \",\n \"Entering just the command 'alias' without any arguments prints the list of all defined aliases in the reusable form \",\n \"'alias NAME=VALUE' on the Terminal. \",\n \" \",\n \"The 'unalias' command can be used to remove aliases.\",\n \" \",\n ],\n analyze: [\n \"analze\",\n \" \",\n \"Prints details and statistics about the current server. The information that is printed includes basic \",\n \"server details such as the hostname, whether the player has root access, what ports are opened/closed, and also \",\n \"hacking-related information such as an estimated chance to successfully hack, an estimate of how much money is \",\n \"available on the server, etc.\",\n ],\n backdoor: [\n \"backdoor\",\n \" \",\n \"Install a backdoor on the current machine, grants a secret bonus depending on the machine.\",\n \" \",\n \"Requires root access to run.\",\n \" \",\n ],\n buy: [\n \"buy [-l / program]\",\n \" \",\n \"Purchase a program through the Dark Web. Requires a TOR router to use.\",\n \" \",\n \"If this command is ran with the '-l' flag, it will display a list of all programs that can be bought through the \",\n \"dark web to the Terminal, as well as their costs.\",\n \" \",\n \"Otherwise, the name of the program must be passed in as a parameter. This name is NOT case-sensitive.\",\n ],\n cat: [\n \"cat [file]\",\n \" \",\n \"Display message (.msg), literature (.lit), or text (.txt) files. Examples:\",\n \" \",\n \"cat j1.msg\",\n \" \",\n \"cat foo.lit\",\n \" \",\n \"cat servers.txt\",\n ],\n cd: [\n \"cd [dir]\",\n \" \",\n \"Change to the specified directory. Note that this works even for directories that don't exist. If you \",\n \"change to a directory that does not exist, it will not be 'created'. Examples:\",\n \" \",\n \"cd scripts/hacking\",\n \" \",\n \"cd /logs\",\n \" \",\n \"cd ../\",\n ],\n check: [\n \"check [script name] [args...]\",\n \" \",\n \"Print the logs of the script specified by the script name and arguments to the Terminal. Each argument must be separated by \",\n \"a space. Remember that a running script is uniquely \",\n \"identified both by its name and the arguments that are used to start it. So, if a script was ran with the following arguments: \",\n \" \",\n \"run foo.script 1 2 foodnstuff\",\n \" \",\n \"Then to run the 'check' command on this script you would have to pass the same arguments in: \",\n \" \",\n \"check foo.script 1 2 foodnstuff\",\n ],\n clear: [\n \"clear\",\n \" \",\n \"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up \",\n \"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'cls' command\",\n ],\n cls: [\n \"cls\",\n \" \",\n \"Clear the Terminal screen, deleting all of the text. Note that this does not delete the user's command history, so using the up \",\n \"and down arrow keys is still valid. Also note that this is permanent and there is no way to undo this. Synonymous with 'clear' command\",\n ],\n connect: [\n \"connect [hostname/ip]\",\n \" \",\n \"Connect to a remote server. The hostname or IP address of the remote server must be given as the argument \",\n \"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To \",\n \"see which servers can be connected to, use the 'scan' command.\",\n ],\n download: [\n \"download [script/text file]\",\n \" \",\n \"Downloads a script or text file to your computer (like your real life computer).\",\n \" \",\n \"You can also download all of your scripts/text files as a zip file using the following Terminal commands:\",\n \" \",\n \"Download all scripts and text files: download *\",\n \" \",\n \"Download all scripts: download *.script\",\n \" \",\n \"Download all text files: download *.txt\",\n \" \",\n ],\n expr: [\n \"expr [mathematical expression]\",\n \" \",\n \"Evaluate a simple mathematical expression. Supports native JavaScript operators:\",\n \" \",\n \"+, -, /, *, **, %\",\n \" \",\n \"Example:\",\n \" \",\n \"expr 25 * 2 ** 10\",\n \" \",\n \"Note that letters (non-digits) are not allowed and will be removed from the input.\",\n ],\n free: [\n \"free\",\n \" \",\n \"Display's the memory usage on the current machine. Print the amount of RAM that is available on the current server as well as \",\n \"how much of it is being used.\",\n ],\n hack: [\n \"hack\",\n \" \",\n \"Attempt to hack the current server. Requires root access in order to be run. See the wiki page for hacking mechanics\",\n \" \",\n ],\n help: [\n \"help [command]\",\n \" \",\n \"Display Terminal help information. Without arguments, 'help' prints a list of all valid Terminal commands and a brief \",\n \"description of their functionality. You can also pass the name of a Terminal command as an argument to 'help' to print \",\n \"more detailed information about the Terminal command. Examples: \",\n \" \",\n \"help alias\",\n \" \",\n \"help scan-analyze\",\n ],\n home: [\n \"home\" + \"Connect to your home computer. This will work no matter what server you are currently connected to.\",\n ],\n hostname: [\"hostname\", \" \", \"Prints the hostname of the current server\"],\n ifconfig: [\"ipconfig\", \" \", \"Prints the IP address of the current server\"],\n kill: [\n \"kill [script name] [args...]\",\n \" \",\n \"kill [pid]\",\n \" \",\n \"Kill the script specified by the script name and arguments OR by its PID.\",\n \" \",\n \"If you are killing the script using its filename and arguments, then each \",\n \"argument must be separated by a space. Remember that a running script is \",\n \"uniquely identified by both its name and the arguments that are used to start \",\n \"it. So, if a script was ran with the following arguments:\",\n \" \",\n \"run foo.script 1 sigma-cosmetics\",\n \" \",\n \"Then to kill this script the same arguments would have to be used:\",\n \" \",\n \"kill foo.script 1 sigma-cosmetics\",\n \" \",\n \"If you are killing the script using its PID, then the PID argument must be numeric\",\n ],\n killall: [\n \"killall\",\n \" \",\n \"Kills all scripts on the current server. \",\n \"Note that after the 'kill' command is issued for a script, it may take a while for the script to actually stop running. \",\n \"This will happen if the script is in the middle of a command such as grow() or weaken() that takes time to execute. \",\n \"The script will not be stopped/killed until after that time has elapsed.\",\n ],\n ls: [\n \"ls [dir] [| grep pattern]\",\n \" \",\n \"The ls command, with no arguments, prints all files and directories on the current server's directory to the Terminal screen. \",\n \"The files will be displayed in alphabetical order. \",\n \" \",\n \"The 'dir' optional parameter can be used to display files/directories in another directory.\",\n \" \",\n \"The '| grep pattern' optional parameter can be used to only display files whose filenames match the specified pattern.\",\n \" \",\n \"Examples:\",\n \" \",\n \"List all files with the '.script' extension in the current directory:\",\n \" \",\n \"ls | grep .script\",\n \" \",\n \"List all files with the '.js' extension in the root directory:\",\n \" \",\n \"ls / | grep .js\",\n \" \",\n \"List all files with the word 'purchase' in the filename, in the 'scripts' directory:\",\n \" \",\n \"ls scripts | grep purchase\",\n ],\n lscpu: [\"lscpu\", \" \", \"Prints the number of CPU Cores the current server has\"],\n\n mem: [\n \"mem [script name] [-t num_threads]\",\n \" \",\n \"Displays the amount of RAM needed to run the specified script with a single thread. The command can also be used to print \",\n \"the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then \",\n \"an argument for the number of threads must be passed in afterwards. Examples:\",\n \" \",\n \"mem foo.script\",\n \" \",\n \"mem foo.script -t 50\",\n \" \",\n \"The first example above will print the amount of RAM needed to run 'foo.script' with a single thread. The second example \",\n \"above will print the amount of RAM needed to run 'foo.script' with 50 threads.\",\n ],\n mv: [\n \"mv [src] [dest]\",\n \" \",\n \"Move the source file to the specified destination. This can also be used to rename files. \",\n \"This command only works for scripts and text files (.txt). This command CANNOT be used to \",\n \"convert to different file types\",\n \" \",\n \"Note that, unlike the Linux 'mv' command, the destination argument must be the \",\n \"full filepath. \",\n \"Examples: \",\n \" \",\n \"mv hacking-controller.script scripts/hacking-controller.script\",\n \" \",\n \"mv myScript.js myOldScript.js\",\n ],\n nano: [\n \"nano [file name]\",\n \" \",\n \"Opens up the specified file in the Text Editor. Only scripts (.script) or text files (.txt) can be \",\n \"edited using the Text Editor. If the file does not already exist, then a new, empty one \",\n \"will be created\",\n ],\n ps: [\"ps\", \" \", \"Prints all scripts that are running on the current server\"],\n\n rm: [\n \"rm [file]\",\n \" \",\n \"Removes the specified file from the current server. A file can be a script, a program, or a message file. \",\n \" \",\n \"WARNING: This is permanent and cannot be undone\",\n ],\n run: [\n \"run [file name] [-t] [num threads] [args...]\",\n \" \",\n \"Execute a program or a script.\",\n \" \",\n \"The '[-t]', '[num threads]', and '[args...]' arguments are only valid when running a script. The '-t' flag is used \",\n \"to indicate that the script should be run with the specified number of threads. If the flag is omitted, \",\n \"then the script will be run with a single thread by default. \",\n \"If the '-t' flag is used, then it MUST come immediately \",\n \"after the script name, and the [num threads] argument MUST come immediately afterwards. \",\n \" \",\n \"[args...] represents a variable number of arguments that will be passed into the script. See the documentation \",\n \"about script arguments. Each specified argument must be separated by a space. \",\n \" \",\n ],\n scan: [\n \"scan\",\n \" \",\n \"Prints all immediately-available network connection. This will print a list of all servers that you can currently connect \",\n \"to using the 'connect' Terminal command.\",\n ],\n \"scan-analyze\": [\n \"scan-analyze [depth] [-a]\",\n \" \",\n \"Prints detailed information about all servers up to [depth] nodes away on the network. Calling \",\n \"'scan-analyze 1' will display information for the same servers that are shown by the 'scan' Terminal \",\n \"command. This command also shows the relative paths to reach each server.\",\n \" \",\n \"By default, the maximum depth that can be specified for 'scan-analyze' is 3. However, once you have \",\n \"the DeepscanV1.exe and DeepscanV2.exe programs, you can execute 'scan-analyze' with a depth up to \",\n \"5 and 10, respectively.\",\n \" \",\n \"The information 'scan-analyze' displays about each server includes whether or not you have root access to it, \",\n \"its required hacking level, the number of open ports required to run NUKE.exe on it, and how much RAM \",\n \"it has.\",\n \" \",\n \"By default, this command will not display servers that you have purchased. However, you can pass in the \",\n \"-a flag at the end of the command if you would like to enable that.\",\n ],\n scp: [\n \"scp [filename] [target server]\",\n \" \",\n \"Copies the specified file from the current server to the target server. \",\n \"This command only works for script files (.script extension), literature files (.lit extension), \",\n \"and text files (.txt extension). \",\n \"The second argument passed in must be the hostname or IP of the target server.\",\n ],\n sudov: [\"sudov\", \" \", \"Prints whether or not you have root access to the current machine\"],\n\n tail: [\n \"tail [script name] [args...]\",\n \" \",\n \"Displays dynamic logs for the script specified by the script name and arguments. Each argument must be separated \",\n \"by a space. Remember that a running script is uniquely identified by both its name and the arguments that were used \",\n \"to run it. So, if a script was ran with the following arguments: \",\n \" \",\n \"run foo.script 10 50000\",\n \" \",\n \"Then in order to check its logs with 'tail' the same arguments must be used: \",\n \" \",\n \"tail foo.script 10 50000\",\n ],\n top: [\n \"top\",\n \" \",\n \"Prints a list of all scripts running on the current server as well as their thread count and how much \",\n \"RAM they are using in total.\",\n ],\n unalias: [\n \"unalias [alias name]\",\n \" \",\n \"Deletes the specified alias. Note that the double quotation marks are required. \",\n \" \",\n \"As an example, if an alias was declared using:\",\n \" \",\n 'alias r=\"run\"',\n \" \",\n \"Then it could be removed using:\",\n \" \",\n \"unalias r\",\n \" \",\n \"It is not necessary to differentiate between global and non-global aliases when using 'unalias'\",\n ],\n wget: [\n \"wget [url] [target file]\",\n \" \",\n \"Retrieves data from a URL and downloads it to a file on the current server. The data can only \",\n \"be downloaded to a script (.script, .ns, .js) or a text file (.txt). If the file already exists, \",\n \"it will be overwritten by this command.\",\n \" \",\n \"Note that it will not be possible to download data from many websites because they do not allow \",\n \"cross-origin resource sharing (CORS). Example:\",\n \" \",\n \"wget https://raw.githubusercontent.com/danielyxie/bitburner/master/README.md game_readme.txt\",\n ],\n};\n","import { ITerminal, Output, Link, TTimer } from \"./ITerminal\";\nimport { IRouter } from \"../ui/Router\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { HacknetServer } from \"../Hacknet/HacknetServer\";\nimport { BaseServer } from \"../Server/BaseServer\";\nimport { Server } from \"../Server/Server\";\nimport { Programs } from \"../Programs/Programs\";\nimport { CodingContractResult } from \"../CodingContracts\";\nimport { TerminalEvents, TerminalClearEvents } from \"./TerminalEvents\";\n\nimport { TextFile } from \"../TextFile\";\nimport { Script } from \"../Script/Script\";\nimport { isScriptFilename } from \"../Script/isScriptFilename\";\nimport { CONSTANTS } from \"../Constants\";\nimport { AllServers } from \"../Server/AllServers\";\n\nimport { removeLeadingSlash, isInRootDirectory, evaluateFilePath } from \"./DirectoryHelpers\";\nimport { checkIfConnectedToDarkweb } from \"../DarkWeb/DarkWeb\";\nimport { iTutorialNextStep, iTutorialSteps, ITutorial } from \"../InteractiveTutorial\";\nimport { GetServerByHostname, getServer, getServerOnNetwork } from \"../Server/ServerHelpers\";\nimport { ParseCommand, ParseCommands } from \"./Parser\";\nimport { SpecialServerIps, SpecialServerNames } from \"../Server/SpecialServerIps\";\nimport { Settings } from \"../Settings/Settings\";\nimport { createProgressBarText } from \"../utils/helpers/createProgressBarText\";\nimport {\n calculateHackingChance,\n calculateHackingExpGain,\n calculatePercentMoneyHacked,\n calculateHackingTime,\n} from \"../Hacking\";\nimport { numeralWrapper } from \"../ui/numeralFormat\";\nimport { convertTimeMsToTimeElapsedString } from \"../utils/StringHelperFunctions\";\n\nimport { alias } from \"./commands/alias\";\nimport { analyze } from \"./commands/analyze\";\nimport { backdoor } from \"./commands/backdoor\";\nimport { buy } from \"./commands/buy\";\nimport { cat } from \"./commands/cat\";\nimport { cd } from \"./commands/cd\";\nimport { check } from \"./commands/check\";\nimport { connect } from \"./commands/connect\";\nimport { download } from \"./commands/download\";\nimport { expr } from \"./commands/expr\";\nimport { free } from \"./commands/free\";\nimport { hack } from \"./commands/hack\";\nimport { help } from \"./commands/help\";\nimport { home } from \"./commands/home\";\nimport { hostname } from \"./commands/hostname\";\nimport { ifconfig } from \"./commands/ifconfig\";\nimport { kill } from \"./commands/kill\";\nimport { killall } from \"./commands/killall\";\nimport { ls } from \"./commands/ls\";\nimport { lscpu } from \"./commands/lscpu\";\nimport { mem } from \"./commands/mem\";\nimport { mv } from \"./commands/mv\";\nimport { nano } from \"./commands/nano\";\nimport { ps } from \"./commands/ps\";\nimport { rm } from \"./commands/rm\";\nimport { run } from \"./commands/run\";\nimport { scan } from \"./commands/scan\";\nimport { scananalyze } from \"./commands/scananalyze\";\nimport { scp } from \"./commands/scp\";\nimport { sudov } from \"./commands/sudov\";\nimport { tail } from \"./commands/tail\";\nimport { top } from \"./commands/top\";\nimport { unalias } from \"./commands/unalias\";\nimport { wget } from \"./commands/wget\";\n\nexport class Terminal implements ITerminal {\n // Flags to determine whether the player is currently running a hack or an analyze\n action: TTimer | null = null;\n\n commandHistory: string[] = [];\n commandHistoryIndex = 0;\n\n outputHistory: (Output | Link)[] = [new Output(`Bitburner v${CONSTANTS.Version}`, \"primary\")];\n\n // True if a Coding Contract prompt is opened\n contractOpen = false;\n\n // Full Path of current directory\n // Excludes the trailing forward slash\n currDir = \"/\";\n\n process(router: IRouter, player: IPlayer, cycles: number): void {\n if (this.action === null) return;\n this.action.timeLeft -= (CONSTANTS._idleSpeed * cycles) / 1000;\n if (this.action.timeLeft < 0.01) this.finishAction(router, player, false);\n TerminalEvents.emit();\n }\n\n append(item: Output | Link): void {\n this.outputHistory.push(item);\n if (this.outputHistory.length > Settings.MaxTerminalCapacity) {\n this.outputHistory.splice(0, this.outputHistory.length - Settings.MaxTerminalCapacity);\n }\n TerminalEvents.emit();\n }\n\n print(s: string): void {\n this.append(new Output(s, \"primary\"));\n }\n\n error(s: string): void {\n this.append(new Output(s, \"error\"));\n }\n\n startHack(player: IPlayer): void {\n // Hacking through Terminal should be faster than hacking through a script\n const server = player.getCurrentServer();\n if (server instanceof HacknetServer) {\n this.error(\"Cannot hack this kind of server\");\n return;\n }\n this.startAction(calculateHackingTime(server, player) / 4, \"h\");\n }\n\n startBackdoor(player: IPlayer): void {\n // Backdoor should take the same amount of time as hack\n const server = player.getCurrentServer();\n if (server instanceof HacknetServer) {\n this.error(\"Cannot backdoor this kind of server\");\n return;\n }\n this.startAction(calculateHackingTime(server, player) / 4, \"b\");\n }\n\n startAnalyze(): void {\n this.print(\"Analyzing system...\");\n this.startAction(1, \"a\");\n }\n\n startAction(n: number, action: \"h\" | \"b\" | \"a\"): void {\n this.action = new TTimer(n, action);\n }\n\n // Complete the hack/analyze command\n finishHack(router: IRouter, player: IPlayer, cancelled = false): void {\n if (cancelled) return;\n const server = player.getCurrentServer();\n if (server instanceof HacknetServer) {\n this.error(\"Cannot hack this kind of server\");\n return;\n }\n\n // Calculate whether hack was successful\n const hackChance = calculateHackingChance(server, player);\n const rand = Math.random();\n const expGainedOnSuccess = calculateHackingExpGain(server, player);\n const expGainedOnFailure = expGainedOnSuccess / 4;\n if (rand < hackChance) {\n // Success!\n if (\n SpecialServerIps[SpecialServerNames.WorldDaemon] &&\n SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip\n ) {\n if (player.bitNodeN == null) {\n player.bitNodeN = 1;\n }\n router.toBitVerse(false, false);\n return;\n }\n server.backdoorInstalled = true;\n let moneyGained = calculatePercentMoneyHacked(server, player);\n moneyGained = Math.floor(server.moneyAvailable * moneyGained);\n\n if (moneyGained <= 0) {\n moneyGained = 0;\n } // Safety check\n\n server.moneyAvailable -= moneyGained;\n player.gainMoney(moneyGained);\n player.recordMoneySource(moneyGained, \"hacking\");\n player.gainHackingExp(expGainedOnSuccess);\n player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain);\n\n server.fortify(CONSTANTS.ServerFortifyAmount);\n\n this.print(\n `Hack successful! Gained ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp(\n expGainedOnSuccess,\n )} hacking exp`,\n );\n } else {\n // Failure\n // player only gains 25% exp for failure? TODO Can change this later to balance\n player.gainHackingExp(expGainedOnFailure);\n this.print(\n `Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`,\n );\n }\n }\n\n finishBackdoor(router: IRouter, player: IPlayer, cancelled = false): void {\n if (!cancelled) {\n const server = player.getCurrentServer();\n if (server instanceof HacknetServer) {\n this.error(\"Cannot hack this kind of server\");\n return;\n }\n if (\n SpecialServerIps[SpecialServerNames.WorldDaemon] &&\n SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip\n ) {\n if (player.bitNodeN == null) {\n player.bitNodeN = 1;\n }\n router.toBitVerse(false, false);\n return;\n }\n server.backdoorInstalled = true;\n this.print(\"Backdoor successful!\");\n }\n }\n\n finishAnalyze(player: IPlayer, cancelled = false): void {\n if (!cancelled) {\n const currServ = player.getCurrentServer();\n const isHacknet = currServ instanceof HacknetServer;\n this.print(currServ.hostname + \": \");\n const org = currServ.organizationName;\n this.print(\"Organization name: \" + (!isHacknet ? org : \"player\"));\n const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet;\n this.print(\"Root Access: \" + (hasAdminRights ? \"YES\" : \"NO\"));\n if (currServ instanceof Server) {\n const hackingSkill = currServ.requiredHackingSkill;\n this.print(\"Required hacking skill: \" + (!isHacknet ? hackingSkill : \"N/A\"));\n const security = currServ.hackDifficulty;\n this.print(\"Server security level: \" + (!isHacknet ? numeralWrapper.formatServerSecurity(security) : \"N/A\"));\n const hackingChance = calculateHackingChance(currServ, player);\n this.print(\"Chance to hack: \" + (!isHacknet ? numeralWrapper.formatPercentage(hackingChance) : \"N/A\"));\n const hackingTime = calculateHackingTime(currServ, player) * 1000;\n this.print(\"Time to hack: \" + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : \"N/A\"));\n }\n this.print(\n `Total money available on server: ${\n !(currServ instanceof HacknetServer) ? numeralWrapper.formatMoney(currServ.moneyAvailable) : \"N/A\"\n }`,\n );\n if (currServ instanceof Server) {\n const numPort = currServ.numOpenPortsRequired;\n this.print(\"Required number of open ports for NUKE: \" + (!isHacknet ? numPort : \"N/A\"));\n this.print(\"SSH port: \" + (currServ.sshPortOpen ? \"Open\" : \"Closed\"));\n this.print(\"FTP port: \" + (currServ.ftpPortOpen ? \"Open\" : \"Closed\"));\n this.print(\"SMTP port: \" + (currServ.smtpPortOpen ? \"Open\" : \"Closed\"));\n this.print(\"HTTP port: \" + (currServ.httpPortOpen ? \"Open\" : \"Closed\"));\n this.print(\"SQL port: \" + (currServ.sqlPortOpen ? \"Open\" : \"Closed\"));\n }\n }\n }\n\n finishAction(router: IRouter, player: IPlayer, cancelled = false): void {\n if (this.action === null) {\n if (!cancelled) throw new Error(\"Finish action called when there was no action\");\n return;\n }\n this.print(this.getProgressText());\n if (this.action.action === \"h\") {\n this.finishHack(router, player, cancelled);\n } else if (this.action.action === \"b\") {\n this.finishBackdoor(router, player, cancelled);\n } else if (this.action.action === \"a\") {\n this.finishAnalyze(player, cancelled);\n }\n if (cancelled) {\n this.print(\"Cancelled\");\n }\n this.action = null;\n TerminalEvents.emit();\n }\n\n getFile(player: IPlayer, filename: string): Script | TextFile | string | null {\n if (isScriptFilename(filename)) {\n return this.getScript(player, filename);\n }\n\n if (filename.endsWith(\".lit\")) {\n return this.getLitFile(player, filename);\n }\n\n if (filename.endsWith(\".txt\")) {\n return this.getTextFile(player, filename);\n }\n\n return null;\n }\n\n getFilepath(filename: string): string {\n const path = evaluateFilePath(filename, this.cwd());\n if (path == null) {\n throw new Error(`Invalid file path specified: ${filename}`);\n }\n\n if (isInRootDirectory(path)) {\n return removeLeadingSlash(path);\n }\n\n return path;\n }\n\n getScript(player: IPlayer, filename: string): Script | null {\n const s = player.getCurrentServer();\n const filepath = this.getFilepath(filename);\n for (const script of s.scripts) {\n if (filepath === script.filename) {\n return script;\n }\n }\n\n return null;\n }\n\n getTextFile(player: IPlayer, filename: string): TextFile | null {\n const s = player.getCurrentServer();\n const filepath = this.getFilepath(filename);\n for (const txt of s.textFiles) {\n if (filepath === txt.fn) {\n return txt;\n }\n }\n\n return null;\n }\n\n getLitFile(player: IPlayer, filename: string): string | null {\n const s = player.getCurrentServer();\n const filepath = this.getFilepath(filename);\n for (const lit of s.messages) {\n if (typeof lit === \"string\" && filepath === lit) {\n return lit;\n }\n }\n\n return null;\n }\n\n cwd(): string {\n return this.currDir;\n }\n\n setcwd(dir: string): void {\n this.currDir = dir;\n TerminalEvents.emit();\n }\n\n async runContract(player: IPlayer, contractName: string): Promise {\n // There's already an opened contract\n if (this.contractOpen) {\n return this.error(\"There's already a Coding Contract in Progress\");\n }\n\n const serv = player.getCurrentServer();\n const contract = serv.getContract(contractName);\n if (contract == null) {\n return this.error(\"No such contract\");\n }\n\n this.contractOpen = true;\n const res = await contract.prompt();\n\n switch (res) {\n case CodingContractResult.Success:\n if (contract.reward !== null) {\n const reward = player.gainCodingContractReward(contract.reward, contract.getDifficulty());\n this.print(`Contract SUCCESS - ${reward}`);\n }\n serv.removeContract(contract);\n break;\n case CodingContractResult.Failure:\n ++contract.tries;\n if (contract.tries >= contract.getMaxNumTries()) {\n this.print(\"Contract FAILED - Contract is now self-destructing\");\n serv.removeContract(contract);\n } else {\n this.print(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`);\n }\n break;\n case CodingContractResult.Cancelled:\n default:\n this.print(\"Contract cancelled\");\n break;\n }\n this.contractOpen = false;\n }\n\n executeScanAnalyzeCommand(player: IPlayer, depth = 1, all = false): void {\n // TODO Using array as stack for now, can make more efficient\n this.print(\"~~~~~~~~~~ Beginning scan-analyze ~~~~~~~~~~\");\n this.print(\" \");\n\n // Map of all servers to keep track of which have been visited\n const visited: {\n [key: string]: number | undefined;\n } = {};\n for (const ip in AllServers) {\n visited[ip] = 0;\n }\n\n const stack: BaseServer[] = [];\n const depthQueue: number[] = [0];\n const currServ = player.getCurrentServer();\n stack.push(currServ);\n while (stack.length != 0) {\n const s = stack.pop();\n if (!s) continue;\n const d = depthQueue.pop();\n if (d === undefined) continue;\n const isHacknet = s instanceof HacknetServer;\n if (!all && (s as any).purchasedByPlayer && s.hostname != \"home\") {\n continue; // Purchased server\n } else if (visited[s.ip] || d > depth) {\n continue; // Already visited or out-of-depth\n } else if (!all && isHacknet) {\n continue; // Hacknet Server\n } else {\n visited[s.ip] = 1;\n }\n for (let i = s.serversOnNetwork.length - 1; i >= 0; --i) {\n const newS = getServerOnNetwork(s, i);\n if (newS === null) continue;\n stack.push(newS);\n depthQueue.push(d + 1);\n }\n if (d == 0) {\n continue;\n } // Don't print current server\n const titleDashes = Array((d - 1) * 4 + 1).join(\"-\");\n if (player.hasProgram(Programs.AutoLink.name)) {\n this.append(new Link(titleDashes, s.hostname));\n } else {\n this.print(titleDashes + s.hostname);\n }\n\n const dashes = titleDashes + \"--\";\n let c = \"NO\";\n if (s.hasAdminRights) {\n c = \"YES\";\n }\n this.print(\n `${dashes}Root Access: ${c}${!isHacknet ? \", Required hacking skill: \" + (s as any).requiredHackingSkill : \"\"}`,\n );\n if (s.hasOwnProperty(\"numOpenPortsRequired\")) {\n this.print(dashes + \"Number of open ports required to NUKE: \" + (s as any).numOpenPortsRequired);\n }\n this.print(dashes + \"RAM: \" + numeralWrapper.formatRAM(s.maxRam));\n this.print(\" \");\n }\n }\n\n connectToServer(player: IPlayer, server: string): void {\n const serv = getServer(server);\n if (serv == null) {\n this.error(\"Invalid server. Connection failed.\");\n return;\n }\n player.getCurrentServer().isConnectedTo = false;\n player.currentServer = serv.ip;\n player.getCurrentServer().isConnectedTo = true;\n this.print(\"Connected to \" + serv.hostname);\n this.setcwd(\"/\");\n if (player.getCurrentServer().hostname == \"darkweb\") {\n checkIfConnectedToDarkweb(); // Posts a 'help' message if connecting to dark web\n }\n }\n\n executeCommands(router: IRouter, player: IPlayer, commands: string): void {\n // Sanitize input\n commands = commands.trim();\n commands = commands.replace(/\\s\\s+/g, \" \"); // Replace all extra whitespace in command with a single space\n\n // Handle Terminal History - multiple commands should be saved as one\n if (this.commandHistory[this.commandHistory.length - 1] != commands) {\n this.commandHistory.push(commands);\n if (this.commandHistory.length > 50) {\n this.commandHistory.splice(0, 1);\n }\n }\n this.commandHistoryIndex = this.commandHistory.length;\n const allCommands = ParseCommands(commands);\n\n for (let i = 0; i < allCommands.length; i++) {\n this.executeCommand(router, player, allCommands[i]);\n }\n }\n\n clear(): void {\n // TODO: remove this once we figure out the height issue.\n this.outputHistory = [new Output(`Bitburner v${CONSTANTS.Version}`, \"primary\")];\n TerminalEvents.emit();\n TerminalClearEvents.emit();\n }\n\n prestige(): void {\n this.action = null;\n this.clear();\n }\n\n executeCommand(router: IRouter, player: IPlayer, command: string): void {\n if (this.action !== null) {\n this.error(`Cannot execute command (${command}) while an action is in progress`);\n return;\n }\n // Allow usage of ./\n if (command.startsWith(\"./\")) {\n command = \"run \" + command.slice(2);\n }\n // Only split the first space\n const commandArray = ParseCommand(command);\n if (commandArray.length == 0) {\n return;\n }\n const s = player.getCurrentServer();\n /****************** Interactive Tutorial Terminal Commands ******************/\n if (ITutorial.isRunning) {\n const n00dlesServ = GetServerByHostname(\"n00dles\");\n if (n00dlesServ == null) {\n throw new Error(\"Could not get n00dles server\");\n return;\n }\n switch (ITutorial.currStep) {\n case iTutorialSteps.TerminalHelp:\n if (commandArray.length === 1 && commandArray[0] == \"help\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalLs:\n if (commandArray.length === 1 && commandArray[0] == \"ls\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalScan:\n if (commandArray.length === 1 && commandArray[0] == \"scan\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalScanAnalyze1:\n if (commandArray.length == 1 && commandArray[0] == \"scan-analyze\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalScanAnalyze2:\n if (commandArray.length == 2 && commandArray[0] == \"scan-analyze\" && commandArray[1] === 2) {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalConnect:\n if (commandArray.length == 2) {\n if (commandArray[0] == \"connect\" && (commandArray[1] == \"n00dles\" || commandArray[1] == n00dlesServ.ip)) {\n iTutorialNextStep();\n } else {\n this.print(\"Wrong command! Try again!\");\n return;\n }\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalAnalyze:\n if (commandArray.length === 1 && commandArray[0] === \"analyze\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalNuke:\n if (commandArray.length == 2 && commandArray[0] == \"run\" && commandArray[1] == \"NUKE.exe\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalManualHack:\n if (commandArray.length == 1 && commandArray[0] == \"hack\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalGoHome:\n if (commandArray.length == 1 && commandArray[0] == \"home\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalCreateScript:\n if (commandArray.length == 2 && commandArray[0] == \"nano\" && commandArray[1] == \"n00dles.script\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalFree:\n if (commandArray.length == 1 && commandArray[0] == \"free\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.TerminalRunScript:\n if (commandArray.length == 2 && commandArray[0] == \"run\" && commandArray[1] == \"n00dles.script\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n case iTutorialSteps.ActiveScriptsToTerminal:\n if (commandArray.length == 2 && commandArray[0] == \"tail\" && commandArray[1] == \"n00dles.script\") {\n iTutorialNextStep();\n } else {\n this.print(\"Bad command. Please follow the tutorial\");\n return;\n }\n break;\n default:\n this.print(\"Please follow the tutorial, or click 'EXIT' if you'd like to skip it\");\n return;\n }\n }\n /****************** END INTERACTIVE TUTORIAL ******************/\n /* Command parser */\n const commandName = commandArray[0];\n if (typeof commandName === \"number\") {\n this.error(`Command ${commandArray[0]} not found`);\n return;\n }\n\n const commands: {\n [key: string]: (\n terminal: ITerminal,\n router: IRouter,\n player: IPlayer,\n server: BaseServer,\n args: (string | number)[],\n ) => void;\n } = {\n alias: alias,\n analyze: analyze,\n backdoor: backdoor,\n buy: buy,\n cat: cat,\n cd: cd,\n check: check,\n cls: () => this.clear(),\n clear: () => this.clear(),\n connect: connect,\n download: download,\n expr: expr,\n free: free,\n hack: hack,\n help: help,\n home: home,\n hostname: hostname,\n ifconfig: ifconfig,\n kill: kill,\n killall: killall,\n ls: ls,\n lscpu: lscpu,\n mem: mem,\n mv: mv,\n nano: nano,\n ps: ps,\n rm: rm,\n run: run,\n scan: scan,\n \"scan-analyze\": scananalyze,\n scp: scp,\n sudov: sudov,\n tail: tail,\n top: top,\n unalias: unalias,\n wget: wget,\n };\n\n const f = commands[commandName.toLowerCase()];\n if (!f) {\n this.error(`Command ${commandArray[0]} not found`);\n return;\n }\n\n f(this, router, player, s, commandArray.slice(1));\n }\n\n getProgressText(): string {\n if (this.action === null) throw new Error(\"trying to get the progress text when there's no action\");\n return createProgressBarText({\n progress: (this.action.time - this.action.timeLeft) / this.action.time,\n totalTicks: 50,\n });\n }\n}\n","import { getRandomInt } from \"../utils/helpers/getRandomInt\";\n\n/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */\n\n/* Function that generates a valid 'data' for a contract type */\nexport type GeneratorFunc = () => any;\n\n/* Function that checks if the provided solution is the correct one */\nexport type SolverFunc = (data: any, answer: string) => boolean;\n\n/* Function that returns a string with the problem's description.\n Requires the 'data' of a Contract as input */\nexport type DescriptionFunc = (data: any) => string;\n\nexport interface ICodingContractTypeMetadata {\n desc: DescriptionFunc;\n difficulty: number;\n gen: GeneratorFunc;\n name: string;\n numTries: number;\n solver: SolverFunc;\n}\n\n/* Helper functions for Coding Contract implementations */\nfunction removeBracketsFromArrayString(str: string): string {\n let strCpy: string = str;\n if (strCpy.startsWith(\"[\")) {\n strCpy = strCpy.slice(1);\n }\n if (strCpy.endsWith(\"]\")) {\n strCpy = strCpy.slice(0, -1);\n }\n\n return strCpy;\n}\n\nfunction removeQuotesFromString(str: string): string {\n let strCpy: string = str;\n if (strCpy.startsWith('\"') || strCpy.startsWith(\"'\")) {\n strCpy = strCpy.slice(1);\n }\n if (strCpy.endsWith('\"') || strCpy.endsWith(\"'\")) {\n strCpy = strCpy.slice(0, -1);\n }\n\n return strCpy;\n}\n\nfunction convert2DArrayToString(arr: any[][]): string {\n const components: string[] = [];\n arr.forEach((e: any) => {\n let s: string = e.toString();\n s = [\"[\", s, \"]\"].join(\"\");\n components.push(s);\n });\n\n return components.join(\",\").replace(/\\s/g, \"\");\n}\n\nexport const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [\n {\n desc: (n: number): string => {\n return [\"A prime factor is a factor that is a prime number.\", `What is the largest prime factor of ${n}?`].join(\n \" \",\n );\n },\n difficulty: 1,\n gen: (): number => {\n return getRandomInt(500, 1e9);\n },\n name: \"Find Largest Prime Factor\",\n numTries: 10,\n solver: (data: number, ans: string): boolean => {\n let fac = 2;\n let n: number = data;\n while (n > (fac - 1) * (fac - 1)) {\n while (n % fac === 0) {\n n = Math.round(n / fac);\n }\n ++fac;\n }\n\n return (n === 1 ? fac - 1 : n) === parseInt(ans, 10);\n },\n },\n {\n desc: (n: number[]): string => {\n return [\n \"Given the following integer array, find the contiguous subarray\",\n \"(containing at least one number) which has the largest sum and return that sum.\",\n \"'Sum' refers to the sum of all the numbers in the subarray.\\n\",\n `${n.toString()}`,\n ].join(\" \");\n },\n difficulty: 1,\n gen: (): number[] => {\n const len: number = getRandomInt(5, 40);\n const arr: number[] = [];\n arr.length = len;\n for (let i = 0; i < len; ++i) {\n arr[i] = getRandomInt(-10, 10);\n }\n\n return arr;\n },\n name: \"Subarray with Maximum Sum\",\n numTries: 10,\n solver: (data: number[], ans: string): boolean => {\n const nums: number[] = data.slice();\n for (let i = 1; i < nums.length; i++) {\n nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);\n }\n\n return parseInt(ans, 10) === Math.max(...nums);\n },\n },\n {\n desc: (n: number): string => {\n return [\n \"It is possible write four as a sum in exactly four different ways:\\n\\n\",\n \"    3 + 1\\n\",\n \"    2 + 2\\n\",\n \"    2 + 1 + 1\\n\",\n \"    1 + 1 + 1 + 1\\n\\n\",\n `How many different ways can the number ${n} be written as a sum of at least`,\n \"two positive integers?\",\n ].join(\" \");\n },\n difficulty: 1.5,\n gen: (): number => {\n return getRandomInt(8, 100);\n },\n name: \"Total Ways to Sum\",\n numTries: 10,\n solver: (data: number, ans: string): boolean => {\n const ways: number[] = [1];\n ways.length = data + 1;\n ways.fill(0, 1);\n for (let i = 1; i < data; ++i) {\n for (let j: number = i; j <= data; ++j) {\n ways[j] += ways[j - i];\n }\n }\n\n return ways[data] === parseInt(ans, 10);\n },\n },\n {\n desc: (n: number[][]): string => {\n let d: string = [\n \"Given the following array of array of numbers representing a 2D matrix,\",\n \"return the elements of the matrix as an array in spiral order:\\n\\n\",\n ].join(\" \");\n // for (const line of n) {\n // d += `${line.toString()},\\n`;\n // }\n d += \"    [\\n\";\n d += n\n .map(\n (line: number[]) =>\n \"        [\" +\n line.map((x: number) => `${x}`.padStart(2, \" \")).join(\",\") +\n \"]\",\n )\n .join(\"\\n\");\n d += \"\\n    ]\\n\";\n d += [\n \"\\nHere is an example of what spiral order should be:\\n\\n\",\n \"    [\\n\",\n \"        [1, 2, 3]\\n\",\n \"        [4, 5, 6]\\n\",\n \"        [7, 8, 9]\\n\",\n \"    ]\\n\\n\",\n \"Answer: [1, 2, 3, 6, 9, 8 ,7, 4, 5]\\n\\n\",\n \"Note that the matrix will not always be square:\\n\\n\",\n \"    [\\n\",\n \"        [1,  2,  3,  4]\\n\",\n \"        [5,  6,  7,  8]\\n\",\n \"        [9, 10, 11, 12]\\n\",\n \"    ]\\n\\n\",\n \"Answer: [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]\",\n ].join(\" \");\n\n return d;\n },\n difficulty: 2,\n gen: (): number[][] => {\n const m: number = getRandomInt(1, 15);\n const n: number = getRandomInt(1, 15);\n const matrix: number[][] = [];\n matrix.length = m;\n for (let i = 0; i < m; ++i) {\n matrix[i] = [];\n matrix[i].length = n;\n }\n\n for (let i = 0; i < m; ++i) {\n for (let j = 0; j < n; ++j) {\n matrix[i][j] = getRandomInt(1, 50);\n }\n }\n\n return matrix;\n },\n name: \"Spiralize Matrix\",\n numTries: 10,\n solver: (data: number[][], ans: string): boolean => {\n const spiral: number[] = [];\n const m: number = data.length;\n const n: number = data[0].length;\n let u = 0;\n let d: number = m - 1;\n let l = 0;\n let r: number = n - 1;\n let k = 0;\n while (true) {\n // Up\n for (let col: number = l; col <= r; col++) {\n spiral[k] = data[u][col];\n ++k;\n }\n if (++u > d) {\n break;\n }\n\n // Right\n for (let row: number = u; row <= d; row++) {\n spiral[k] = data[row][r];\n ++k;\n }\n if (--r < l) {\n break;\n }\n\n // Down\n for (let col: number = r; col >= l; col--) {\n spiral[k] = data[d][col];\n ++k;\n }\n if (--d < u) {\n break;\n }\n\n // Left\n for (let row: number = d; row >= u; row--) {\n spiral[k] = data[row][l];\n ++k;\n }\n if (++l > r) {\n break;\n }\n }\n\n const sanitizedPlayerAns: string = removeBracketsFromArrayString(ans).replace(/\\s/g, \"\");\n const playerAns: any[] = sanitizedPlayerAns.split(\",\");\n for (let i = 0; i < playerAns.length; ++i) {\n playerAns[i] = parseInt(playerAns[i], 10);\n }\n if (spiral.length !== playerAns.length) {\n return false;\n }\n for (let i = 0; i < spiral.length; ++i) {\n if (spiral[i] !== playerAns[i]) {\n return false;\n }\n }\n\n return true;\n },\n },\n {\n desc: (arr: number[]): string => {\n return [\n \"You are given the following array of integers:\\n\\n\",\n `${arr}\\n\\n`,\n \"Each element in the array represents your MAXIMUM jump length\",\n \"at that position. This means that if you are at position i and your\",\n \"maximum jump length is n, you can jump to any position from\",\n \"i to i+n.\",\n \"\\n\\nAssuming you are initially positioned\",\n \"at the start of the array, determine whether you are\",\n \"able to reach the last index exactly.\\n\\n\",\n \"Your answer should be submitted as 1 or 0, representing true and false respectively\",\n ].join(\" \");\n },\n difficulty: 2.5,\n gen: (): number[] => {\n const len: number = getRandomInt(3, 25);\n const arr: number[] = [];\n arr.length = len;\n for (let i = 0; i < arr.length; ++i) {\n if (Math.random() < 0.2) {\n arr[i] = 0; // 20% chance of being 0\n } else {\n arr[i] = getRandomInt(0, 10);\n }\n }\n\n return arr;\n },\n name: \"Array Jumping Game\",\n numTries: 1,\n solver: (data: number[], ans: string): boolean => {\n const n: number = data.length;\n let i = 0;\n for (let reach = 0; i < n && i <= reach; ++i) {\n reach = Math.max(i + data[i], reach);\n }\n const solution: boolean = i === n;\n\n if (ans === \"1\" && solution) {\n return true;\n }\n if (ans === \"0\" && !solution) {\n return true;\n }\n\n return false;\n },\n },\n {\n desc: (arr: number[][]): string => {\n return [\n \"Given the following array of array of numbers representing a list of\",\n \"intervals, merge all overlapping intervals.\\n\\n\",\n `[${convert2DArrayToString(arr)}]\\n\\n`,\n \"Example:\\n\\n\",\n \"[[1, 3], [8, 10], [2, 6], [10, 16]]\\n\\n\",\n \"would merge into [[1, 6], [8, 16]].\\n\\n\",\n \"The intervals must be returned in ASCENDING order.\",\n \"You can assume that in an interval, the first number will always be\",\n \"smaller than the second.\",\n ].join(\" \");\n },\n difficulty: 3,\n gen: (): number[][] => {\n const intervals: number[][] = [];\n const numIntervals: number = getRandomInt(3, 20);\n for (let i = 0; i < numIntervals; ++i) {\n const start: number = getRandomInt(1, 25);\n const end: number = start + getRandomInt(1, 10);\n intervals.push([start, end]);\n }\n\n return intervals;\n },\n name: \"Merge Overlapping Intervals\",\n numTries: 15,\n solver: (data: number[][], ans: string): boolean => {\n const intervals: number[][] = data.slice();\n intervals.sort((a: number[], b: number[]) => {\n return a[0] - b[0];\n });\n\n const result: number[][] = [];\n let start: number = intervals[0][0];\n let end: number = intervals[0][1];\n for (const interval of intervals) {\n if (interval[0] <= end) {\n end = Math.max(end, interval[1]);\n } else {\n result.push([start, end]);\n start = interval[0];\n end = interval[1];\n }\n }\n result.push([start, end]);\n\n const sanitizedResult: string = convert2DArrayToString(result);\n const sanitizedAns: string = ans.replace(/\\s/g, \"\");\n\n return sanitizedResult === sanitizedAns || sanitizedResult === removeBracketsFromArrayString(sanitizedAns);\n },\n },\n {\n desc: (data: string): string => {\n return [\n \"Given the following string containing only digits, return\",\n \"an array with all possible valid IP address combinations\",\n \"that can be created from the string:\\n\\n\",\n `${data}\\n\\n`,\n \"Note that an octet cannot begin with a '0' unless the number\",\n \"itself is actually 0. For example, '192.168.010.1' is not a valid IP.\\n\\n\",\n \"Examples:\\n\\n\",\n \"25525511135 -> [255.255.11.135, 255.255.111.35]\\n\",\n \"1938718066 -> [193.87.180.66]\",\n ].join(\" \");\n },\n difficulty: 3,\n gen: (): string => {\n let str = \"\";\n for (let i = 0; i < 4; ++i) {\n const num: number = getRandomInt(0, 255);\n const convNum: string = num.toString();\n str += convNum;\n }\n\n return str;\n },\n name: \"Generate IP Addresses\",\n numTries: 10,\n solver: (data: string, ans: string): boolean => {\n const ret: string[] = [];\n for (let a = 1; a <= 3; ++a) {\n for (let b = 1; b <= 3; ++b) {\n for (let c = 1; c <= 3; ++c) {\n for (let d = 1; d <= 3; ++d) {\n if (a + b + c + d === data.length) {\n const A: number = parseInt(data.substring(0, a), 10);\n const B: number = parseInt(data.substring(a, a + b), 10);\n const C: number = parseInt(data.substring(a + b, a + b + c), 10);\n const D: number = parseInt(data.substring(a + b + c, a + b + c + d), 10);\n if (A <= 255 && B <= 255 && C <= 255 && D <= 255) {\n const ip: string = [A.toString(), \".\", B.toString(), \".\", C.toString(), \".\", D.toString()].join(\"\");\n if (ip.length === data.length + 3) {\n ret.push(ip);\n }\n }\n }\n }\n }\n }\n }\n\n const sanitizedAns: string = removeBracketsFromArrayString(ans).replace(/\\s/g, \"\");\n const ansArr: string[] = sanitizedAns.split(\",\");\n if (ansArr.length !== ret.length) {\n return false;\n }\n for (const ipInAns of ansArr) {\n if (!ret.includes(ipInAns)) {\n return false;\n }\n }\n\n return true;\n },\n },\n {\n desc: (data: number[]): string => {\n return [\n \"You are given the following array of stock prices (which are numbers)\",\n \"where the i-th element represents the stock price on day i:\\n\\n\",\n `${data}\\n\\n`,\n \"Determine the maximum possible profit you can earn using at most\",\n \"one transaction (i.e. you can only buy and sell the stock once). If no profit can be made\",\n \"then the answer should be 0. Note\",\n \"that you have to buy the stock before you can sell it\",\n ].join(\" \");\n },\n difficulty: 1,\n gen: (): number[] => {\n const len: number = getRandomInt(3, 50);\n const arr: number[] = [];\n arr.length = len;\n for (let i = 0; i < len; ++i) {\n arr[i] = getRandomInt(1, 200);\n }\n\n return arr;\n },\n name: \"Algorithmic Stock Trader I\",\n numTries: 5,\n solver: (data: number[], ans: string): boolean => {\n let maxCur = 0;\n let maxSoFar = 0;\n for (let i = 1; i < data.length; ++i) {\n maxCur = Math.max(0, (maxCur += data[i] - data[i - 1]));\n maxSoFar = Math.max(maxCur, maxSoFar);\n }\n\n return maxSoFar.toString() === ans;\n },\n },\n {\n desc: (data: number[]): string => {\n return [\n \"You are given the following array of stock prices (which are numbers)\",\n \"where the i-th element represents the stock price on day i:\\n\\n\",\n `${data}\\n\\n`,\n \"Determine the maximum possible profit you can earn using as many\",\n \"transactions as you'd like. A transaction is defined as buying\",\n \"and then selling one share of the stock. Note that you cannot\",\n \"engage in multiple transactions at once. In other words, you\",\n \"must sell the stock before you buy it again.\\n\\n\",\n \"If no profit can be made, then the answer should be 0\",\n ].join(\" \");\n },\n difficulty: 2,\n gen: (): number[] => {\n const len: number = getRandomInt(3, 50);\n const arr: number[] = [];\n arr.length = len;\n for (let i = 0; i < len; ++i) {\n arr[i] = getRandomInt(1, 200);\n }\n\n return arr;\n },\n name: \"Algorithmic Stock Trader II\",\n numTries: 10,\n solver: (data: number[], ans: string): boolean => {\n let profit = 0;\n for (let p = 1; p < data.length; ++p) {\n profit += Math.max(data[p] - data[p - 1], 0);\n }\n\n return profit.toString() === ans;\n },\n },\n {\n desc: (data: number[]): string => {\n return [\n \"You are given the following array of stock prices (which are numbers)\",\n \"where the i-th element represents the stock price on day i:\\n\\n\",\n `${data}\\n\\n`,\n \"Determine the maximum possible profit you can earn using at most\",\n \"two transactions. A transaction is defined as buying\",\n \"and then selling one share of the stock. Note that you cannot\",\n \"engage in multiple transactions at once. In other words, you\",\n \"must sell the stock before you buy it again.\\n\\n\",\n \"If no profit can be made, then the answer should be 0\",\n ].join(\" \");\n },\n difficulty: 5,\n gen: (): number[] => {\n const len: number = getRandomInt(3, 50);\n const arr: number[] = [];\n arr.length = len;\n for (let i = 0; i < len; ++i) {\n arr[i] = getRandomInt(1, 200);\n }\n\n return arr;\n },\n name: \"Algorithmic Stock Trader III\",\n numTries: 10,\n solver: (data: number[], ans: string): boolean => {\n let hold1: number = Number.MIN_SAFE_INTEGER;\n let hold2: number = Number.MIN_SAFE_INTEGER;\n let release1 = 0;\n let release2 = 0;\n for (const price of data) {\n release2 = Math.max(release2, hold2 + price);\n hold2 = Math.max(hold2, release1 - price);\n release1 = Math.max(release1, hold1 + price);\n hold1 = Math.max(hold1, price * -1);\n }\n\n return release2.toString() === ans;\n },\n },\n {\n desc: (data: any[]): string => {\n const k: number = data[0];\n const prices: number[] = data[1];\n return [\n \"You are given the following array with two elements:\\n\\n\",\n `[${k}, [${prices}]]\\n\\n`,\n \"The first element is an integer k. The second element is an\",\n \"array of stock prices (which are numbers) where the i-th element\",\n \"represents the stock price on day i.\\n\\n\",\n \"Determine the maximum possible profit you can earn using at most\",\n \"k transactions. A transaction is defined as buying and then selling\",\n \"one share of the stock. Note that you cannot engage in multiple\",\n \"transactions at once. In other words, you must sell the stock before\",\n \"you can buy it again.\\n\\n\",\n \"If no profit can be made, then the answer should be 0.\",\n ].join(\" \");\n },\n difficulty: 8,\n gen: (): any[] => {\n const k: number = getRandomInt(2, 10);\n const len: number = getRandomInt(3, 50);\n const prices: number[] = [];\n prices.length = len;\n for (let i = 0; i < len; ++i) {\n prices[i] = getRandomInt(1, 200);\n }\n\n return [k, prices];\n },\n name: \"Algorithmic Stock Trader IV\",\n numTries: 10,\n solver: (data: any[], ans: string): boolean => {\n const k: number = data[0];\n const prices: number[] = data[1];\n\n const len = prices.length;\n if (len < 2) {\n return parseInt(ans) === 0;\n }\n if (k > len / 2) {\n let res = 0;\n for (let i = 1; i < len; ++i) {\n res += Math.max(prices[i] - prices[i - 1], 0);\n }\n\n return parseInt(ans) === res;\n }\n\n const hold: number[] = [];\n const rele: number[] = [];\n hold.length = k + 1;\n rele.length = k + 1;\n for (let i = 0; i <= k; ++i) {\n hold[i] = Number.MIN_SAFE_INTEGER;\n rele[i] = 0;\n }\n\n let cur: number;\n for (let i = 0; i < len; ++i) {\n cur = prices[i];\n for (let j = k; j > 0; --j) {\n rele[j] = Math.max(rele[j], hold[j] + cur);\n hold[j] = Math.max(hold[j], rele[j - 1] - cur);\n }\n }\n\n return parseInt(ans) === rele[k];\n },\n },\n {\n desc: (data: number[][]): string => {\n function createTriangleRecurse(data: number[][], level = 0): string {\n const numLevels: number = data.length;\n if (level >= numLevels) {\n return \"\";\n }\n const numSpaces = numLevels - level + 1;\n\n let str: string = [\" \".repeat(numSpaces), \"[\", data[level].toString(), \"]\"].join(\"\");\n if (level < numLevels - 1) {\n str += \",\";\n }\n\n return str + \"\\n\" + createTriangleRecurse(data, level + 1);\n }\n\n function createTriangle(data: number[][]): string {\n return [\"[\\n\", createTriangleRecurse(data), \"]\"].join(\"\");\n }\n\n const triangle = createTriangle(data);\n\n return [\n \"Given a triangle, find the minimum path sum from top to bottom. In each step\",\n \"of the path, you may only move to adjacent numbers in the row below.\",\n \"The triangle is represented as a 2D array of numbers:\\n\\n\",\n `${triangle}\\n\\n`,\n \"Example: If you are given the following triangle:\\n\\n\" + \"[\\n\",\n \"     [2],\\n\",\n \"    [3,4],\\n\",\n \"   [6,5,7],\\n\",\n \"  [4,1,8,3]\\n\",\n \"]\\n\\n\",\n \"The minimum path sum is 11 (2 -> 3 -> 5 -> 1).\",\n ].join(\" \");\n },\n difficulty: 5,\n gen: (): number[][] => {\n const triangle: number[][] = [];\n const levels: number = getRandomInt(3, 12);\n triangle.length = levels;\n\n for (let row = 0; row < levels; ++row) {\n triangle[row] = [];\n triangle[row].length = row + 1;\n for (let i = 0; i < triangle[row].length; ++i) {\n triangle[row][i] = getRandomInt(1, 9);\n }\n }\n\n return triangle;\n },\n name: \"Minimum Path Sum in a Triangle\",\n numTries: 10,\n solver: (data: number[][], ans: string): boolean => {\n const n: number = data.length;\n const dp: number[] = data[n - 1].slice();\n for (let i = n - 2; i > -1; --i) {\n for (let j = 0; j < data[i].length; ++j) {\n dp[j] = Math.min(dp[j], dp[j + 1]) + data[i][j];\n }\n }\n\n return dp[0] === parseInt(ans);\n },\n },\n {\n desc: (data: number[]): string => {\n const numRows = data[0];\n const numColumns = data[1];\n return [\n \"You are in a grid with\",\n `${numRows} rows and ${numColumns} columns, and you are`,\n \"positioned in the top-left corner of that grid. You are trying to\",\n \"reach the bottom-right corner of the grid, but you can only\",\n \"move down or right on each step. Determine how many\",\n \"unique paths there are from start to finish.\\n\\n\",\n \"NOTE: The data returned for this contract is an array\",\n \"with the number of rows and columns:\\n\\n\",\n `[${numRows}, ${numColumns}]`,\n ].join(\" \");\n },\n difficulty: 3,\n gen: (): number[] => {\n const numRows: number = getRandomInt(2, 14);\n const numColumns: number = getRandomInt(2, 14);\n\n return [numRows, numColumns];\n },\n name: \"Unique Paths in a Grid I\",\n numTries: 10,\n solver: (data: number[], ans: string): boolean => {\n const n: number = data[0]; // Number of rows\n const m: number = data[1]; // Number of columns\n const currentRow: number[] = [];\n currentRow.length = n;\n\n for (let i = 0; i < n; i++) {\n currentRow[i] = 1;\n }\n for (let row = 1; row < m; row++) {\n for (let i = 1; i < n; i++) {\n currentRow[i] += currentRow[i - 1];\n }\n }\n\n return parseInt(ans) === currentRow[n - 1];\n },\n },\n {\n desc: (data: number[][]): string => {\n let gridString = \"\";\n for (const line of data) {\n gridString += `${line.toString()},\\n`;\n }\n return [\n \"You are located in the top-left corner of the following grid:\\n\\n\",\n `${gridString}\\n`,\n \"You are trying reach the bottom-right corner of the grid, but you can only\",\n \"move down or right on each step. Furthermore, there are obstacles on the grid\",\n \"that you cannot move onto. These obstacles are denoted by '1', while empty\",\n \"spaces are denoted by 0.\\n\\n\",\n \"Determine how many unique paths there are from start to finish.\\n\\n\",\n \"NOTE: The data returned for this contract is an 2D array of numbers representing the grid.\",\n ].join(\" \");\n },\n difficulty: 5,\n gen: (): number[][] => {\n const numRows: number = getRandomInt(2, 12);\n const numColumns: number = getRandomInt(2, 12);\n\n const grid: number[][] = [];\n grid.length = numRows;\n for (let i = 0; i < numRows; ++i) {\n grid[i] = [];\n grid[i].length = numColumns;\n grid[i].fill(0);\n }\n\n for (let r = 0; r < numRows; ++r) {\n for (let c = 0; c < numColumns; ++c) {\n if (r === 0 && c === 0) {\n continue;\n }\n if (r === numRows - 1 && c === numColumns - 1) {\n continue;\n }\n\n // 15% chance of an element being an obstacle\n if (Math.random() < 0.15) {\n grid[r][c] = 1;\n }\n }\n }\n\n return grid;\n },\n name: \"Unique Paths in a Grid II\",\n numTries: 10,\n solver: (data: number[][], ans: string): boolean => {\n const obstacleGrid: number[][] = [];\n obstacleGrid.length = data.length;\n for (let i = 0; i < obstacleGrid.length; ++i) {\n obstacleGrid[i] = data[i].slice();\n }\n\n for (let i = 0; i < obstacleGrid.length; i++) {\n for (let j = 0; j < obstacleGrid[0].length; j++) {\n if (obstacleGrid[i][j] == 1) {\n obstacleGrid[i][j] = 0;\n } else if (i == 0 && j == 0) {\n obstacleGrid[0][0] = 1;\n } else {\n obstacleGrid[i][j] = (i > 0 ? obstacleGrid[i - 1][j] : 0) + (j > 0 ? obstacleGrid[i][j - 1] : 0);\n }\n }\n }\n\n return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] === parseInt(ans);\n },\n },\n {\n desc: (data: string): string => {\n return [\n \"Given the following string:\\n\\n\",\n `${data}\\n\\n`,\n \"remove the minimum number of invalid parentheses in order to validate\",\n \"the string. If there are multiple minimal ways to validate the string,\",\n \"provide all of the possible results. The answer should be provided\",\n \"as an array of strings. If it is impossible to validate the string\",\n \"the result should be an array with only an empty string.\\n\\n\",\n \"IMPORTANT: The string may contain letters, not just parentheses.\",\n `Examples:\\n`,\n `\"()())()\" -> [()()(), (())()]\\n`,\n `\"(a)())()\" -> [(a)()(), (a())()]\\n`,\n `\")( -> [\"\"]`,\n ].join(\" \");\n },\n difficulty: 10,\n gen: (): string => {\n const len: number = getRandomInt(6, 20);\n const chars: string[] = [];\n chars.length = len;\n\n // 80% chance of the first parenthesis being (\n Math.random() < 0.8 ? (chars[0] = \"(\") : (chars[0] = \")\");\n\n for (let i = 1; i < len; ++i) {\n const roll = Math.random();\n if (roll < 0.4) {\n chars[i] = \"(\";\n } else if (roll < 0.8) {\n chars[i] = \")\";\n } else {\n chars[i] = \"a\";\n }\n }\n\n return chars.join(\"\");\n },\n name: \"Sanitize Parentheses in Expression\",\n numTries: 10,\n solver: (data: string, ans: string): boolean => {\n let left = 0;\n let right = 0;\n const res: string[] = [];\n\n for (let i = 0; i < data.length; ++i) {\n if (data[i] === \"(\") {\n ++left;\n } else if (data[i] === \")\") {\n left > 0 ? --left : ++right;\n }\n }\n\n function dfs(\n pair: number,\n index: number,\n left: number,\n right: number,\n s: string,\n solution: string,\n res: string[],\n ): void {\n if (s.length === index) {\n if (left === 0 && right === 0 && pair === 0) {\n for (let i = 0; i < res.length; i++) {\n if (res[i] === solution) {\n return;\n }\n }\n res.push(solution);\n }\n return;\n }\n\n if (s[index] === \"(\") {\n if (left > 0) {\n dfs(pair, index + 1, left - 1, right, s, solution, res);\n }\n dfs(pair + 1, index + 1, left, right, s, solution + s[index], res);\n } else if (s[index] === \")\") {\n if (right > 0) dfs(pair, index + 1, left, right - 1, s, solution, res);\n if (pair > 0) dfs(pair - 1, index + 1, left, right, s, solution + s[index], res);\n } else {\n dfs(pair, index + 1, left, right, s, solution + s[index], res);\n }\n }\n\n dfs(0, 0, left, right, data, \"\", res);\n\n const sanitizedPlayerAns = removeBracketsFromArrayString(ans).replace(/\\s/g, \"\");\n\n const playerAnsArray: string[] = sanitizedPlayerAns.split(\",\");\n if (playerAnsArray.length !== res.length) {\n return false;\n }\n for (const resultInAnswer of res) {\n if (!playerAnsArray.includes(resultInAnswer)) {\n return false;\n }\n }\n\n return true;\n },\n },\n {\n desc: (data: any[]): string => {\n const digits: string = data[0];\n const target: number = data[1];\n\n return [\n \"You are given the following string which contains only digits between 0 and 9:\\n\\n\",\n `${digits}\\n\\n`,\n `You are also given a target number of ${target}. Return all possible ways`,\n \"you can add the +, -, and * operators to the string such that it evaluates\",\n \"to the target number.\\n\\n\",\n \"The provided answer should be an array of strings containing the valid expressions.\",\n \"The data provided by this problem is an array with two elements. The first element\",\n \"is the string of digits, while the second element is the target number:\\n\\n\",\n `[\"${digits}\", ${target}]\\n\\n`,\n \"NOTE: Numbers in the expression cannot have leading 0's. In other words,\",\n `\"1+01\" is not a valid expression`,\n \"Examples:\\n\\n\",\n `Input: digits = \"123\", target = 6\\n`,\n `Output: [1+2+3, 1*2*3]\\n\\n`,\n `Input: digits = \"105\", target = 5\\n`,\n `Output: [1*0+5, 10-5]`,\n ].join(\" \");\n },\n difficulty: 10,\n gen: (): any[] => {\n const numDigits = getRandomInt(4, 12);\n const digitsArray: string[] = [];\n digitsArray.length = numDigits;\n for (let i = 0; i < digitsArray.length; ++i) {\n if (i === 0) {\n digitsArray[i] = String(getRandomInt(1, 9));\n } else {\n digitsArray[i] = String(getRandomInt(0, 9));\n }\n }\n\n const target: number = getRandomInt(-100, 100);\n const digits: string = digitsArray.join(\"\");\n\n return [digits, target];\n },\n name: \"Find All Valid Math Expressions\",\n numTries: 10,\n solver: (data: any[], ans: string): boolean => {\n const num: string = data[0];\n const target: number = data[1];\n\n function helper(\n res: string[],\n path: string,\n num: string,\n target: number,\n pos: number,\n evaluated: number,\n multed: number,\n ): void {\n if (pos === num.length) {\n if (target === evaluated) {\n res.push(path);\n }\n return;\n }\n\n for (let i = pos; i < num.length; ++i) {\n if (i != pos && num[pos] == \"0\") {\n break;\n }\n const cur = parseInt(num.substring(pos, i + 1));\n\n if (pos === 0) {\n helper(res, path + cur, num, target, i + 1, cur, cur);\n } else {\n helper(res, path + \"+\" + cur, num, target, i + 1, evaluated + cur, cur);\n helper(res, path + \"-\" + cur, num, target, i + 1, evaluated - cur, -cur);\n helper(res, path + \"*\" + cur, num, target, i + 1, evaluated - multed + multed * cur, multed * cur);\n }\n }\n }\n\n const sanitizedPlayerAns: string = removeBracketsFromArrayString(ans);\n const sanitizedPlayerAnsArr: string[] = sanitizedPlayerAns.split(\",\");\n for (let i = 0; i < sanitizedPlayerAnsArr.length; ++i) {\n sanitizedPlayerAnsArr[i] = removeQuotesFromString(sanitizedPlayerAnsArr[i]).replace(/\\s/g, \"\");\n }\n\n if (num == null || num.length === 0) {\n if (sanitizedPlayerAnsArr.length === 0) {\n return true;\n }\n if (sanitizedPlayerAnsArr.length === 1 && sanitizedPlayerAnsArr[0] === \"\") {\n return true;\n }\n return false;\n }\n\n const result: string[] = [];\n helper(result, \"\", num, target, 0, 0, 0);\n\n for (const expr of result) {\n if (!sanitizedPlayerAnsArr.includes(expr)) {\n return false;\n }\n }\n\n return true;\n },\n },\n];\n","// Function that generates a random gibberish string of length n\nconst chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n\nexport function createRandomString(n: number): string {\n let str = \"\";\n\n for (let i = 0; i < n; ++i) {\n str += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n\n return str;\n}\n","import { BaseServer } from \"../Server/BaseServer\";\nimport { ITerminal } from \"../Terminal/ITerminal\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { IRouter } from \"../ui/Router\";\n\nexport interface IProgramCreate {\n level: number;\n req(p: IPlayer): boolean; // Function that indicates whether player meets requirements\n time: number;\n tooltip: string;\n}\n\nexport class Program {\n name = \"\";\n create: IProgramCreate | null;\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;\n\n constructor(\n name: string,\n create: IProgramCreate | null,\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void,\n ) {\n this.name = name;\n this.create = create;\n this.run = run;\n }\n\n htmlID(): string {\n const name = this.name.endsWith(\".exe\") ? this.name.slice(0, -\".exe\".length) : this.name;\n return \"create-program-\" + name;\n }\n}\n","import { IProgramCreate } from \"../Program\";\nimport { CONSTANTS } from \"../../Constants\";\nimport { BaseServer } from \"../../Server/BaseServer\";\nimport { Server } from \"../../Server/Server\";\nimport { ITerminal } from \"../../Terminal/ITerminal\";\nimport { IRouter } from \"../../ui/Router\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { HacknetServer } from \"../../Hacknet/HacknetServer\";\nimport { convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { getServer } from \"../../Server/ServerHelpers\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { BitNodeMultipliers } from \"../../BitNode/BitNodeMultipliers\";\nimport { BitFlumeEvent } from \"../../BitNode/ui/BitFlumeModal\";\nimport { calculateHackingTime, calculateGrowTime, calculateWeakenTime } from \"../../Hacking\";\n\nfunction requireHackingLevel(lvl: number) {\n return function (p: IPlayer) {\n return p.hacking_skill >= lvl;\n };\n}\n\nfunction bitFlumeRequirements() {\n return function (p: IPlayer) {\n return p.sourceFiles.length > 0 && p.hacking_skill >= 1;\n };\n}\n\nexport interface IProgramCreationParams {\n key: string;\n name: string;\n create: IProgramCreate | null;\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]) => void;\n}\n\nexport const programsMetadata: IProgramCreationParams[] = [\n {\n key: \"NukeProgram\",\n name: \"NUKE.exe\",\n create: {\n level: 1,\n tooltip: \"This virus is used to gain root access to a machine if enough ports are opened.\",\n req: requireHackingLevel(1),\n time: CONSTANTS.MillisecondsPerFiveMinutes,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {\n if (!(server instanceof Server)) {\n terminal.error(\"Cannot nuke this kind of server.\");\n return;\n }\n if (server.hasAdminRights) {\n terminal.print(\"You already have root access to this computer. There is no reason to run NUKE.exe\");\n return;\n }\n if (server.openPortCount >= server.numOpenPortsRequired) {\n server.hasAdminRights = true;\n terminal.print(\"NUKE successful! Gained root access to \" + server.hostname);\n // TODO: Make this take time rather than be instant\n return;\n }\n\n terminal.print(\"NUKE unsuccessful. Not enough ports have been opened\");\n },\n },\n {\n key: \"BruteSSHProgram\",\n name: \"BruteSSH.exe\",\n create: {\n level: 50,\n tooltip: \"This program executes a brute force attack that opens SSH ports\",\n req: requireHackingLevel(50),\n time: CONSTANTS.MillisecondsPerFiveMinutes * 2,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {\n if (!(server instanceof Server)) {\n terminal.error(\"Cannot run BruteSSH.exe on this kind of server.\");\n return;\n }\n if (server.sshPortOpen) {\n terminal.print(\"SSH Port (22) is already open!\");\n return;\n }\n\n server.sshPortOpen = true;\n terminal.print(\"Opened SSH Port(22)!\");\n server.openPortCount++;\n },\n },\n {\n key: \"FTPCrackProgram\",\n name: \"FTPCrack.exe\",\n create: {\n level: 100,\n tooltip: \"This program cracks open FTP ports\",\n req: requireHackingLevel(100),\n time: CONSTANTS.MillisecondsPerHalfHour,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {\n if (!(server instanceof Server)) {\n terminal.error(\"Cannot run FTPCrack.exe on this kind of server.\");\n return;\n }\n if (server.ftpPortOpen) {\n terminal.print(\"FTP Port (21) is already open!\");\n return;\n }\n\n server.ftpPortOpen = true;\n terminal.print(\"Opened FTP Port (21)!\");\n server.openPortCount++;\n },\n },\n {\n key: \"RelaySMTPProgram\",\n name: \"relaySMTP.exe\",\n create: {\n level: 250,\n tooltip: \"This program opens SMTP ports by redirecting data\",\n req: requireHackingLevel(250),\n time: CONSTANTS.MillisecondsPer2Hours,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {\n if (!(server instanceof Server)) {\n terminal.error(\"Cannot run relaySMTP.exe on this kind of server.\");\n return;\n }\n if (server.smtpPortOpen) {\n terminal.print(\"SMTP Port (25) is already open!\");\n return;\n }\n\n server.smtpPortOpen = true;\n terminal.print(\"Opened SMTP Port (25)!\");\n server.openPortCount++;\n },\n },\n {\n key: \"HTTPWormProgram\",\n name: \"HTTPWorm.exe\",\n create: {\n level: 500,\n tooltip: \"This virus opens up HTTP ports\",\n req: requireHackingLevel(500),\n time: CONSTANTS.MillisecondsPer4Hours,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {\n if (!(server instanceof Server)) {\n terminal.error(\"Cannot run HTTPWorm.exe on this kind of server.\");\n return;\n }\n if (server.httpPortOpen) {\n terminal.print(\"HTTP Port (80) is already open!\");\n return;\n }\n\n server.httpPortOpen = true;\n terminal.print(\"Opened HTTP Port (80)!\");\n server.openPortCount++;\n },\n },\n {\n key: \"SQLInjectProgram\",\n name: \"SQLInject.exe\",\n create: {\n level: 750,\n tooltip: \"This virus opens SQL ports\",\n req: requireHackingLevel(750),\n time: CONSTANTS.MillisecondsPer8Hours,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer): void => {\n if (!(server instanceof Server)) {\n terminal.error(\"Cannot run SQLInject.exe on this kind of server.\");\n return;\n }\n if (server.sqlPortOpen) {\n terminal.print(\"SQL Port (1433) is already open!\");\n return;\n }\n\n server.sqlPortOpen = true;\n terminal.print(\"Opened SQL Port (1433)!\");\n server.openPortCount++;\n },\n },\n {\n key: \"DeepscanV1\",\n name: \"DeepscanV1.exe\",\n create: {\n level: 75,\n tooltip: \"This program allows you to use the scan-analyze command with a depth up to 5\",\n req: requireHackingLevel(75),\n time: CONSTANTS.MillisecondsPerQuarterHour,\n },\n run: (router: IRouter, terminal: ITerminal): void => {\n terminal.print(\"This executable cannot be run.\");\n terminal.print(\"DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5.\");\n },\n },\n {\n key: \"DeepscanV2\",\n name: \"DeepscanV2.exe\",\n create: {\n level: 400,\n tooltip: \"This program allows you to use the scan-analyze command with a depth up to 10\",\n req: requireHackingLevel(400),\n time: CONSTANTS.MillisecondsPer2Hours,\n },\n run: (router: IRouter, terminal: ITerminal): void => {\n terminal.print(\"This executable cannot be run.\");\n terminal.print(\"DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.\");\n },\n },\n {\n key: \"ServerProfiler\",\n name: \"ServerProfiler.exe\",\n create: {\n level: 75,\n tooltip: \"This program is used to display hacking and Netscript-related information about servers\",\n req: requireHackingLevel(75),\n time: CONSTANTS.MillisecondsPerHalfHour,\n },\n run: (router: IRouter, terminal: ITerminal, player: IPlayer, server: BaseServer, args: string[]): void => {\n if (args.length !== 1) {\n terminal.print(\"Must pass a server hostname or IP as an argument for ServerProfiler.exe\");\n return;\n }\n\n const targetServer = getServer(args[0]);\n if (targetServer == null) {\n terminal.print(\"Invalid server IP/hostname\");\n return;\n }\n\n if (targetServer instanceof HacknetServer) {\n terminal.print(`ServerProfiler.exe cannot be run on a Hacknet Server.`);\n return;\n }\n\n terminal.print(targetServer.hostname + \":\");\n terminal.print(\"Server base security level: \" + targetServer.baseDifficulty);\n terminal.print(\"Server current security level: \" + targetServer.hackDifficulty);\n terminal.print(\"Server growth rate: \" + targetServer.serverGrowth);\n terminal.print(\n `Netscript hack() execution time: ${convertTimeMsToTimeElapsedString(\n calculateHackingTime(targetServer, player) * 1000,\n true,\n )}`,\n );\n terminal.print(\n `Netscript grow() execution time: ${convertTimeMsToTimeElapsedString(\n calculateGrowTime(targetServer, player) * 1000,\n true,\n )}`,\n );\n terminal.print(\n `Netscript weaken() execution time: ${convertTimeMsToTimeElapsedString(\n calculateWeakenTime(targetServer, player) * 1000,\n true,\n )}`,\n );\n },\n },\n {\n key: \"AutoLink\",\n name: \"AutoLink.exe\",\n create: {\n level: 25,\n tooltip: \"This program allows you to directly connect to other servers through the 'scan-analyze' command\",\n req: requireHackingLevel(25),\n time: CONSTANTS.MillisecondsPerQuarterHour,\n },\n run: (router: IRouter, terminal: ITerminal): void => {\n terminal.print(\"This executable cannot be run.\");\n terminal.print(\"AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'.\");\n terminal.print(\"When using scan-analyze, click on a server's hostname to connect to it.\");\n },\n },\n {\n key: \"BitFlume\",\n name: \"b1t_flum3.exe\",\n create: {\n level: 1,\n tooltip: \"This program creates a portal to the BitNode Nexus (allows you to restart and switch BitNodes)\",\n req: bitFlumeRequirements(),\n time: CONSTANTS.MillisecondsPerFiveMinutes / 20,\n },\n run: (): void => {\n BitFlumeEvent.emit();\n },\n },\n {\n key: \"Flight\",\n name: \"fl1ght.exe\",\n create: null,\n run: (router: IRouter, terminal: ITerminal, player: IPlayer): void => {\n const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement * 30);\n const fulfilled =\n player.augmentations.length >= numAugReq && player.money.gt(1e11) && player.hacking_skill >= 2500;\n if (!fulfilled) {\n terminal.print(`Augmentations: ${player.augmentations.length} / ${numAugReq}`);\n terminal.print(`Money: ${numeralWrapper.formatMoney(player.money.toNumber())} / $100b`);\n terminal.print(`Hacking skill: ${player.hacking_skill} / 2500`);\n return;\n }\n\n terminal.print(\"We will contact you.\");\n terminal.print(\"-- Daedalus --\");\n },\n },\n];\n","// tslint:disable:max-file-line-count\n\n// This could actually be a JSON file as it should be constant metadata to be imported...\nimport { IMinMaxRange } from \"../../types\";\nimport { LocationName } from \"../../Locations/data/LocationNames\";\nimport { LiteratureNames } from \"../../Literature/data/LiteratureNames\";\n\n/**\n * The metadata describing the base state of servers on the network.\n * These values will be adjusted based on Bitnode multipliers when the Server objects are built out.\n */\ninterface IServerMetadata {\n /**\n * When populated, the base security level of the server.\n */\n hackDifficulty?: number | IMinMaxRange;\n\n /**\n * The DNS name of the server.\n */\n hostname: string;\n\n /**\n * When populated, the files will be added to the server when created.\n */\n literature?: string[];\n\n /**\n * When populated, the exponent of 2^x amount of RAM the server has.\n * This should be in the range of 1-20, to match the Player's max RAM.\n */\n maxRamExponent?: number | IMinMaxRange;\n\n /**\n * How much money the server starts out with.\n */\n moneyAvailable: number | IMinMaxRange;\n\n /**\n * The number of network layers away from the `home` server.\n * This value is between 1 and 15.\n * If this is not populated, @specialName should be.\n */\n networkLayer?: number | IMinMaxRange;\n\n /**\n * The number of ports that must be opened before the player can execute NUKE.\n */\n numOpenPortsRequired: number;\n\n /**\n * The organization that the server belongs to.\n */\n organizationName: string;\n\n /**\n * The minimum hacking level before the player can run NUKE.\n */\n requiredHackingSkill: number | IMinMaxRange;\n\n /**\n * The growth factor for the server.\n */\n serverGrowth?: number | IMinMaxRange;\n\n /**\n * A \"unique\" server that has special implications when the player manually hacks it.\n */\n specialName?: string;\n\n [key: string]: any;\n}\n\n/**\n * The metadata for building up the servers on the network.\n */\nexport const serverMetadata: IServerMetadata[] = [\n {\n hackDifficulty: 99,\n hostname: \"ecorp\",\n moneyAvailable: {\n max: 70e9,\n min: 30e9,\n },\n networkLayer: 15,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumECorp,\n requiredHackingSkill: {\n max: 1400,\n min: 1050,\n },\n serverGrowth: 99,\n specialName: LocationName.AevumECorp,\n },\n {\n hackDifficulty: 99,\n hostname: \"megacorp\",\n moneyAvailable: {\n max: 60e9,\n min: 40e9,\n },\n networkLayer: 15,\n numOpenPortsRequired: 5,\n organizationName: LocationName.Sector12MegaCorp,\n requiredHackingSkill: {\n max: 1350,\n min: 1100,\n },\n serverGrowth: 99,\n specialName: LocationName.Sector12MegaCorp,\n },\n {\n hackDifficulty: {\n max: 88,\n min: 72,\n },\n hostname: \"b-and-a\",\n moneyAvailable: {\n max: 30e9,\n min: 15e9,\n },\n networkLayer: 14,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumBachmanAndAssociates,\n requiredHackingSkill: {\n max: 1150,\n min: 900,\n },\n serverGrowth: {\n max: 80,\n min: 60,\n },\n specialName: LocationName.AevumBachmanAndAssociates,\n },\n {\n hackDifficulty: {\n max: 97,\n min: 88,\n },\n hostname: \"blade\",\n literature: [LiteratureNames.BeyondMan],\n maxRamExponent: {\n max: 9,\n min: 5,\n },\n moneyAvailable: {\n max: 40e9,\n min: 10e9,\n },\n networkLayer: 14,\n numOpenPortsRequired: 5,\n organizationName: LocationName.Sector12BladeIndustries,\n requiredHackingSkill: {\n max: 1200,\n min: 900,\n },\n serverGrowth: {\n max: 85,\n min: 55,\n },\n specialName: LocationName.Sector12BladeIndustries,\n },\n {\n hackDifficulty: 99,\n hostname: \"nwo\",\n literature: [LiteratureNames.TheHiddenWorld],\n moneyAvailable: {\n max: 40e9,\n min: 20e9,\n },\n networkLayer: 14,\n numOpenPortsRequired: 5,\n organizationName: LocationName.VolhavenNWO,\n requiredHackingSkill: {\n max: 1300,\n min: 950,\n },\n serverGrowth: {\n max: 95,\n min: 65,\n },\n specialName: LocationName.VolhavenNWO,\n },\n {\n hackDifficulty: {\n max: 65,\n min: 45,\n },\n hostname: \"clarkinc\",\n literature: [LiteratureNames.BeyondMan, LiteratureNames.CostOfImmortality],\n moneyAvailable: {\n max: 25e9,\n min: 15e9,\n },\n networkLayer: 14,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumClarkeIncorporated,\n requiredHackingSkill: {\n max: 1250,\n min: 950,\n },\n serverGrowth: {\n max: 75,\n min: 45,\n },\n specialName: LocationName.AevumClarkeIncorporated,\n },\n {\n hackDifficulty: {\n max: 99,\n min: 90,\n },\n hostname: \"omnitek\",\n literature: [LiteratureNames.CodedIntelligence, LiteratureNames.HistoryOfSynthoids],\n maxRamExponent: {\n max: 9,\n min: 7,\n },\n moneyAvailable: {\n max: 22e9,\n min: 13e9,\n },\n networkLayer: 13,\n numOpenPortsRequired: 5,\n organizationName: LocationName.VolhavenOmniTekIncorporated,\n requiredHackingSkill: {\n max: 1100,\n min: 900,\n },\n serverGrowth: {\n max: 99,\n min: 95,\n },\n specialName: LocationName.VolhavenOmniTekIncorporated,\n },\n {\n hackDifficulty: {\n max: 75,\n min: 55,\n },\n hostname: \"4sigma\",\n moneyAvailable: {\n max: 25e9,\n min: 15e9,\n },\n networkLayer: 13,\n numOpenPortsRequired: 5,\n organizationName: LocationName.Sector12FourSigma,\n requiredHackingSkill: {\n max: 1250,\n min: 900,\n },\n serverGrowth: {\n max: 99,\n min: 75,\n },\n specialName: LocationName.Sector12FourSigma,\n },\n {\n hackDifficulty: {\n max: 99,\n min: 95,\n },\n hostname: \"kuai-gong\",\n moneyAvailable: {\n max: 30e9,\n min: 20e9,\n },\n networkLayer: 13,\n numOpenPortsRequired: 5,\n organizationName: LocationName.ChongqingKuaiGongInternational,\n requiredHackingSkill: {\n max: 1300,\n min: 950,\n },\n serverGrowth: {\n max: 99,\n min: 90,\n },\n specialName: LocationName.ChongqingKuaiGongInternational,\n },\n {\n hackDifficulty: {\n max: 97,\n min: 83,\n },\n hostname: \"fulcrumtech\",\n literature: [LiteratureNames.SimulatedReality],\n maxRamExponent: {\n max: 11,\n min: 7,\n },\n moneyAvailable: {\n max: 1800e6,\n min: 1400e6,\n },\n networkLayer: 12,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumFulcrumTechnologies,\n requiredHackingSkill: {\n max: 1250,\n min: 950,\n },\n serverGrowth: {\n max: 99,\n min: 80,\n },\n specialName: LocationName.AevumFulcrumTechnologies,\n },\n {\n hackDifficulty: 99,\n hostname: \"fulcrumassets\",\n moneyAvailable: 1e6,\n networkLayer: 15,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumFulcrumTechnologies,\n requiredHackingSkill: {\n max: 1600,\n min: 1100,\n },\n serverGrowth: 1,\n specialName: \"Fulcrum Secret Technologies Server\",\n },\n {\n hackDifficulty: {\n max: 92,\n min: 78,\n },\n hostname: \"stormtech\",\n moneyAvailable: {\n max: 1200e6,\n min: 1000e6,\n },\n networkLayer: 12,\n numOpenPortsRequired: 5,\n organizationName: LocationName.IshimaStormTechnologies,\n requiredHackingSkill: {\n max: 1075,\n min: 875,\n },\n serverGrowth: {\n max: 92,\n min: 68,\n },\n specialName: LocationName.IshimaStormTechnologies,\n },\n {\n hackDifficulty: {\n max: 96,\n min: 84,\n },\n hostname: \"defcomm\",\n moneyAvailable: {\n max: 950e6,\n min: 800e6,\n },\n networkLayer: 9,\n numOpenPortsRequired: 5,\n organizationName: LocationName.NewTokyoDefComm,\n requiredHackingSkill: {\n max: 1050,\n min: 850,\n },\n serverGrowth: {\n max: 73,\n min: 47,\n },\n specialName: LocationName.NewTokyoDefComm,\n },\n {\n hackDifficulty: {\n max: 90,\n min: 70,\n },\n hostname: \"infocomm\",\n moneyAvailable: {\n max: 900e6,\n min: 600e6,\n },\n networkLayer: 10,\n numOpenPortsRequired: 5,\n organizationName: \"InfoComm\",\n requiredHackingSkill: {\n max: 950,\n min: 875,\n },\n serverGrowth: {\n max: 75,\n min: 35,\n },\n },\n {\n hackDifficulty: {\n max: 95,\n min: 85,\n },\n hostname: \"helios\",\n literature: [LiteratureNames.BeyondMan],\n maxRamExponent: {\n max: 8,\n min: 5,\n },\n moneyAvailable: {\n max: 750e6,\n min: 550e6,\n },\n networkLayer: 12,\n numOpenPortsRequired: 5,\n organizationName: LocationName.VolhavenHeliosLabs,\n requiredHackingSkill: {\n max: 900,\n min: 800,\n },\n serverGrowth: {\n max: 80,\n min: 70,\n },\n specialName: LocationName.VolhavenHeliosLabs,\n },\n {\n hackDifficulty: {\n max: 90,\n min: 80,\n },\n hostname: \"vitalife\",\n literature: [LiteratureNames.AGreenTomorrow],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 800e6,\n min: 700e6,\n },\n networkLayer: 12,\n numOpenPortsRequired: 5,\n organizationName: LocationName.NewTokyoVitaLife,\n requiredHackingSkill: {\n max: 900,\n min: 775,\n },\n serverGrowth: {\n max: 80,\n min: 60,\n },\n specialName: LocationName.NewTokyoVitaLife,\n },\n {\n hackDifficulty: {\n max: 95,\n min: 85,\n },\n hostname: \"icarus\",\n moneyAvailable: {\n max: 1000e6,\n min: 900e6,\n },\n networkLayer: 9,\n numOpenPortsRequired: 5,\n organizationName: LocationName.Sector12IcarusMicrosystems,\n requiredHackingSkill: {\n max: 925,\n min: 850,\n },\n serverGrowth: {\n max: 95,\n min: 85,\n },\n specialName: LocationName.Sector12IcarusMicrosystems,\n },\n {\n hackDifficulty: {\n max: 90,\n min: 80,\n },\n hostname: \"univ-energy\",\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 1200e6,\n min: 1100e6,\n },\n networkLayer: 9,\n numOpenPortsRequired: 4,\n organizationName: LocationName.Sector12UniversalEnergy,\n requiredHackingSkill: {\n max: 900,\n min: 800,\n },\n serverGrowth: {\n max: 90,\n min: 80,\n },\n specialName: LocationName.Sector12UniversalEnergy,\n },\n {\n hackDifficulty: {\n max: 80,\n min: 70,\n },\n hostname: \"titan-labs\",\n literature: [LiteratureNames.CodedIntelligence],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 900000000,\n min: 750000000,\n },\n networkLayer: 11,\n numOpenPortsRequired: 5,\n organizationName: \"Titan Laboratories\",\n requiredHackingSkill: {\n max: 875,\n min: 800,\n },\n serverGrowth: {\n max: 80,\n min: 60,\n },\n },\n {\n hackDifficulty: {\n max: 75,\n min: 65,\n },\n hostname: \"microdyne\",\n literature: [LiteratureNames.SyntheticMuscles],\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: {\n max: 700000000,\n min: 500000000,\n },\n networkLayer: 11,\n numOpenPortsRequired: 5,\n organizationName: \"Microdyne Technologies\",\n requiredHackingSkill: {\n max: 875,\n min: 800,\n },\n serverGrowth: {\n max: 90,\n min: 70,\n },\n },\n {\n hackDifficulty: {\n max: 80,\n min: 70,\n },\n hostname: \"taiyang-digital\",\n literature: [LiteratureNames.AGreenTomorrow, LiteratureNames.BrighterThanTheSun],\n moneyAvailable: {\n max: 900000000,\n min: 800000000,\n },\n networkLayer: 10,\n numOpenPortsRequired: 5,\n organizationName: \"Taiyang Digital\",\n requiredHackingSkill: {\n max: 950,\n min: 850,\n },\n serverGrowth: {\n max: 80,\n min: 70,\n },\n },\n {\n hackDifficulty: {\n max: 65,\n min: 55,\n },\n hostname: \"galactic-cyber\",\n moneyAvailable: {\n max: 850000000,\n min: 750000000,\n },\n networkLayer: 7,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumGalacticCybersystems,\n requiredHackingSkill: {\n max: 875,\n min: 825,\n },\n serverGrowth: {\n max: 90,\n min: 70,\n },\n specialName: LocationName.AevumGalacticCybersystems,\n },\n {\n hackDifficulty: {\n max: 90,\n min: 80,\n },\n hostname: \"aerocorp\",\n literature: [LiteratureNames.ManAndMachine],\n moneyAvailable: {\n max: 1200000000,\n min: 1000000000,\n },\n networkLayer: 7,\n numOpenPortsRequired: 5,\n organizationName: LocationName.AevumAeroCorp,\n requiredHackingSkill: {\n max: 925,\n min: 850,\n },\n serverGrowth: {\n max: 65,\n min: 55,\n },\n specialName: LocationName.AevumAeroCorp,\n },\n {\n hackDifficulty: {\n max: 95,\n min: 85,\n },\n hostname: \"omnia\",\n literature: [LiteratureNames.HistoryOfSynthoids],\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: {\n max: 1000000000,\n min: 900000000,\n },\n networkLayer: 8,\n numOpenPortsRequired: 5,\n organizationName: LocationName.VolhavenOmniaCybersystems,\n requiredHackingSkill: {\n max: 950,\n min: 850,\n },\n serverGrowth: {\n max: 70,\n min: 60,\n },\n specialName: LocationName.VolhavenOmniaCybersystems,\n },\n {\n hackDifficulty: {\n max: 65,\n min: 55,\n },\n hostname: \"zb-def\",\n literature: [LiteratureNames.SyntheticMuscles],\n moneyAvailable: {\n max: 1100000000,\n min: 900000000,\n },\n networkLayer: 10,\n numOpenPortsRequired: 4,\n organizationName: \"ZB Defense Industries\",\n requiredHackingSkill: {\n max: 825,\n min: 775,\n },\n serverGrowth: {\n max: 75,\n min: 65,\n },\n },\n {\n hackDifficulty: {\n max: 80,\n min: 60,\n },\n hostname: \"applied-energetics\",\n moneyAvailable: {\n max: 1000000000,\n min: 700000000,\n },\n networkLayer: 11,\n numOpenPortsRequired: 4,\n organizationName: \"Applied Energetics\",\n requiredHackingSkill: {\n max: 850,\n min: 775,\n },\n serverGrowth: {\n max: 75,\n min: 70,\n },\n },\n {\n hackDifficulty: {\n max: 80,\n min: 70,\n },\n hostname: \"solaris\",\n literature: [LiteratureNames.AGreenTomorrow, LiteratureNames.TheFailedFrontier],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 900000000,\n min: 700000000,\n },\n networkLayer: 9,\n numOpenPortsRequired: 5,\n organizationName: LocationName.ChongqingSolarisSpaceSystems,\n requiredHackingSkill: {\n max: 850,\n min: 750,\n },\n serverGrowth: {\n max: 80,\n min: 70,\n },\n specialName: LocationName.ChongqingSolarisSpaceSystems,\n },\n {\n hackDifficulty: {\n max: 85,\n min: 75,\n },\n hostname: \"deltaone\",\n moneyAvailable: {\n max: 1700000000,\n min: 1300000000,\n },\n networkLayer: 8,\n numOpenPortsRequired: 5,\n organizationName: LocationName.Sector12DeltaOne,\n requiredHackingSkill: {\n max: 900,\n min: 800,\n },\n serverGrowth: {\n max: 70,\n min: 50,\n },\n specialName: LocationName.Sector12DeltaOne,\n },\n {\n hackDifficulty: {\n max: 85,\n min: 75,\n },\n hostname: \"global-pharm\",\n literature: [LiteratureNames.AGreenTomorrow],\n maxRamExponent: {\n max: 6,\n min: 3,\n },\n moneyAvailable: {\n max: 1750000000,\n min: 1500000000,\n },\n networkLayer: 7,\n numOpenPortsRequired: 4,\n organizationName: LocationName.NewTokyoGlobalPharmaceuticals,\n requiredHackingSkill: {\n max: 850,\n min: 750,\n },\n serverGrowth: {\n max: 90,\n min: 80,\n },\n specialName: LocationName.NewTokyoGlobalPharmaceuticals,\n },\n {\n hackDifficulty: {\n max: 80,\n min: 60,\n },\n hostname: \"nova-med\",\n moneyAvailable: {\n max: 1250000000,\n min: 1100000000,\n },\n networkLayer: 10,\n numOpenPortsRequired: 4,\n organizationName: LocationName.IshimaNovaMedical,\n requiredHackingSkill: {\n max: 850,\n min: 775,\n },\n serverGrowth: {\n max: 85,\n min: 65,\n },\n specialName: LocationName.IshimaNovaMedical,\n },\n {\n hackDifficulty: {\n max: 90,\n min: 70,\n },\n hostname: \"zeus-med\",\n moneyAvailable: {\n max: 1500000000,\n min: 1300000000,\n },\n networkLayer: 9,\n numOpenPortsRequired: 5,\n organizationName: \"Zeus Medical\",\n requiredHackingSkill: {\n max: 850,\n min: 800,\n },\n serverGrowth: {\n max: 80,\n min: 70,\n },\n },\n {\n hackDifficulty: {\n max: 80,\n min: 70,\n },\n hostname: \"unitalife\",\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: {\n max: 1100000000,\n min: 1000000000,\n },\n networkLayer: 8,\n numOpenPortsRequired: 4,\n organizationName: \"UnitaLife Group\",\n requiredHackingSkill: {\n max: 825,\n min: 775,\n },\n serverGrowth: {\n max: 80,\n min: 70,\n },\n },\n {\n hackDifficulty: {\n max: 80,\n min: 60,\n },\n hostname: \"lexo-corp\",\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 800000000,\n min: 700000000,\n },\n networkLayer: 6,\n numOpenPortsRequired: 4,\n organizationName: LocationName.VolhavenLexoCorp,\n requiredHackingSkill: {\n max: 750,\n min: 650,\n },\n serverGrowth: {\n max: 65,\n min: 55,\n },\n specialName: LocationName.VolhavenLexoCorp,\n },\n {\n hackDifficulty: {\n max: 60,\n min: 40,\n },\n hostname: \"rho-construction\",\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: {\n max: 700000000,\n min: 500000000,\n },\n networkLayer: 6,\n numOpenPortsRequired: 3,\n organizationName: LocationName.AevumRhoConstruction,\n requiredHackingSkill: {\n max: 525,\n min: 475,\n },\n serverGrowth: {\n max: 60,\n min: 40,\n },\n specialName: LocationName.AevumRhoConstruction,\n },\n {\n hackDifficulty: {\n max: 70,\n min: 50,\n },\n hostname: \"alpha-ent\",\n literature: [LiteratureNames.Sector12Crime],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 750000000,\n min: 600000000,\n },\n networkLayer: 6,\n numOpenPortsRequired: 4,\n organizationName: LocationName.Sector12AlphaEnterprises,\n requiredHackingSkill: {\n max: 600,\n min: 500,\n },\n serverGrowth: {\n max: 60,\n min: 50,\n },\n specialName: LocationName.Sector12AlphaEnterprises,\n },\n {\n hackDifficulty: {\n max: 80,\n min: 70,\n },\n hostname: \"aevum-police\",\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: {\n max: 400000000,\n min: 200000000,\n },\n networkLayer: 6,\n numOpenPortsRequired: 4,\n organizationName: LocationName.AevumPolice,\n requiredHackingSkill: {\n max: 450,\n min: 400,\n },\n serverGrowth: {\n max: 50,\n min: 30,\n },\n specialName: LocationName.AevumPolice,\n },\n {\n hackDifficulty: {\n max: 55,\n min: 45,\n },\n hostname: \"rothman-uni\",\n literature: [\n LiteratureNames.SecretSocieties,\n LiteratureNames.TheFailedFrontier,\n LiteratureNames.TensionsInTechRace,\n ],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 250000000,\n min: 175000000,\n },\n networkLayer: 5,\n numOpenPortsRequired: 3,\n organizationName: LocationName.Sector12RothmanUniversity,\n requiredHackingSkill: {\n max: 430,\n min: 370,\n },\n serverGrowth: {\n max: 45,\n min: 35,\n },\n specialName: LocationName.Sector12RothmanUniversity,\n },\n {\n hackDifficulty: {\n max: 85,\n min: 65,\n },\n hostname: \"zb-institute\",\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 1100000000,\n min: 800000000,\n },\n networkLayer: 5,\n numOpenPortsRequired: 5,\n organizationName: LocationName.VolhavenZBInstituteOfTechnology,\n requiredHackingSkill: {\n max: 775,\n min: 725,\n },\n serverGrowth: {\n max: 85,\n min: 75,\n },\n specialName: LocationName.VolhavenZBInstituteOfTechnology,\n },\n {\n hackDifficulty: {\n max: 65,\n min: 45,\n },\n hostname: \"summit-uni\",\n literature: [LiteratureNames.SecretSocieties, LiteratureNames.TheFailedFrontier, LiteratureNames.SyntheticMuscles],\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: {\n max: 350000000,\n min: 200000000,\n },\n networkLayer: 5,\n numOpenPortsRequired: 3,\n organizationName: LocationName.AevumSummitUniversity,\n requiredHackingSkill: {\n max: 475,\n min: 425,\n },\n serverGrowth: {\n max: 60,\n min: 40,\n },\n specialName: LocationName.AevumSummitUniversity,\n },\n {\n hackDifficulty: {\n max: 80,\n min: 60,\n },\n hostname: \"syscore\",\n moneyAvailable: {\n max: 600000000,\n min: 400000000,\n },\n networkLayer: 5,\n numOpenPortsRequired: 4,\n organizationName: LocationName.VolhavenSysCoreSecurities,\n requiredHackingSkill: {\n max: 650,\n min: 550,\n },\n serverGrowth: {\n max: 70,\n min: 60,\n },\n specialName: LocationName.VolhavenSysCoreSecurities,\n },\n {\n hackDifficulty: {\n max: 70,\n min: 60,\n },\n hostname: \"catalyst\",\n literature: [LiteratureNames.TensionsInTechRace],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: {\n max: 550000000,\n min: 300000000,\n },\n networkLayer: 5,\n numOpenPortsRequired: 3,\n organizationName: \"Catalyst Ventures\",\n requiredHackingSkill: {\n max: 450,\n min: 400,\n },\n serverGrowth: {\n max: 55,\n min: 25,\n },\n },\n {\n hackDifficulty: {\n max: 45,\n min: 35,\n },\n hostname: \"the-hub\",\n maxRamExponent: {\n max: 6,\n min: 3,\n },\n moneyAvailable: {\n max: 200000000,\n min: 150000000,\n },\n networkLayer: 4,\n numOpenPortsRequired: 2,\n organizationName: \"The Hub\",\n requiredHackingSkill: {\n max: 325,\n min: 275,\n },\n serverGrowth: {\n max: 55,\n min: 45,\n },\n },\n {\n hackDifficulty: {\n max: 65,\n min: 55,\n },\n hostname: \"comptek\",\n literature: [LiteratureNames.ManAndMachine],\n moneyAvailable: {\n max: 250000000,\n min: 220000000,\n },\n networkLayer: 4,\n numOpenPortsRequired: 3,\n organizationName: LocationName.VolhavenCompuTek,\n requiredHackingSkill: {\n max: 400,\n min: 300,\n },\n serverGrowth: {\n max: 65,\n min: 45,\n },\n specialName: LocationName.VolhavenCompuTek,\n },\n {\n hackDifficulty: {\n max: 80,\n min: 60,\n },\n hostname: \"netlink\",\n literature: [LiteratureNames.SimulatedReality],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: 275000000,\n networkLayer: 4,\n numOpenPortsRequired: 3,\n organizationName: LocationName.AevumNetLinkTechnologies,\n requiredHackingSkill: {\n max: 425,\n min: 375,\n },\n serverGrowth: {\n max: 75,\n min: 45,\n },\n specialName: LocationName.AevumNetLinkTechnologies,\n },\n {\n hackDifficulty: {\n max: 65,\n min: 35,\n },\n hostname: \"johnson-ortho\",\n moneyAvailable: {\n max: 85000000,\n min: 70000000,\n },\n networkLayer: 4,\n numOpenPortsRequired: 2,\n organizationName: \"Johnson Orthopedics\",\n requiredHackingSkill: {\n max: 300,\n min: 250,\n },\n serverGrowth: {\n max: 65,\n min: 35,\n },\n },\n {\n hackDifficulty: 1,\n hostname: \"n00dles\",\n literature: [],\n maxRamExponent: 2,\n moneyAvailable: 70000,\n networkLayer: 1,\n numOpenPortsRequired: 0,\n organizationName: LocationName.NewTokyoNoodleBar,\n requiredHackingSkill: 1,\n serverGrowth: 3000,\n specialName: LocationName.NewTokyoNoodleBar,\n },\n {\n hackDifficulty: 10,\n hostname: \"foodnstuff\",\n literature: [LiteratureNames.Sector12Crime],\n maxRamExponent: 4,\n moneyAvailable: 2000000,\n networkLayer: 1,\n numOpenPortsRequired: 0,\n organizationName: LocationName.Sector12FoodNStuff,\n requiredHackingSkill: 1,\n serverGrowth: 5,\n specialName: LocationName.Sector12FoodNStuff,\n },\n {\n hackDifficulty: 10,\n hostname: \"sigma-cosmetics\",\n maxRamExponent: 4,\n moneyAvailable: 2300000,\n networkLayer: 1,\n numOpenPortsRequired: 0,\n organizationName: \"Sigma Cosmetics\",\n requiredHackingSkill: 5,\n serverGrowth: 10,\n },\n {\n hackDifficulty: 15,\n hostname: \"joesguns\",\n maxRamExponent: 4,\n moneyAvailable: 2500000,\n networkLayer: 1,\n numOpenPortsRequired: 0,\n organizationName: LocationName.Sector12JoesGuns,\n requiredHackingSkill: 10,\n serverGrowth: 20,\n specialName: LocationName.Sector12JoesGuns,\n },\n {\n hackDifficulty: 25,\n hostname: \"zer0\",\n maxRamExponent: 5,\n moneyAvailable: 7500000,\n networkLayer: 2,\n numOpenPortsRequired: 1,\n organizationName: \"ZER0 Nightclub\",\n requiredHackingSkill: 75,\n serverGrowth: 40,\n },\n {\n hackDifficulty: 20,\n hostname: \"nectar-net\",\n maxRamExponent: 4,\n moneyAvailable: 2750000,\n networkLayer: 2,\n numOpenPortsRequired: 0,\n organizationName: \"Nectar Nightclub Network\",\n requiredHackingSkill: 20,\n serverGrowth: 25,\n },\n {\n hackDifficulty: 25,\n hostname: \"neo-net\",\n literature: [LiteratureNames.TheHiddenWorld],\n maxRamExponent: 5,\n moneyAvailable: 5000000,\n networkLayer: 3,\n numOpenPortsRequired: 1,\n organizationName: \"Neo Nightclub Network\",\n requiredHackingSkill: 50,\n serverGrowth: 25,\n },\n {\n hackDifficulty: 30,\n hostname: \"silver-helix\",\n literature: [LiteratureNames.NewTriads],\n maxRamExponent: 6,\n moneyAvailable: 45000000,\n networkLayer: 3,\n numOpenPortsRequired: 2,\n organizationName: \"Silver Helix\",\n requiredHackingSkill: 150,\n serverGrowth: 30,\n },\n {\n hackDifficulty: 15,\n hostname: \"hong-fang-tea\",\n literature: [LiteratureNames.BrighterThanTheSun],\n maxRamExponent: 4,\n moneyAvailable: 3000000,\n networkLayer: 1,\n numOpenPortsRequired: 0,\n organizationName: \"HongFang Teahouse\",\n requiredHackingSkill: 30,\n serverGrowth: 20,\n },\n {\n hackDifficulty: 15,\n hostname: \"harakiri-sushi\",\n maxRamExponent: 4,\n moneyAvailable: 4000000,\n networkLayer: 1,\n numOpenPortsRequired: 0,\n organizationName: \"HaraKiri Sushi Bar Network\",\n requiredHackingSkill: 40,\n serverGrowth: 40,\n },\n {\n hackDifficulty: 20,\n hostname: \"phantasy\",\n maxRamExponent: 5,\n moneyAvailable: 24000000,\n networkLayer: 3,\n numOpenPortsRequired: 2,\n organizationName: \"Phantasy Club\",\n requiredHackingSkill: 100,\n serverGrowth: 35,\n },\n {\n hackDifficulty: 15,\n hostname: \"max-hardware\",\n maxRamExponent: 5,\n moneyAvailable: 10000000,\n networkLayer: 2,\n numOpenPortsRequired: 1,\n organizationName: \"Max Hardware Store\",\n requiredHackingSkill: 80,\n serverGrowth: 30,\n },\n {\n hackDifficulty: {\n max: 35,\n min: 25,\n },\n hostname: \"omega-net\",\n literature: [LiteratureNames.TheNewGod],\n maxRamExponent: 5,\n moneyAvailable: {\n max: 70000000,\n min: 60000000,\n },\n networkLayer: 3,\n numOpenPortsRequired: 2,\n organizationName: LocationName.IshimaOmegaSoftware,\n requiredHackingSkill: {\n max: 220,\n min: 180,\n },\n serverGrowth: {\n max: 40,\n min: 30,\n },\n specialName: LocationName.IshimaOmegaSoftware,\n },\n {\n hackDifficulty: {\n max: 45,\n min: 35,\n },\n hostname: \"crush-fitness\",\n moneyAvailable: {\n max: 60000000,\n min: 40000000,\n },\n networkLayer: 4,\n numOpenPortsRequired: 2,\n organizationName: \"Crush Fitness\",\n requiredHackingSkill: {\n max: 275,\n min: 225,\n },\n serverGrowth: {\n max: 33,\n min: 27,\n },\n specialName: LocationName.AevumCrushFitnessGym,\n },\n {\n hackDifficulty: 30,\n hostname: \"iron-gym\",\n maxRamExponent: 5,\n moneyAvailable: 20000000,\n networkLayer: 1,\n numOpenPortsRequired: 1,\n organizationName: \"Iron Gym Network\",\n requiredHackingSkill: 100,\n serverGrowth: 20,\n specialName: LocationName.Sector12IronGym,\n },\n {\n hackDifficulty: {\n max: 55,\n min: 45,\n },\n hostname: \"millenium-fitness\",\n maxRamExponent: {\n max: 8,\n min: 4,\n },\n moneyAvailable: 250000000,\n networkLayer: 6,\n numOpenPortsRequired: 3,\n organizationName: \"Millenium Fitness Network\",\n requiredHackingSkill: {\n max: 525,\n min: 475,\n },\n serverGrowth: {\n max: 45,\n min: 25,\n },\n specialName: LocationName.VolhavenMilleniumFitnessGym,\n },\n {\n hackDifficulty: {\n max: 65,\n min: 55,\n },\n hostname: \"powerhouse-fitness\",\n maxRamExponent: {\n max: 6,\n min: 4,\n },\n moneyAvailable: 900000000,\n networkLayer: 14,\n numOpenPortsRequired: 5,\n organizationName: \"Powerhouse Fitness\",\n requiredHackingSkill: {\n max: 1100,\n min: 950,\n },\n serverGrowth: {\n max: 60,\n min: 50,\n },\n specialName: LocationName.Sector12PowerhouseGym,\n },\n {\n hackDifficulty: {\n max: 60,\n min: 40,\n },\n hostname: \"snap-fitness\",\n moneyAvailable: 450000000,\n networkLayer: 7,\n numOpenPortsRequired: 4,\n organizationName: \"Snap Fitness\",\n requiredHackingSkill: {\n max: 800,\n min: 675,\n },\n serverGrowth: {\n max: 60,\n min: 40,\n },\n specialName: LocationName.AevumSnapFitnessGym,\n },\n {\n hackDifficulty: 0,\n hostname: \"run4theh111z\",\n literature: [LiteratureNames.SimulatedReality, LiteratureNames.TheNewGod],\n maxRamExponent: {\n max: 9,\n min: 5,\n },\n moneyAvailable: 0,\n networkLayer: 11,\n numOpenPortsRequired: 4,\n organizationName: \"The Runners\",\n requiredHackingSkill: {\n max: 550,\n min: 505,\n },\n serverGrowth: 0,\n specialName: \"BitRunners Server\",\n },\n {\n hackDifficulty: 0,\n hostname: \"I.I.I.I\",\n literature: [LiteratureNames.DemocracyIsDead],\n maxRamExponent: {\n max: 8,\n min: 4,\n },\n moneyAvailable: 0,\n networkLayer: 5,\n numOpenPortsRequired: 3,\n organizationName: \"I.I.I.I\",\n requiredHackingSkill: {\n max: 365,\n min: 340,\n },\n serverGrowth: 0,\n specialName: \"The Black Hand Server\",\n },\n {\n hackDifficulty: 0,\n hostname: \"avmnite-02h\",\n literature: [LiteratureNames.DemocracyIsDead],\n maxRamExponent: {\n max: 7,\n min: 4,\n },\n moneyAvailable: 0,\n networkLayer: 4,\n numOpenPortsRequired: 2,\n organizationName: \"NiteSec\",\n requiredHackingSkill: {\n max: 220,\n min: 202,\n },\n serverGrowth: 0,\n specialName: \"NiteSec Server\",\n },\n {\n hackDifficulty: 0,\n hostname: \".\",\n maxRamExponent: 4,\n moneyAvailable: 0,\n networkLayer: 13,\n numOpenPortsRequired: 4,\n organizationName: \".\",\n requiredHackingSkill: {\n max: 550,\n min: 505,\n },\n serverGrowth: 0,\n specialName: \"The Dark Army Server\",\n },\n {\n hackDifficulty: 0,\n hostname: \"CSEC\",\n literature: [LiteratureNames.DemocracyIsDead],\n maxRamExponent: 3,\n moneyAvailable: 0,\n networkLayer: 2,\n numOpenPortsRequired: 1,\n organizationName: \"CyberSec\",\n requiredHackingSkill: {\n max: 60,\n min: 51,\n },\n serverGrowth: 0,\n specialName: \"CyberSec Server\",\n },\n {\n hackDifficulty: 0,\n hostname: \"The-Cave\",\n literature: [LiteratureNames.AlphaOmega],\n moneyAvailable: 0,\n networkLayer: 15,\n numOpenPortsRequired: 5,\n organizationName: \"Helios\",\n requiredHackingSkill: 925,\n serverGrowth: 0,\n specialName: \"Daedalus Server\",\n },\n {\n hackDifficulty: 0,\n hostname: \"w0r1d_d43m0n\",\n moneyAvailable: 0,\n numOpenPortsRequired: 5,\n organizationName: \"w0r1d_d43m0n\",\n requiredHackingSkill: 3000,\n serverGrowth: 0,\n specialName: \"w0r1d_d43m0n\",\n },\n];\n","import { Generic_fromJSON, Generic_toJSON, Reviver } from \"../utils/JSONReviver\";\n\n// Array of all valid states\nconst AllCorporationStates: string[] = [\"START\", \"PURCHASE\", \"PRODUCTION\", \"SALE\", \"EXPORT\"];\n\nexport class CorporationState {\n // Number representing what state the Corporation is in. The number\n // is an index for the array that holds all Corporation States\n state = 0;\n\n // Get the name of the current state\n // NOTE: This does NOT return the number stored in the 'state' property,\n // which is just an index for the array of all possible Corporation States.\n getState(): string {\n return AllCorporationStates[this.state];\n }\n\n // Transition to the next state\n nextState(): void {\n if (this.state < 0 || this.state >= AllCorporationStates.length) {\n this.state = 0;\n }\n\n ++this.state;\n if (this.state >= AllCorporationStates.length) {\n this.state = 0;\n }\n }\n\n // Serialize the current object to a JSON save state.\n toJSON(): any {\n return Generic_toJSON(\"CorporationState\", this);\n }\n\n // Initiatizes a CorporationState object from a JSON save state.\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): CorporationState {\n return Generic_fromJSON(CorporationState, value.data);\n }\n}\n\nReviver.constructors.CorporationState = CorporationState;\n","import { Literature } from \"./Literature\";\nimport { LiteratureNames } from \"./data/LiteratureNames\";\nimport { IMap } from \"../types\";\n\nexport const Literatures: IMap = {};\n\n(function () {\n let title, fn, txt;\n title = \"The Beginner's Guide to Hacking\";\n fn = LiteratureNames.HackersStartingHandbook;\n txt =\n \"Some resources:

\" +\n \"Learn to Program

\" +\n \"For Experienced JavaScript Developers: NetscriptJS

\" +\n \"Netscript Documentation

\" +\n \"When starting out, hacking is the most profitable way to earn money and progress. This \" +\n \"is a brief collection of tips/pointers on how to make the most out of your hacking scripts.

\" +\n \"-hack() and grow() both work by percentages. hack() steals a certain percentage of the \" +\n \"money on a server, and grow() increases the amount of money on a server by some percentage (multiplicatively)

\" +\n \"-Because hack() and grow() work by percentages, they are more effective if the target server has a high amount of money. \" +\n \"Therefore, you should try to increase the amount of money on a server (using grow()) to a certain amount before hacking it. Two \" +\n \"import Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()

\" +\n \"-Keep security level low. Security level affects everything when hacking. Two important Netscript functions \" +\n \"for this are getServerSecurityLevel() and getServerMinSecurityLevel()

\" +\n \"-Purchase additional servers by visiting 'Alpha Enterprises' in the city. They are relatively cheap \" +\n \"and give you valuable RAM to run more scripts early in the game

\" +\n \"-Prioritize upgrading the RAM on your home computer. This can also be done at 'Alpha Enterprises'

\" +\n \"-Many low level servers have free RAM. You can use this RAM to run your scripts. Use the scp Terminal or \" +\n \"Netscript command to copy your scripts onto these servers and then run them.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"The Complete Handbook for Creating a Successful Corporation\";\n fn = LiteratureNames.CorporationManagementHandbook;\n txt =\n \"Getting Started with Corporations
\" +\n \"To get started, visit the City Hall in Sector-12 in order to create a Corporation. This requires \" +\n \"$150b of your own money, but this $150b will get put into your Corporation's funds. \" +\n \"After creating your Corporation, you will see it listed as one of the locations in the city. Click on \" +\n \"your Corporation in order to manage it.

\" +\n \"Your Corporation can have many different divisions, each in a different Industry. There are many different \" +\n \"types of Industries, each with different properties. To create your first division, click the \" +\n \"'Expand into new Industry' button at the top of the management UI. The Agriculture \" +\n \"and Software industries are recommended for your first division.

\" +\n \"The first thing you'll need to do is hire some employees. Employees can be assigned to five different positions. \" +\n \"Each position has a different effect on various aspects of your Corporation. It is recommended to have at least \" +\n \"one employee at each position.

\" +\n \"Each industry uses some combination of Materials in order to produce other Materials and/or create Products. \" +\n \"Specific information about this is displayed in each of your divisions' UI.

\" +\n \"Products are special, industry-specific objects. They are different than Materials because you \" +\n \"must manually choose to develop them, and you can choose to develop any number of Products. Developing \" +\n \"a Product takes time, but a Product typically generates significantly more revenue than any Material. \" +\n \"Not all industries allow you to create Products. To create a Product, look for a button \" +\n \"in the top-left panel of the division UI (e.g. For the Software Industry, the button says 'Develop Software').

\" +\n \"To get your supply chain system started, \" +\n \"purchase the Materials that your industry needs to produce other Materials/Products. This can be done \" +\n \"by clicking the 'Buy' button next to the corresponding Material(s). After you have the required Materials, \" +\n \"you will immediately start production. The amount of Materials/Products you produce is based on a variety of factors, \" +\n \"one of which is your employees and their productivity.

\" +\n \"Once you start producing Materials/Products, you can sell them in order to start earning revenue. This can be done \" +\n \"by clicking the 'Sell' button next to the corresponding Material or Product. The amount of Material/Product you sell is dependent \" +\n \"on a wide variety of different factors.

\" +\n \"These are the basics of getting your Corporation up and running! Now, you can start purchasing upgrades to improve \" +\n \"your bottom line. If you need money, consider looking for seed investors, who will give you money in exchange for stock shares. \" +\n \"Otherwise, once you feel you are ready, take your Corporation public! Once your Corporation goes public, you can no longer \" +\n \"find investors. Instead, your Corporation will be publicly traded and its stock price will change based on how well \" +\n \"it's performing financially. You can then sell your stock shares in order to make money.

\" +\n \"Tips/Pointers
\" +\n \"-The 'Smart Supply' upgrade is extremely useful. Consider purchasing it as soon as possible.

\" +\n \"-Purchasing Hardware, Robots, AI Cores, and Real Estate can potentially increase your production. \" +\n \"The effects of these depend on what industry you are in.

\" +\n \"-In order to optimize your production, you will need a good balance of Operators, Managers, and Engineers

\" +\n \"-Different employees excel in different jobs. For example, the highly intelligent employees will probably do best \" +\n \"if they are assigned to do Engineering work or Research & Development.

\" +\n \"-If your employees have low morale, energy, or happiness, their production will greatly suffer.

\" +\n \"-Tech is important, but don't neglect sales! Having several Businessmen can boost your sales and your bottom line.

\" +\n \"-Don't forget to advertise your company. You won't have any business if nobody knows you.

\" +\n \"-Having company awareness is great, but what's really important is your company's popularity. Try to keep \" +\n \"your popularity as high as possible to see the biggest benefit for your sales

\" +\n \"-Remember, you need to spend money to make money!

\" +\n \"-Corporations do not reset when installing Augmentations, but they do reset when destroying a BitNode\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"A Brief History of Synthoids\";\n fn = LiteratureNames.HistoryOfSynthoids;\n txt =\n \"Synthetic androids, or Synthoids for short, are genetically engineered robots and, short of Augmentations, \" +\n \"are composed entirely of organic substances. For this reason, Synthoids are virtually identical to \" +\n \"humans in form, composition, and appearance.

\" +\n \"Synthoids were first designed and manufactured by OmniTek Incorporated sometime around the middle of the century. \" +\n \"Their original purpose was to be used for manual labor and as emergency responders for disasters. As such, they \" +\n \"were initially programmed only for their specific tasks. Each iteration that followed improved upon the \" +\n \"intelligence and capabilities of the Synthoids. By the 6th iteration, called MK-VI, the Synthoids were \" +\n \"so smart and capable enough of making their own decisions that many argued OmniTek had created the first \" +\n \"sentient AI. These MK-VI Synthoids were produced in mass quantities (estimates up to 50 billion) with the hopes of increasing society's \" +\n \"productivity and bolstering the global economy. Stemming from humanity's desire for technological advancement, optimism \" +\n \"and excitement about the future had never been higher.

\" +\n \"All of that excitement and optimism quickly turned to fear, panic, and dread in 2070, when a terrorist group \" +\n \"called Ascendis Totalis hacked into OmniTek and uploaded a rogue AI into severeal of their Synthoid manufacturing facilities. \" +\n \"This hack went undetected and for months OmniTek unknowingly churned out legions of Synthoids embedded with this \" +\n \"rogue AI. Then, on December 24th, 2070, Omnica activated dormant protocols in the rogue AI, causing all of the \" +\n \"infected Synthoids to immediately launch a military campaign to seek and destroy all of humanity.

\" +\n \"What ensued was the deadlist conflict in human history. This crisis, now commonly known as the Synthoid Uprising, \" +\n \"resulted in almost ten billion deaths over the course of a year. Despite the nations of the world banding together \" +\n \"to combat the threat, the MK-VI Synthoids were simply stronger, faster, more intelligent, and more adaptable than humans, \" +\n \"outsmarting them at every turn.

\" +\n \"It wasn't until the sacrifice of an elite international military taskforce, called the Bladeburners, that humanity \" +\n \"was finally able to defeat the Synthoids. The Bladeburners' final act was a suicide bombing mission that \" +\n \"destroyed a large portion of the MK-VI Synthoids, including many of its leaders. In the following \" +\n \"weeks militaries from around the world were able to round up and shut down the remaining rogue MK-VI Synthoids, ending \" +\n \"the Synthoid Uprising.

\" +\n \"In the aftermath of the bloodshed, the Synthoid Accords were drawn up. These Accords banned OmniTek Incorporated \" +\n \"from manufacturing any Synthoids beyond the MK-III series. They also banned any other corporation \" +\n \"from constructing androids with advanced, near-sentient AI. MK-VI Synthoids that did not have the rogue Ascendis Totalis \" +\n \"AI were allowed to continue their existence, but they were stripped of all rights and protections as they \" +\n \"were not considered humans. They were also banned from doing anything that may pose a global security threat, such \" +\n \"as working for any military/defense organization or conducting any bioengineering, computing, or robotics related research.

\" +\n \"Unfortunately, many believe that not all of the rogue MK-VI Synthoids from the Uprising were found and destroyed, \" +\n \"and that many of them are blending in as normal humans in society today. In response, many nations have created \" +\n \"Bladeburner divisions, special military branches that are tasked with investigating and dealing with any Synthoid threads.

\" +\n \"To this day, tensions still exist between the remaining Synthoids and humans as a result of the Uprising.

\" +\n \"Nobody knows what happened to the terrorist group Ascendis Totalis.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"A Green Tomorrow\";\n fn = LiteratureNames.AGreenTomorrow;\n txt =\n \"Starting a few decades ago, there was a massive global movement towards the generation of renewable energy in an effort to \" +\n \"combat global warming and climate change. The shift towards renewable energy was a big success, or so it seemed. In 2045 \" +\n \"a staggering 80% of the world's energy came from non-renewable fossil fuels. Now, about three decades later, that \" +\n \"number is down to only 15%. Most of the world's energy now comes from nuclear power and renewable sources such as \" +\n \"solar and geothermal energy. Unfortunately, these efforts were not the huge success that they seem to be.

\" +\n \"Since 2045 primary energy use has soared almost tenfold. This was mainly due to growing urban populations and \" +\n \"the rise of increasingly advanced (and power-hungry) technology that has become ubiquitous in our lives. So, \" +\n \"despite the fact that the percentage of our energy that comes from fossil fuels has drastically decreased, \" +\n \"the total amount of energy we are producing from fossil fuels has actually increased.

\" +\n \"The grim effects of our species' irresponsible use of energy and neglect of our mother world have become increasingly apparent. \" +\n \"Last year a temperature of 190F was recorded in the Death Valley desert, which is over 50% higher than the highest \" +\n \"recorded temperature at the beginning of the century. In the last two decades numerous major cities such as Manhattan, Boston, and \" +\n \"Los Angeles have been partially or fully submerged by rising sea levels. In the present day, over 75% of the world's agriculture is \" +\n \"done in climate-controlled vertical farms, as most traditional farmland has become unusable due to severe climate conditions.

\" +\n \"Despite all of this, the greedy and corrupt corporations that rule the world have done nothing to address these problems that \" +\n \"threaten our species. And so it's up to us, the common people. Each and every one of us can make a difference by doing what \" +\n \"these corporations won't: taking responsibility. If we don't, pretty soon there won't be an Earth left to save. We are \" +\n \"the last hope for a green tomorrow.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Alpha and Omega\";\n fn = LiteratureNames.AlphaOmega;\n txt =\n \"Then we saw a new Heaven and a new Earth, for our first Heaven and Earth had gone away, and our sea was no more. \" +\n \"And we saw a new holy city, new Aeria, coming down out of this new Heaven, prepared as a bride adorned for her husband. \" +\n \"And we heard a loud voice saying, 'Behold, the new dwelling place of the Gods. We will dwell with them, and they \" +\n \"will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death \" +\n \"shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things \" +\n \"have passed away.'

\" +\n \"And once we were seated on the throne we said 'Behold, I am making all things new.' \" +\n \"Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, \" +\n \"'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring \" +\n \"of the water of life without payment. The one who conquers will have this heritage, and we will be his God and \" +\n \"he will be our son. But as for the cowardly, the faithless, the detestable, as for murderers, \" +\n \"the sexually immoral, sorcerers, idolaters, and all liars, their portion will be in the lake that \" +\n \"burns with fire and sulfur, for it is the second true death.'\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Are We Living in a Computer Simulation?\";\n fn = LiteratureNames.SimulatedReality;\n txt =\n \"The idea that we are living in a virtual world is not new. It's a trope that has \" +\n \"been explored constantly in literature and pop culture. However, it is also a legitimate \" +\n \"scientific hypothesis that many notable physicists and philosophers have debated for years.

\" +\n \"Proponents for this simulated reality theory often point to how advanced our technology has become, \" +\n \"as well as the incredibly fast pace at which it has advanced over the past decades. The amount of computing \" +\n \"power available to us has increased over 100-fold since 2060 due to the development of nanoprocessors and \" +\n \"quantum computers. Artifical Intelligence has advanced to the point where our entire lives are controlled \" +\n \"by robots and machines that handle our day-to-day activities such as autonomous transportation and scheduling. \" +\n \"If we consider the pace at which this technology has advanced and assume that these developments continue, it's \" +\n \"reasonable to assume that at some point in the future our technology would be advanced enough that \" +\n \"we could create simulations that are indistinguishable from reality. However, if continued technological advancement \" +\n \"is a reasonable outcome, then it is very likely that such a scenario has already happened.

\" +\n \"Statistically speaking, somewhere out there in the infinite universe there is an advanced, intelligent species \" +\n \"that already has such technology. Who's to say that they haven't already created such a virtual reality: our own?\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Beyond Man\";\n fn = LiteratureNames.BeyondMan;\n txt =\n \"Humanity entered a 'transhuman' era a long time ago. And despite the protests and criticisms of many who cried out against \" +\n \"human augmentation at the time, the transhuman movement continued and prospered. Proponents of the movement ignored the critics, \" +\n \"arguing that it was in our inherent nature to better ourselves. To improve. To be more than we were. They claimed that \" +\n \"not doing so would be to go against every living organism's biological purpose: evolution and survival of the fittest.

\" +\n \"And here we are today, with technology that is advanced enough to augment humans to a state that \" +\n \"can only be described as posthuman. But what do we have to show for it when this augmentation \" +\n \"technology is only available to the so-called 'elite'? Are we really better off than before when only 5% of the \" +\n \"world's population has access to this technology? When the powerful corporations and organizations of the world \" +\n \"keep it all to themselves, have we really evolved?

\" +\n \"Augmentation technology has only further increased the divide between the rich and the poor, between the powerful and \" +\n \"the oppressed. We have not become 'more than human'. We have not evolved from nature's original design. We are still the greedy, \" +\n \"corrupted, and evil men that we always were.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Brighter than the Sun\";\n fn = LiteratureNames.BrighterThanTheSun;\n txt =\n \"When people think about the corporations that dominate the East, they typically think of KuaiGong International, which \" +\n \"holds a complete monopoly for manufacturing and commerce in Asia, or Global Pharmaceuticals, the world's largest \" +\n \"drug company, or OmniTek Incorporated, the global leader in intelligent and autonomous robots. But there's one company \" +\n \"that has seen a rapid rise in the last year and is poised to dominate not only the East, but the entire world: TaiYang Digital.

\" +\n \"TaiYang Digital is a Chinese internet-technology corporation that provides services such as \" +\n \"online advertising, search engines, gaming, media, entertainment, and cloud computing/storage. Its name TaiYang comes from the Chinese word \" +\n \"for 'sun'. In Chinese culture, the sun is a 'yang' symbol \" +\n \"associated with life, heat, masculinity, and heaven.

\" +\n \"The company was founded \" +\n \"less than 5 years ago and is already the third highest valued company in all of Asia. In 2076 it generated a total revenue of \" +\n \"over 10 trillion yuan. It's services are used daily by over a billion people worldwide.

\" +\n \"TaiYang Digital's meteoric rise is extremely surprising in modern society. This sort of growth is \" +\n \"something you'd commonly see in the first half of the century, especially for tech companies. However in \" +\n \"the last two decades the number of corporations has significantly declined as the largest entities \" +\n \"quickly took over the economy. Corporations such as ECorp, MegaCorp, and KuaiGong have established \" +\n \"such strong monopolies in their market sectors that they have effectively killed off all \" +\n \"of the smaller and new corporations that have tried to start up over the years. This is what makes \" +\n \"the rise of TaiYang Digital so impressive. And if TaiYang continues down this path, then they have \" +\n \"a bright future ahead of them.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Democracy is Dead: The Fall of an Empire\";\n fn = LiteratureNames.DemocracyIsDead;\n txt =\n \"They rose from the shadows in the street.
From the places where the oppressed meet.
\" +\n \"Their cries echoed loudly through the air.
As they once did in Tiananmen Square.
\" +\n \"Loudness in the silence, Darkness in the light.
They came forth with power and might.
\" +\n \"Once the beacon of democracy, America was first.
Its pillars of society destroyed and dispersed.
\" +\n \"Soon the cries rose everywhere, with revolt and riot.
Until one day, finally, all was quiet.
\" +\n \"From the ashes rose a new order, corporatocracy was its name.
\" +\n \"Rome, Mongol, Byzantine, all of history is just the same.
\" +\n \"For man will never change in a fundamental way.
\" +\n \"And now democracy is dead, in the USA.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Figures Show Rising Crime Rates in Sector-12\";\n fn = LiteratureNames.Sector12Crime;\n txt =\n \"A recent study by analytics company Wilson Inc. shows a significant rise \" +\n \"in criminal activity in Sector-12. Perhaps the most alarming part of the statistic \" +\n \"is that most of the rise is in violent crime such as homicide and assault. According \" +\n \"to the study, the city saw a total of 21,406 reported homicides in 2076, which is over \" +\n \"a 20% increase compared to 2075.

\" +\n \"CIA director David Glarow says its too early to know \" +\n \"whether these figures indicate the beginning of a sustained increase in crime rates, or whether \" +\n \"the year was just an unfortunate outlier. He states that many intelligence and law enforcement \" +\n \"agents have noticed an increase in organized crime activites, and believes that these figures may \" +\n \"be the result of an uprising from criminal organizations such as The Syndicate or the Slum Snakes.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Man and the Machine\";\n fn = LiteratureNames.ManAndMachine;\n txt =\n \"In 2005 Ray Kurzweil popularized his theory of the Singularity. He predicted that the rate \" +\n \"of technological advancement would continue to accelerate faster and faster until one day \" +\n \"machines would be become infinitely more intelligent than humans. This point, called the \" +\n \"Singularity, would result in a drastic transformation of the world as we know it. He predicted \" +\n \"that the Singularity would arrive by 2045. \" +\n \"And yet here we are, more than three decades later, where most would agree that we have not \" +\n \"yet reached a point where computers and machines are vastly more intelligent than we are. So what gives?

\" +\n \"The answer is that we have reached the Singularity, just not in the way we expected. The artifical superintelligence \" +\n \"that was predicted by Kurzweil and others exists in the world today - in the form of Augmentations. \" +\n \"Yes, those Augmentations that the rich and powerful keep to themselves enable humans \" +\n \"to become superintelligent beings. The Singularity did not lead to a world where \" +\n \"our machines are infinitely more intelligent than us, it led to a world \" +\n \"where man and machine can merge to become something greater. Most of the world just doesn't \" +\n \"know it yet.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Secret Societies\";\n fn = LiteratureNames.SecretSocieties;\n txt =\n \"The idea of secret societies has long intrigued the general public by inspiring curiosity, fascination, and \" +\n \"distrust. People have long wondered about who these secret society members are and what they do, with the \" +\n \"most radical of conspiracy theorists claiming that they control everything in the entire world. And while the world \" +\n \"may never know for sure, it is likely that many secret societies do actually exist, even today.

\" +\n \"However, the secret societies of the modern world are nothing like those that (supposedly) existed \" +\n \"decades and centuries ago. The Freemasons, Knights Templar, and Illuminati, while they may have been around \" +\n \"at the turn of the 21st century, almost assuredly do not exist today. The dominance of the Web in \" +\n \"our everyday lives and the fact that so much of the world is now digital has given rise to a new breed \" +\n \"of secret societies: Internet-based ones.

\" +\n \"Commonly called 'hacker groups', Internet-based secret societies have become well-known in today's \" +\n \"world. Some of these, such as The Black Hand, are black hat groups that claim they are trying to \" +\n \"help the oppressed by attacking the elite and powerful. Others, such as NiteSec, are hacktivist groups \" +\n \"that try to push political and social agendas. Perhaps the most intriguing hacker group \" +\n \"is the mysterious Bitrunners, whose purpose still remains unknown.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Space: The Failed Frontier\";\n fn = LiteratureNames.TheFailedFrontier;\n txt =\n \"Humans have long dreamed about spaceflight. With enduring interest, we were driven to explore \" +\n \"the unknown and discover new worlds. We dreamed about conquering the stars. And in our quest, \" +\n \"we pushed the boundaries of our scientific limits, and then pushed further. Space exploration \" +\n \"lead to the development of many important technologies and new industries.

\" +\n \"But sometime in the middle of the 21st century, all of that changed. Humanity lost its ambitions and \" +\n \"aspirations of exploring the cosmos. The once-large funding for agencies like NASA and the European \" +\n \"Space Agency gradually whittled away until their eventual disbanding in the 2060's. Not even \" +\n \"militaries are fielding flights into space nowadays. The only remnants of the once great mission for cosmic \" +\n \"conquest are the countless satellites in near-earth orbit, used for communications, espionage, \" +\n \"and other corporate interests.

\" +\n \"And as we continue to look at the state of space technology, it becomes more and \" +\n \"more apparent that we will never return to that golden age of space exploration, that \" +\n \"age where everyone dreamed of going beyond earth for the sake of discovery.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Coded Intelligence: Myth or Reality?\";\n fn = LiteratureNames.CodedIntelligence;\n txt =\n \"Tremendous progress has been made in the field of Artificial Intelligence over the past few decades. \" +\n \"Our autonomous vehicles and transporation systems. The electronic personal assistants that control our everyday lives. \" +\n \"Medical, service, and manufacturing robots. All of these are examples of how far AI has come and how much it has \" +\n \"improved our daily lives. However, the question still remains of whether AI will ever be advanced enough to re-create \" +\n \"human intelligence.

\" +\n \"We've certainly come close to artificial intelligence that is similar to humans. For example OmniTek Incorporated's \" +\n \"CompanionBot, a robot meant to act as a comforting friend for lonely and grieving people, is eerily human-like \" +\n \"in its appearance, speech, mannerisms, and even movement. However its artificial intelligence isn't the same as \" +\n \"that of humans. Not yet. It doesn't have sentience or self-awareness or consciousness.

\" +\n \"Many neuroscientists believe that we won't ever reach the point of creating artificial human intelligence. 'At the end of the \" +\n \"the day, AI comes down to 1's and 0's, while the human brain does not. We'll never see AI that is identical to that of \" +\n \"humans.'\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Synthetic Muscles\";\n fn = LiteratureNames.SyntheticMuscles;\n txt =\n \"Initial versions of synthetic muscles weren't made of anything organic but were actually \" +\n \"crude devices made to mimic human muscle function. Some of the early iterations were actually made of \" +\n \"common materials such as fishing lines and sewing threads due to their high strength for \" +\n \"a cheap cost.

\" +\n \"As technology progressed, however, advances in biomedical engineering paved the way for a new method of \" +\n \"creating synthetic muscles. Instead of creating something that closely imitated the functionality \" +\n \"of human muscle, scientists discovered a way of forcing the human body itself to augment its own \" +\n \"muscle tissue using both synthetic and organic materials. This is typically done using gene therapy \" +\n \"or chemical injections.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"Tensions rise in global tech race\";\n fn = LiteratureNames.TensionsInTechRace;\n txt =\n \"Have we entered a new Cold War? Is WWIII just beyond the horizon?

\" +\n \"After rumors came out that OmniTek Incorporated had begun developing advanced robotic supersoldiers, \" +\n \"geopolitical tensions quickly flared between the USA, Russia, and several Asian superpowers. \" +\n \"In a rare show of cooperation between corporations, MegaCorp and ECorp have \" +\n \"reportedly launched hundreds of new surveillance and espionage satellites. \" +\n \"Defense contractors such as \" +\n \"DeltaOne and AeroCorp have been working with the CIA and NSA to prepare \" +\n \"for conflict. Meanwhile, the rest of the world sits in earnest \" +\n \"hoping that it never reaches full-scale war. With today's technology \" +\n \"and firepower, a World War would assuredly mean the end of human civilization.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"The Cost of Immortality\";\n fn = LiteratureNames.CostOfImmortality;\n txt =\n \"Evolution and advances in medical and augmentation technology has lead to drastic improvements \" +\n \"in human mortality rates. Recent figures show that the life expectancy for humans \" +\n \"that live in a first-world country is about 130 years of age, almost double of what it was \" +\n \"at the turn of the century. However, this increase in average lifespan has had some \" +\n \"significant effects on society and culture.

\" +\n \"Due to longer lifespans and a better quality of life, many adults are holding \" +\n \"off on having kids until much later. As a result, the percentage of youth in \" +\n \"first-world countries has been decreasing, while the number \" +\n \"of senior citizens is significantly increasing.

\" +\n \"Perhaps the most alarming result of all of this is the rapidly shrinking workforce. \" +\n \"Despite the increase in life expectancy, the typical retirement age for \" +\n \"workers in America has remained about the same, meaning a larger and larger \" +\n \"percentage of people in America are retirees. Furthermore, many \" +\n \"young adults are holding off on joining the workforce because they feel that \" +\n \"they have plenty of time left in their lives for employment, and want to \" +\n \"'enjoy life while they're young.' For most industries, this shrinking workforce \" +\n \"is not a major issue as most things are handled by robots anyways. However, \" +\n \"there are still several key industries such as engineering and education \" +\n \"that have not been automated, and these remain in danger to this cultural \" +\n \"phenomenon.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"The Hidden World\";\n fn = LiteratureNames.TheHiddenWorld;\n txt =\n \"WAKE UP SHEEPLE

\" +\n \"THE GOVERNMENT DOES NOT EXIST. CORPORATIONS DO NOT RUN SOCIETY

\" +\n \"THE ILLUMINATI ARE THE SECRET RULERS OF THE WORLD!

\" +\n \"Yes, the Illuminati of legends. The ancient secret society that controls the entire \" +\n \"world from the shadows with their invisible hand. The group of the rich and wealthy \" +\n \"that have penetrated every major government, financial agency, and corporation in the last \" +\n \"three hundred years.

\" +\n \"OPEN YOUR EYES

\" +\n \"It was the Illuminati that brought an end to democracy in the world. They are the driving force \" +\n \"behind everything that happens.

\" +\n \"THEY ARE ALL AROUND YOU

\" +\n \"After destabilizing the world's governments, they are now entering the final stage of their master plan. \" +\n \"They will secretly initiate global crises. Terrorism. Pandemics. World War. And out of the chaos \" +\n \"that ensues they will build their New World Order.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"The New God\";\n fn = LiteratureNames.TheNewGod;\n txt =\n \"Everyone has a moment in their life when they wonder about the bigger questions.

\" +\n \"What's the point of all this? What is my purpose?

\" +\n \"Some people dare to think even bigger.

\" +\n \"What will the fate of the human race be?

\" +\n \"We live in an era vastly different from that of 15 or even 20 years ago. We have gone \" +\n \"beyond the limits of humanity. We have stripped ourselves of the tyranny of flesh.

\" +\n \"The Singularity is here. The merging of man and machine. This is where humanity evolves into \";\n \"something greater. This is our future.

\" + \"Embrace it, and you will obey a new god. The God in the Machine.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"The New Triads\";\n fn = LiteratureNames.NewTriads;\n txt =\n \"The Triads were an ancient transnational crime syndicate based in China, Hong Kong, and other Asian \" +\n \"territories. They were often considered one of the first and biggest criminal secret societies. \" +\n \"While most of the branches of the Triads have been destroyed over the past few decades, the \" +\n \"crime faction has spawned and inspired a number of other Asian crime organizations over the past few years. \" +\n \"The most notable of these is the Tetrads.

\" +\n \"It is widely believed that the Tetrads are a rogue group that splintered off from the Triads sometime in the \" +\n \"mid 21st century. The founders of the Tetrads, all of whom were ex-Triad members, believed that the \" +\n \"Triads were losing their purpose and direction. The Tetrads started off as a small group that mainly engaged \" +\n \"in fraud and extortion. They were largely unknown until just a few years ago when they took over the illegal \" +\n \"drug trade in all of the major Asian cities. They quickly became the most powerful crime syndicate in the \" +\n \"continent.

\" +\n \"Not much else is known about the Tetrads, or about the efforts the Asian governments and corporations are making \" +\n \"to take down this large new crime organization. Many believe that the Tetrads have infiltrated the governments \" +\n \"and powerful corporations in Asia, which has helped faciliate their recent rapid rise.\";\n Literatures[fn] = new Literature(title, fn, txt);\n\n title = \"The Secret War\";\n fn = LiteratureNames.TheSecretWar;\n txt = \"\";\n Literatures[fn] = new Literature(title, fn, txt);\n})();\n","import * as augmentationMethods from \"./PlayerObjectAugmentationMethods\";\nimport * as bladeburnerMethods from \"./PlayerObjectBladeburnerMethods\";\nimport * as corporationMethods from \"./PlayerObjectCorporationMethods\";\nimport * as gangMethods from \"./PlayerObjectGangMethods\";\nimport * as generalMethods from \"./PlayerObjectGeneralMethods\";\nimport * as serverMethods from \"./PlayerObjectServerMethods\";\n\nimport { IMap } from \"../../types\";\nimport { Resleeve } from \"../Resleeving/Resleeve\";\nimport { Sleeve } from \"../Sleeve/Sleeve\";\nimport { IPlayerOwnedSourceFile } from \"../../SourceFile/PlayerOwnedSourceFile\";\nimport { Exploit } from \"../../Exploits/Exploit\";\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\nimport { CompanyPosition } from \"../../Company/CompanyPosition\";\nimport { Server } from \"../../Server/Server\";\nimport { HacknetServer } from \"../../Hacknet/HacknetServer\";\nimport { Faction } from \"../../Faction/Faction\";\nimport { Company } from \"../../Company/Company\";\nimport { Augmentation } from \"../../Augmentation/Augmentation\";\nimport { IRouter } from \"../../ui/Router\";\nimport { ICodingContractReward } from \"../../CodingContracts\";\n\nimport { IPlayer } from \"../IPlayer\";\nimport { LocationName } from \"../../Locations/data/LocationNames\";\nimport { IPlayerOwnedAugmentation } from \"../../Augmentation/PlayerOwnedAugmentation\";\nimport { ICorporation } from \"../../Corporation/ICorporation\";\nimport { IGang } from \"../../Gang/IGang\";\nimport { IBladeburner } from \"../../Bladeburner/IBladeburner\";\nimport { HacknetNode } from \"../../Hacknet/HacknetNode\";\n\nimport { HashManager } from \"../../Hacknet/HashManager\";\nimport { CityName } from \"../../Locations/data/CityNames\";\n\nimport { MoneySourceTracker } from \"../../utils/MoneySourceTracker\";\nimport { Reviver, Generic_toJSON, Generic_fromJSON } from \"../../utils/JSONReviver\";\n\nimport Decimal from \"decimal.js\";\n\nexport class PlayerObject implements IPlayer {\n // Class members\n augmentations: IPlayerOwnedAugmentation[];\n bitNodeN: number;\n city: CityName;\n companyName: string;\n corporation: ICorporation | null;\n gang: IGang | null;\n bladeburner: IBladeburner | null;\n currentServer: string;\n factions: string[];\n factionInvitations: string[];\n hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server\n has4SData: boolean;\n has4SDataTixApi: boolean;\n hashManager: HashManager;\n hasTixApiAccess: boolean;\n hasWseAccount: boolean;\n homeComputer: string;\n hp: number;\n jobs: IMap;\n init: () => void;\n isWorking: boolean;\n karma: number;\n numPeopleKilled: number;\n location: LocationName;\n max_hp: number;\n money: any;\n moneySourceA: MoneySourceTracker;\n moneySourceB: MoneySourceTracker;\n playtimeSinceLastAug: number;\n playtimeSinceLastBitnode: number;\n purchasedServers: any[];\n queuedAugmentations: IPlayerOwnedAugmentation[];\n resleeves: Resleeve[];\n scriptProdSinceLastAug: number;\n sleeves: Sleeve[];\n sleevesFromCovenant: number;\n sourceFiles: IPlayerOwnedSourceFile[];\n exploits: Exploit[];\n lastUpdate: number;\n totalPlaytime: number;\n\n // Stats\n hacking_skill: number;\n strength: number;\n defense: number;\n dexterity: number;\n agility: number;\n charisma: number;\n intelligence: number;\n\n // Experience\n hacking_exp: number;\n strength_exp: number;\n defense_exp: number;\n dexterity_exp: number;\n agility_exp: number;\n charisma_exp: number;\n intelligence_exp: number;\n\n // Multipliers\n hacking_chance_mult: number;\n hacking_speed_mult: number;\n hacking_money_mult: number;\n hacking_grow_mult: number;\n hacking_mult: number;\n hacking_exp_mult: number;\n strength_mult: number;\n strength_exp_mult: number;\n defense_mult: number;\n defense_exp_mult: number;\n dexterity_mult: number;\n dexterity_exp_mult: number;\n agility_mult: number;\n agility_exp_mult: number;\n charisma_mult: number;\n charisma_exp_mult: number;\n hacknet_node_money_mult: number;\n hacknet_node_purchase_cost_mult: number;\n hacknet_node_ram_cost_mult: number;\n hacknet_node_core_cost_mult: number;\n hacknet_node_level_cost_mult: number;\n company_rep_mult: number;\n faction_rep_mult: number;\n work_money_mult: number;\n crime_success_mult: number;\n crime_money_mult: number;\n bladeburner_max_stamina_mult: number;\n bladeburner_stamina_gain_mult: number;\n bladeburner_analysis_mult: number;\n bladeburner_success_chance_mult: number;\n\n createProgramReqLvl: number;\n factionWorkType: string;\n createProgramName: string;\n timeWorkedCreateProgram: number;\n crimeType: string;\n committingCrimeThruSingFn: boolean;\n singFnCrimeWorkerScript: WorkerScript | null;\n timeNeededToCompleteWork: number;\n focus: boolean;\n className: string;\n currentWorkFactionName: string;\n workType: string;\n currentWorkFactionDescription: string;\n timeWorked: number;\n workMoneyGained: number;\n workMoneyGainRate: number;\n workRepGained: number;\n workRepGainRate: number;\n workHackExpGained: number;\n workHackExpGainRate: number;\n workStrExpGained: number;\n workStrExpGainRate: number;\n workDefExpGained: number;\n workDefExpGainRate: number;\n workDexExpGained: number;\n workDexExpGainRate: number;\n workAgiExpGained: number;\n workAgiExpGainRate: number;\n workChaExpGained: number;\n workChaExpGainRate: number;\n workMoneyLossRate: number;\n\n // Methods\n work: (numCycles: number) => boolean;\n workPartTime: (numCycles: number) => boolean;\n workForFaction: (numCycles: number) => boolean;\n applyForAgentJob: (sing?: boolean) => boolean;\n applyForBusinessConsultantJob: (sing?: boolean) => boolean;\n applyForBusinessJob: (sing?: boolean) => boolean;\n applyForEmployeeJob: (sing?: boolean) => boolean;\n applyForItJob: (sing?: boolean) => boolean;\n applyForJob: (entryPosType: CompanyPosition, sing?: boolean) => boolean;\n applyForNetworkEngineerJob: (sing?: boolean) => boolean;\n applyForPartTimeEmployeeJob: (sing?: boolean) => boolean;\n applyForPartTimeWaiterJob: (sing?: boolean) => boolean;\n applyForSecurityEngineerJob: (sing?: boolean) => boolean;\n applyForSecurityJob: (sing?: boolean) => boolean;\n applyForSoftwareConsultantJob: (sing?: boolean) => boolean;\n applyForSoftwareJob: (sing?: boolean) => boolean;\n applyForWaiterJob: (sing?: boolean) => boolean;\n canAccessBladeburner: () => boolean;\n canAccessCorporation: () => boolean;\n canAccessGang: () => boolean;\n canAccessResleeving: () => boolean;\n canAfford: (cost: number) => boolean;\n gainHackingExp: (exp: number) => void;\n gainStrengthExp: (exp: number) => void;\n gainDefenseExp: (exp: number) => void;\n gainDexterityExp: (exp: number) => void;\n gainAgilityExp: (exp: number) => void;\n gainCharismaExp: (exp: number) => void;\n gainIntelligenceExp: (exp: number) => void;\n gainMoney: (money: number) => void;\n getCurrentServer: () => Server | HacknetServer;\n getGangFaction: () => Faction;\n getGangName: () => string;\n getHomeComputer: () => Server;\n getNextCompanyPosition: (company: Company, entryPosType: CompanyPosition) => CompanyPosition | null;\n getUpgradeHomeRamCost: () => number;\n gotoLocation: (to: LocationName) => boolean;\n hasAugmentation: (aug: string | Augmentation) => boolean;\n hasCorporation: () => boolean;\n hasGangWith: (facName: string) => boolean;\n hasTorRouter: () => boolean;\n hasProgram: (program: string) => boolean;\n inBladeburner: () => boolean;\n inGang: () => boolean;\n isQualified: (company: Company, position: CompanyPosition) => boolean;\n loseMoney: (money: number) => void;\n reapplyAllAugmentations: (resetMultipliers?: boolean) => void;\n reapplyAllSourceFiles: () => void;\n regenerateHp: (amt: number) => void;\n recordMoneySource: (amt: number, source: string) => void;\n setMoney: (amt: number) => void;\n singularityStopWork: () => string;\n startBladeburner: (p: any) => void;\n startFactionWork: (router: IRouter, faction: Faction) => void;\n startClass: (router: IRouter, costMult: number, expMult: number, className: string) => void;\n startCorporation: (corpName: string, additionalShares?: number) => void;\n startCrime: (\n router: IRouter,\n crimeType: string,\n hackExp: number,\n strExp: number,\n defExp: number,\n dexExp: number,\n agiExp: number,\n chaExp: number,\n money: number,\n time: number,\n singParams: any,\n ) => void;\n startFactionFieldWork: (router: IRouter, faction: Faction) => void;\n startFactionHackWork: (router: IRouter, faction: Faction) => void;\n startFactionSecurityWork: (router: IRouter, faction: Faction) => void;\n startFocusing: () => void;\n startGang: (facName: string, isHacking: boolean) => void;\n startWork: (router: IRouter, companyName: string) => void;\n startWorkPartTime: (router: IRouter, companyName: string) => void;\n takeDamage: (amt: number) => boolean;\n travel: (to: CityName) => boolean;\n giveExploit: (exploit: Exploit) => void;\n queryStatFromString: (str: string) => number;\n getIntelligenceBonus: (weight: number) => number;\n getCasinoWinnings: () => number;\n quitJob: (company: string) => void;\n createHacknetServer: () => HacknetServer;\n startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void;\n queueAugmentation: (augmentationName: string) => void;\n receiveInvite: (factionName: string) => void;\n updateSkillLevels: () => void;\n gainCodingContractReward: (reward: ICodingContractReward, difficulty?: number) => string;\n stopFocusing: () => void;\n finishFactionWork: (cancelled: boolean, sing?: boolean) => string;\n finishClass: (sing?: boolean) => string;\n finishWork: (cancelled: boolean, sing?: boolean) => string;\n cancelationPenalty: () => number;\n finishWorkPartTime: (sing?: boolean) => string;\n finishCrime: (cancelled: boolean) => string;\n finishCreateProgramWork: (cancelled: boolean) => string;\n resetMultipliers: () => void;\n prestigeAugmentation: () => void;\n prestigeSourceFile: () => void;\n calculateSkill: (exp: number, mult?: number) => number;\n resetWorkStatus: (generalType?: string, group?: string, workType?: string) => void;\n getWorkHackExpGain: () => number;\n getWorkStrExpGain: () => number;\n getWorkDefExpGain: () => number;\n getWorkDexExpGain: () => number;\n getWorkAgiExpGain: () => number;\n getWorkChaExpGain: () => number;\n getWorkRepGain: () => number;\n getWorkMoneyGain: () => number;\n processWorkEarnings: (cycles: number) => void;\n hospitalize: () => void;\n createProgramWork: (numCycles: number) => boolean;\n takeClass: (numCycles: number) => boolean;\n commitCrime: (numCycles: number) => boolean;\n checkForFactionInvitations: () => Faction[];\n setBitNodeNumber: (n: number) => void;\n getMult: (name: string) => number;\n setMult: (name: string, mult: number) => void;\n\n constructor() {\n //Skills and stats\n this.hacking_skill = 1;\n\n //Combat stats\n this.hp = 10;\n this.max_hp = 10;\n this.strength = 1;\n this.defense = 1;\n this.dexterity = 1;\n this.agility = 1;\n\n //Labor stats\n this.charisma = 1;\n\n //Special stats\n this.intelligence = 0;\n\n //Hacking multipliers\n this.hacking_chance_mult = 1;\n this.hacking_speed_mult = 1;\n this.hacking_money_mult = 1;\n this.hacking_grow_mult = 1;\n\n //Experience and multipliers\n this.hacking_exp = 0;\n this.strength_exp = 0;\n this.defense_exp = 0;\n this.dexterity_exp = 0;\n this.agility_exp = 0;\n this.charisma_exp = 0;\n this.intelligence_exp = 0;\n\n this.hacking_mult = 1;\n this.strength_mult = 1;\n this.defense_mult = 1;\n this.dexterity_mult = 1;\n this.agility_mult = 1;\n this.charisma_mult = 1;\n\n this.hacking_exp_mult = 1;\n this.strength_exp_mult = 1;\n this.defense_exp_mult = 1;\n this.dexterity_exp_mult = 1;\n this.agility_exp_mult = 1;\n this.charisma_exp_mult = 1;\n\n this.company_rep_mult = 1;\n this.faction_rep_mult = 1;\n\n //Money\n this.money = new Decimal(1000);\n\n //IP Address of Starting (home) computer\n this.homeComputer = \"\";\n\n //Location information\n this.city = CityName.Sector12;\n this.location = LocationName.TravelAgency;\n\n // Jobs that the player holds\n // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object)\n // The CompanyPosition name must match a key value in CompanyPositions\n this.jobs = {};\n\n // Company at which player is CURRENTLY working (only valid when the player is actively working)\n this.companyName = \"\"; // Name of Company. Must match a key value in Companies ma;\n\n // Servers\n this.currentServer = \"\"; //IP address of Server currently being accessed through termina;\n this.purchasedServers = []; //IP Addresses of purchased server;\n\n // Hacknet Nodes/Servers\n this.hacknetNodes = []; // Note= For Hacknet Servers, this array holds the IP addresses of the server;\n this.hashManager = new HashManager();\n\n //Factions\n this.factions = []; //Names of all factions player has joine;\n this.factionInvitations = []; //Outstanding faction invitation;\n\n //Augmentations\n this.queuedAugmentations = [];\n this.augmentations = [];\n\n this.sourceFiles = [];\n\n //Crime statistics\n this.numPeopleKilled = 0;\n this.karma = 0;\n\n this.crime_money_mult = 1;\n this.crime_success_mult = 1;\n\n //Flags/variables for working (Company, Faction, Creating Program, Taking Class)\n this.isWorking = false;\n this.focus = false;\n this.workType = \"\";\n\n this.currentWorkFactionName = \"\";\n this.currentWorkFactionDescription = \"\";\n\n this.workHackExpGainRate = 0;\n this.workStrExpGainRate = 0;\n this.workDefExpGainRate = 0;\n this.workDexExpGainRate = 0;\n this.workAgiExpGainRate = 0;\n this.workChaExpGainRate = 0;\n this.workRepGainRate = 0;\n this.workMoneyGainRate = 0;\n this.workMoneyLossRate = 0;\n\n this.workHackExpGained = 0;\n this.workStrExpGained = 0;\n this.workDefExpGained = 0;\n this.workDexExpGained = 0;\n this.workAgiExpGained = 0;\n this.workChaExpGained = 0;\n this.workRepGained = 0;\n this.workMoneyGained = 0;\n\n this.createProgramName = \"\";\n this.createProgramReqLvl = 0;\n\n this.className = \"\";\n\n this.crimeType = \"\";\n\n this.timeWorked = 0; //in m;\n this.timeWorkedCreateProgram = 0;\n this.timeNeededToCompleteWork = 0;\n\n this.work_money_mult = 1;\n\n //Hacknet Node multipliers\n this.hacknet_node_money_mult = 1;\n this.hacknet_node_purchase_cost_mult = 1;\n this.hacknet_node_ram_cost_mult = 1;\n this.hacknet_node_core_cost_mult = 1;\n this.hacknet_node_level_cost_mult = 1;\n\n //Stock Market\n this.hasWseAccount = false;\n this.hasTixApiAccess = false;\n this.has4SData = false;\n this.has4SDataTixApi = false;\n\n //Gang\n this.gang = null;\n\n //Corporation\n this.corporation = null;\n\n //Bladeburner\n this.bladeburner = null;\n this.bladeburner_max_stamina_mult = 1;\n this.bladeburner_stamina_gain_mult = 1;\n this.bladeburner_analysis_mult = 1; //Field Analysis Onl;\n this.bladeburner_success_chance_mult = 1;\n\n // Sleeves & Re-sleeving\n this.sleeves = [];\n this.resleeves = [];\n this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenan;\n //bitnode\n this.bitNodeN = 1;\n\n //Used to store the last update time.\n this.lastUpdate = 0;\n this.totalPlaytime = 0;\n this.playtimeSinceLastAug = 0;\n this.playtimeSinceLastBitnode = 0;\n\n // Keep track of where money comes from\n this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentatio;\n this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode ru;\n // Production since last Augmentation installation\n this.scriptProdSinceLastAug = 0;\n\n this.exploits = [];\n\n this.init = generalMethods.init;\n this.prestigeAugmentation = generalMethods.prestigeAugmentation;\n this.prestigeSourceFile = generalMethods.prestigeSourceFile;\n this.receiveInvite = generalMethods.receiveInvite;\n this.calculateSkill = generalMethods.calculateSkill;\n this.updateSkillLevels = generalMethods.updateSkillLevels;\n this.resetMultipliers = generalMethods.resetMultipliers;\n this.hasProgram = generalMethods.hasProgram;\n this.setMoney = generalMethods.setMoney;\n this.gainMoney = generalMethods.gainMoney;\n this.loseMoney = generalMethods.loseMoney;\n this.canAfford = generalMethods.canAfford;\n this.recordMoneySource = generalMethods.recordMoneySource;\n this.gainHackingExp = generalMethods.gainHackingExp;\n this.gainStrengthExp = generalMethods.gainStrengthExp;\n this.gainDefenseExp = generalMethods.gainDefenseExp;\n this.gainDexterityExp = generalMethods.gainDexterityExp;\n this.gainAgilityExp = generalMethods.gainAgilityExp;\n this.gainCharismaExp = generalMethods.gainCharismaExp;\n this.gainIntelligenceExp = generalMethods.gainIntelligenceExp;\n this.queryStatFromString = generalMethods.queryStatFromString;\n this.resetWorkStatus = generalMethods.resetWorkStatus;\n this.processWorkEarnings = generalMethods.processWorkEarnings;\n this.startWork = generalMethods.startWork;\n this.cancelationPenalty = generalMethods.cancelationPenalty;\n this.work = generalMethods.work;\n this.finishWork = generalMethods.finishWork;\n this.startWorkPartTime = generalMethods.startWorkPartTime;\n this.workPartTime = generalMethods.workPartTime;\n this.finishWorkPartTime = generalMethods.finishWorkPartTime;\n this.startFocusing = generalMethods.startFocusing;\n this.stopFocusing = generalMethods.stopFocusing;\n this.startFactionWork = generalMethods.startFactionWork;\n this.startFactionHackWork = generalMethods.startFactionHackWork;\n this.startFactionFieldWork = generalMethods.startFactionFieldWork;\n this.startFactionSecurityWork = generalMethods.startFactionSecurityWork;\n this.workForFaction = generalMethods.workForFaction;\n this.finishFactionWork = generalMethods.finishFactionWork;\n this.getWorkMoneyGain = generalMethods.getWorkMoneyGain;\n this.getWorkHackExpGain = generalMethods.getWorkHackExpGain;\n this.getWorkStrExpGain = generalMethods.getWorkStrExpGain;\n this.getWorkDefExpGain = generalMethods.getWorkDefExpGain;\n this.getWorkDexExpGain = generalMethods.getWorkDexExpGain;\n this.getWorkAgiExpGain = generalMethods.getWorkAgiExpGain;\n this.getWorkChaExpGain = generalMethods.getWorkChaExpGain;\n this.getWorkRepGain = generalMethods.getWorkRepGain;\n this.startCreateProgramWork = generalMethods.startCreateProgramWork;\n this.createProgramWork = generalMethods.createProgramWork;\n this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;\n this.startClass = generalMethods.startClass;\n this.takeClass = generalMethods.takeClass;\n this.finishClass = generalMethods.finishClass;\n this.startCrime = generalMethods.startCrime;\n this.commitCrime = generalMethods.commitCrime;\n this.finishCrime = generalMethods.finishCrime;\n this.singularityStopWork = generalMethods.singularityStopWork;\n this.takeDamage = generalMethods.takeDamage;\n this.regenerateHp = generalMethods.regenerateHp;\n this.hospitalize = generalMethods.hospitalize;\n this.applyForJob = generalMethods.applyForJob;\n this.getNextCompanyPosition = generalMethods.getNextCompanyPosition;\n this.quitJob = generalMethods.quitJob;\n this.applyForSoftwareJob = generalMethods.applyForSoftwareJob;\n this.applyForSoftwareConsultantJob = generalMethods.applyForSoftwareConsultantJob;\n this.applyForItJob = generalMethods.applyForItJob;\n this.applyForSecurityEngineerJob = generalMethods.applyForSecurityEngineerJob;\n this.applyForNetworkEngineerJob = generalMethods.applyForNetworkEngineerJob;\n this.applyForBusinessJob = generalMethods.applyForBusinessJob;\n this.applyForBusinessConsultantJob = generalMethods.applyForBusinessConsultantJob;\n this.applyForSecurityJob = generalMethods.applyForSecurityJob;\n this.applyForAgentJob = generalMethods.applyForAgentJob;\n this.applyForEmployeeJob = generalMethods.applyForEmployeeJob;\n this.applyForPartTimeEmployeeJob = generalMethods.applyForPartTimeEmployeeJob;\n this.applyForWaiterJob = generalMethods.applyForWaiterJob;\n this.applyForPartTimeWaiterJob = generalMethods.applyForPartTimeWaiterJob;\n this.isQualified = generalMethods.isQualified;\n this.reapplyAllAugmentations = generalMethods.reapplyAllAugmentations;\n this.reapplyAllSourceFiles = generalMethods.reapplyAllSourceFiles;\n this.checkForFactionInvitations = generalMethods.checkForFactionInvitations;\n this.setBitNodeNumber = generalMethods.setBitNodeNumber;\n this.queueAugmentation = generalMethods.queueAugmentation;\n this.gainCodingContractReward = generalMethods.gainCodingContractReward;\n this.travel = generalMethods.travel;\n this.gotoLocation = generalMethods.gotoLocation;\n this.canAccessResleeving = generalMethods.canAccessResleeving;\n this.giveExploit = generalMethods.giveExploit;\n this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;\n this.getCasinoWinnings = generalMethods.getCasinoWinnings;\n this.hasAugmentation = augmentationMethods.hasAugmentation;\n this.canAccessBladeburner = bladeburnerMethods.canAccessBladeburner;\n this.inBladeburner = bladeburnerMethods.inBladeburner;\n this.startBladeburner = bladeburnerMethods.startBladeburner;\n this.canAccessCorporation = corporationMethods.canAccessCorporation;\n this.hasCorporation = corporationMethods.hasCorporation;\n this.startCorporation = corporationMethods.startCorporation;\n this.canAccessGang = gangMethods.canAccessGang;\n this.getGangFaction = gangMethods.getGangFaction;\n this.getGangName = gangMethods.getGangName;\n this.hasGangWith = gangMethods.hasGangWith;\n this.inGang = gangMethods.inGang;\n this.startGang = gangMethods.startGang;\n\n this.hasTorRouter = serverMethods.hasTorRouter;\n this.getCurrentServer = serverMethods.getCurrentServer;\n this.getHomeComputer = serverMethods.getHomeComputer;\n this.getUpgradeHomeRamCost = serverMethods.getUpgradeHomeRamCost;\n this.createHacknetServer = serverMethods.createHacknetServer;\n this.factionWorkType = \"\";\n this.committingCrimeThruSingFn = false;\n this.singFnCrimeWorkerScript = null;\n\n this.getMult = generalMethods.getMult;\n this.setMult = generalMethods.setMult;\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"PlayerObject\", this);\n }\n\n /**\n * Initiatizes a PlayerObject object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): PlayerObject {\n return Generic_fromJSON(PlayerObject, value.data);\n }\n}\n\nReviver.constructors.PlayerObject = PlayerObject;\n","/**\n * Augmentation-related methods for the Player class (PlayerObject)\n */\nimport { IPlayer } from \"../IPlayer\";\n\nimport { Augmentation } from \"../../Augmentation/Augmentation\";\n\nexport function hasAugmentation(this: IPlayer, aug: string | Augmentation): boolean {\n const augName: string = aug instanceof Augmentation ? aug.name : aug;\n\n for (const owned of this.augmentations) {\n if (owned.name === augName) {\n return true;\n }\n }\n\n for (const owned of this.queuedAugmentations) {\n if (owned.name === augName) {\n return true;\n }\n }\n\n return false;\n}\n","/**\n * Clears defined properties from an object.\n * Does not delete up the prototype chain.\n * @deprecated Look into using `Map` or `Set` rather than manipulating properties on an Object.\n * @param obj the object to clear all properties\n */\n// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\nexport function clearObject(obj: any): void {\n for (const key in obj) {\n if (obj.hasOwnProperty(key)) {\n // tslint:disable-next-line:no-dynamic-delete\n delete obj[key];\n }\n }\n}\n","import * as posNames from \"./companypositionnames\";\nimport { IConstructorParams } from \"../Company\";\n\nimport { IMap } from \"../../types\";\nimport { LocationName } from \"../../Locations/data/LocationNames\";\n\n// Create Objects containing Company Positions by category\n// Will help in metadata construction later\nconst AllSoftwarePositions: IMap = {};\nconst AllITPositions: IMap = {};\nconst AllNetworkEngineerPositions: IMap = {};\nconst SecurityEngineerPositions: IMap = {};\nconst AllTechnologyPositions: IMap = {};\nconst AllBusinessPositions: IMap = {};\nconst AllAgentPositions: IMap = {};\nconst AllSecurityPositions: IMap = {};\nconst AllSoftwareConsultantPositions: IMap = {};\nconst AllBusinessConsultantPositions: IMap = {};\nconst SoftwarePositionsUpToHeadOfEngineering: IMap = {};\nconst SoftwarePositionsUpToLeadDeveloper: IMap = {};\nconst BusinessPositionsUpToOperationsManager: IMap = {};\nconst WaiterOnly: IMap = {};\nconst EmployeeOnly: IMap = {};\nconst PartTimeWaiterOnly: IMap = {};\nconst PartTimeEmployeeOnly: IMap = {};\nconst OperationsManagerOnly: IMap = {};\nconst CEOOnly: IMap = {};\n\nposNames.SoftwareCompanyPositions.forEach((e) => {\n AllSoftwarePositions[e] = true;\n AllTechnologyPositions[e] = true;\n});\n\nposNames.ITCompanyPositions.forEach((e) => {\n AllITPositions[e] = true;\n AllTechnologyPositions[e] = true;\n});\n\nposNames.NetworkEngineerCompanyPositions.forEach((e) => {\n AllNetworkEngineerPositions[e] = true;\n AllTechnologyPositions[e] = true;\n});\n\nAllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;\nSecurityEngineerPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;\n\nposNames.BusinessCompanyPositions.forEach((e) => {\n AllBusinessPositions[e] = true;\n});\n\nposNames.SecurityCompanyPositions.forEach((e) => {\n AllSecurityPositions[e] = true;\n});\n\nposNames.AgentCompanyPositions.forEach((e) => {\n AllAgentPositions[e] = true;\n});\n\nposNames.SoftwareConsultantCompanyPositions.forEach((e) => {\n AllSoftwareConsultantPositions[e] = true;\n});\n\nposNames.BusinessConsultantCompanyPositions.forEach((e) => {\n AllBusinessConsultantPositions[e] = true;\n});\n\nfor (let i = 0; i < posNames.SoftwareCompanyPositions.length; ++i) {\n const e = posNames.SoftwareCompanyPositions[i];\n if (i <= 5) {\n SoftwarePositionsUpToHeadOfEngineering[e] = true;\n }\n if (i <= 3) {\n SoftwarePositionsUpToLeadDeveloper[e] = true;\n }\n}\nfor (let i = 0; i < posNames.BusinessCompanyPositions.length; ++i) {\n const e = posNames.BusinessCompanyPositions[i];\n if (i <= 3) {\n BusinessPositionsUpToOperationsManager[e] = true;\n }\n}\n\nWaiterOnly[posNames.MiscCompanyPositions[0]] = true;\nEmployeeOnly[posNames.MiscCompanyPositions[1]] = true;\nPartTimeWaiterOnly[posNames.PartTimeCompanyPositions[0]] = true;\nPartTimeEmployeeOnly[posNames.PartTimeCompanyPositions[1]] = true;\nOperationsManagerOnly[posNames.BusinessCompanyPositions[3]] = true;\nCEOOnly[posNames.BusinessCompanyPositions[5]] = true;\n\n// Metadata\nexport const companiesMetadata: IConstructorParams[] = [\n {\n name: LocationName.AevumECorp,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 3,\n salaryMultiplier: 3,\n jobStatReqOffset: 249,\n },\n {\n name: LocationName.Sector12MegaCorp,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 3,\n salaryMultiplier: 3,\n jobStatReqOffset: 249,\n },\n {\n name: LocationName.AevumBachmanAndAssociates,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.6,\n salaryMultiplier: 2.6,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.Sector12BladeIndustries,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.75,\n salaryMultiplier: 2.75,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.VolhavenNWO,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.75,\n salaryMultiplier: 2.75,\n jobStatReqOffset: 249,\n },\n {\n name: LocationName.AevumClarkeIncorporated,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.25,\n salaryMultiplier: 2.25,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.VolhavenOmniTekIncorporated,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.25,\n salaryMultiplier: 2.25,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.Sector12FourSigma,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.5,\n salaryMultiplier: 2.5,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.ChongqingKuaiGongInternational,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),\n expMultiplier: 2.2,\n salaryMultiplier: 2.2,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.AevumFulcrumTechnologies,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions),\n expMultiplier: 2,\n salaryMultiplier: 2,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.IshimaStormTechnologies,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllSoftwareConsultantPositions, AllBusinessPositions),\n expMultiplier: 1.8,\n salaryMultiplier: 1.8,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.NewTokyoDefComm,\n info: \"\",\n companyPositions: Object.assign({}, CEOOnly, AllTechnologyPositions, AllSoftwareConsultantPositions),\n expMultiplier: 1.75,\n salaryMultiplier: 1.75,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.VolhavenHeliosLabs,\n info: \"\",\n companyPositions: Object.assign({}, CEOOnly, AllTechnologyPositions, AllSoftwareConsultantPositions),\n expMultiplier: 1.8,\n salaryMultiplier: 1.8,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.NewTokyoVitaLife,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),\n expMultiplier: 1.8,\n salaryMultiplier: 1.8,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.Sector12IcarusMicrosystems,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),\n expMultiplier: 1.9,\n salaryMultiplier: 1.9,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.Sector12UniversalEnergy,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),\n expMultiplier: 2,\n salaryMultiplier: 2,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.AevumGalacticCybersystems,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),\n expMultiplier: 1.9,\n salaryMultiplier: 1.9,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.AevumAeroCorp,\n info: \"\",\n companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),\n expMultiplier: 1.7,\n salaryMultiplier: 1.7,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.VolhavenOmniaCybersystems,\n info: \"\",\n companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),\n expMultiplier: 1.7,\n salaryMultiplier: 1.7,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.ChongqingSolarisSpaceSystems,\n info: \"\",\n companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),\n expMultiplier: 1.7,\n salaryMultiplier: 1.7,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.Sector12DeltaOne,\n info: \"\",\n companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),\n expMultiplier: 1.6,\n salaryMultiplier: 1.6,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.NewTokyoGlobalPharmaceuticals,\n info: \"\",\n companyPositions: Object.assign(\n {},\n AllTechnologyPositions,\n AllBusinessPositions,\n AllSoftwareConsultantPositions,\n AllSecurityPositions,\n ),\n expMultiplier: 1.8,\n salaryMultiplier: 1.8,\n jobStatReqOffset: 224,\n },\n {\n name: LocationName.IshimaNovaMedical,\n info: \"\",\n companyPositions: Object.assign(\n {},\n AllTechnologyPositions,\n AllBusinessPositions,\n AllSoftwareConsultantPositions,\n AllSecurityPositions,\n ),\n expMultiplier: 1.75,\n salaryMultiplier: 1.75,\n jobStatReqOffset: 199,\n },\n {\n name: LocationName.Sector12CIA,\n info: \"\",\n companyPositions: Object.assign(\n {},\n SoftwarePositionsUpToHeadOfEngineering,\n AllNetworkEngineerPositions,\n SecurityEngineerPositions,\n AllITPositions,\n AllSecurityPositions,\n AllAgentPositions,\n ),\n expMultiplier: 2,\n salaryMultiplier: 2,\n jobStatReqOffset: 149,\n },\n {\n name: LocationName.Sector12NSA,\n info: \"\",\n companyPositions: Object.assign(\n {},\n SoftwarePositionsUpToHeadOfEngineering,\n AllNetworkEngineerPositions,\n SecurityEngineerPositions,\n AllITPositions,\n AllSecurityPositions,\n AllAgentPositions,\n ),\n expMultiplier: 2,\n salaryMultiplier: 2,\n jobStatReqOffset: 149,\n },\n {\n name: LocationName.AevumWatchdogSecurity,\n info: \"\",\n companyPositions: Object.assign(\n {},\n SoftwarePositionsUpToHeadOfEngineering,\n AllNetworkEngineerPositions,\n AllITPositions,\n AllSecurityPositions,\n AllAgentPositions,\n AllSoftwareConsultantPositions,\n ),\n expMultiplier: 1.5,\n salaryMultiplier: 1.5,\n jobStatReqOffset: 124,\n },\n {\n name: LocationName.VolhavenLexoCorp,\n info: \"\",\n companyPositions: Object.assign(\n {},\n AllTechnologyPositions,\n AllSoftwareConsultantPositions,\n AllBusinessPositions,\n AllSecurityPositions,\n ),\n expMultiplier: 1.4,\n salaryMultiplier: 1.4,\n jobStatReqOffset: 99,\n },\n {\n name: LocationName.AevumRhoConstruction,\n info: \"\",\n companyPositions: Object.assign({}, SoftwarePositionsUpToLeadDeveloper, BusinessPositionsUpToOperationsManager),\n expMultiplier: 1.3,\n salaryMultiplier: 1.3,\n jobStatReqOffset: 49,\n },\n {\n name: LocationName.Sector12AlphaEnterprises,\n info: \"\",\n companyPositions: Object.assign(\n {},\n SoftwarePositionsUpToLeadDeveloper,\n BusinessPositionsUpToOperationsManager,\n AllSoftwareConsultantPositions,\n ),\n expMultiplier: 1.5,\n salaryMultiplier: 1.5,\n jobStatReqOffset: 99,\n },\n {\n name: LocationName.AevumPolice,\n info: \"\",\n companyPositions: Object.assign({}, AllSecurityPositions, SoftwarePositionsUpToLeadDeveloper),\n expMultiplier: 1.3,\n salaryMultiplier: 1.3,\n jobStatReqOffset: 99,\n },\n {\n name: LocationName.VolhavenSysCoreSecurities,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions),\n expMultiplier: 1.3,\n salaryMultiplier: 1.3,\n jobStatReqOffset: 124,\n },\n {\n name: LocationName.VolhavenCompuTek,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions),\n expMultiplier: 1.2,\n salaryMultiplier: 1.2,\n jobStatReqOffset: 74,\n },\n {\n name: LocationName.AevumNetLinkTechnologies,\n info: \"\",\n companyPositions: Object.assign({}, AllTechnologyPositions),\n expMultiplier: 1.2,\n salaryMultiplier: 1.2,\n jobStatReqOffset: 99,\n },\n {\n name: LocationName.Sector12CarmichaelSecurity,\n info: \"\",\n companyPositions: Object.assign(\n {},\n AllTechnologyPositions,\n AllSoftwareConsultantPositions,\n AllAgentPositions,\n AllSecurityPositions,\n ),\n expMultiplier: 1.2,\n salaryMultiplier: 1.2,\n jobStatReqOffset: 74,\n },\n {\n name: LocationName.Sector12FoodNStuff,\n info: \"\",\n companyPositions: Object.assign({}, EmployeeOnly, PartTimeEmployeeOnly),\n expMultiplier: 1,\n salaryMultiplier: 1,\n jobStatReqOffset: 0,\n },\n {\n name: LocationName.Sector12JoesGuns,\n info: \"\",\n companyPositions: Object.assign({}, EmployeeOnly, PartTimeEmployeeOnly),\n expMultiplier: 1,\n salaryMultiplier: 1,\n jobStatReqOffset: 0,\n },\n {\n name: LocationName.IshimaOmegaSoftware,\n info: \"\",\n companyPositions: Object.assign({}, AllSoftwarePositions, AllSoftwareConsultantPositions, AllITPositions),\n expMultiplier: 1.1,\n salaryMultiplier: 1.1,\n jobStatReqOffset: 49,\n },\n {\n name: LocationName.NewTokyoNoodleBar,\n info: \"\",\n companyPositions: Object.assign({}, WaiterOnly, PartTimeWaiterOnly),\n expMultiplier: 1,\n salaryMultiplier: 1,\n jobStatReqOffset: 0,\n },\n];\n","export interface IConstructorParams {\n name: string;\n cost: number;\n desc: string;\n advertisingMult?: number;\n employeeChaMult?: number;\n employeeCreMult?: number;\n employeeEffMult?: number;\n employeeIntMult?: number;\n productionMult?: number;\n productProductionMult?: number;\n salesMult?: number;\n sciResearchMult?: number;\n storageMult?: number;\n}\n\nexport class Research {\n // Name of research. This will be used to identify researches in the Research Tree\n name = \"\";\n\n // How much scientific research it costs to unlock this\n cost = 0;\n\n // Description of what the Research does\n desc = \"\";\n\n // All possible generic upgrades for the company, in the form of multipliers\n advertisingMult = 1;\n employeeChaMult = 1;\n employeeCreMult = 1;\n employeeEffMult = 1;\n employeeIntMult = 1;\n productionMult = 1;\n productProductionMult = 1;\n salesMult = 1;\n sciResearchMult = 1;\n storageMult = 1;\n\n constructor(p: IConstructorParams = { name: \"\", cost: 0, desc: \"\" }) {\n this.name = p.name;\n this.cost = p.cost;\n this.desc = p.desc;\n if (p.advertisingMult) {\n this.advertisingMult = p.advertisingMult;\n }\n if (p.employeeChaMult) {\n this.employeeChaMult = p.employeeChaMult;\n }\n if (p.employeeCreMult) {\n this.employeeCreMult = p.employeeCreMult;\n }\n if (p.employeeEffMult) {\n this.employeeEffMult = p.employeeEffMult;\n }\n if (p.employeeIntMult) {\n this.employeeIntMult = p.employeeIntMult;\n }\n if (p.productionMult) {\n this.productionMult = p.productionMult;\n }\n if (p.productProductionMult) {\n this.productProductionMult = p.productProductionMult;\n }\n if (p.salesMult) {\n this.salesMult = p.salesMult;\n }\n if (p.sciResearchMult) {\n this.sciResearchMult = p.sciResearchMult;\n }\n if (p.storageMult) {\n this.storageMult = p.storageMult;\n }\n }\n}\n","import { IConstructorParams } from \"../Research\";\n\nexport const researchMetadata: IConstructorParams[] = [\n {\n name: \"AutoBrew\",\n cost: 12e3,\n desc:\n \"Automatically keep your employees fully caffeinated with \" +\n \"coffee injections. This research will keep the energy of all \" +\n \"employees at its maximum possible value, for no cost. \" +\n \"This will also disable the Coffee upgrade.\",\n },\n {\n name: \"AutoPartyManager\",\n cost: 15e3,\n desc:\n \"Automatically analyzes your employees' happiness and morale \" +\n \"and boosts them whenever it detects a decrease. This research will \" +\n \"keep the morale and happiness of all employees at their maximum possible \" +\n \"values, for no cost. \" +\n \"This will also disable the 'Throw Party' feature.\",\n },\n {\n name: \"Automatic Drug Administration\",\n cost: 10e3,\n desc:\n \"Research how to automatically administer performance-enhacing drugs to all of \" +\n \"your employees. This unlocks Drug-related Research.\",\n },\n {\n name: \"Bulk Purchasing\",\n cost: 5e3,\n desc:\n \"Research the art of buying materials in bulk. This allows you to purchase \" +\n \"any amount of a material instantly.\",\n },\n {\n name: \"CPH4 Injections\",\n cost: 25e3,\n desc:\n \"Develop an advanced and harmless synthetic drug that is administered to \" +\n \"employees to increase all of their stats, except experience, by 10%.\",\n employeeCreMult: 1.1,\n employeeChaMult: 1.1,\n employeeEffMult: 1.1,\n employeeIntMult: 1.1,\n },\n {\n name: \"Drones\",\n cost: 5e3,\n desc:\n \"Acquire the knowledge needed to create advanced drones. This research does nothing \" +\n \"by itself, but unlocks other Drone-related research.\",\n },\n {\n name: \"Drones - Assembly\",\n cost: 25e3,\n desc:\n \"Manufacture and use Assembly Drones to improve the efficiency of \" +\n \"your production lines. This increases all production by 20%.\",\n productionMult: 1.2,\n },\n {\n name: \"Drones - Transport\",\n cost: 30e3,\n desc:\n \"Manufacture and use intelligent Transport Drones to optimize \" +\n \"your warehouses. This increases the storage space of all warehouses \" +\n \"by 50%.\",\n storageMult: 1.5,\n },\n {\n name: \"Go-Juice\",\n cost: 25e3,\n desc:\n \"Provide employees with Go-Juice, a coffee-derivative that further enhances \" +\n \"the brain's dopamine production. This increases the maximum energy of all \" +\n \"employees by 10.\",\n },\n {\n name: \"Hi-Tech R&D Laboratory\",\n cost: 5e3,\n desc:\n \"Construct a cutting edge facility dedicated to advanced research and \" +\n \"and development. This allows you to spend Scientific Research \" +\n \"on powerful upgrades. It also globally increases Scientific Research \" +\n \"production by 10%.\",\n sciResearchMult: 1.1,\n },\n {\n name: \"HRBuddy-Recruitment\",\n cost: 15e3,\n desc:\n \"Use automated software to handle the hiring of employees. With this \" +\n \"research, each office will automatically hire one employee per \" +\n \"market cycle if there is available space.\",\n },\n {\n name: \"HRBuddy-Training\",\n cost: 20e3,\n desc:\n \"Use automated software to handle the training of employees. With this \" +\n \"research, each employee hired with HRBuddy-Recruitment will automatically \" +\n \"be assigned to 'Training', rather than being unassigned.\",\n },\n {\n name: \"JoyWire\",\n cost: 20e3,\n desc: \"A brain implant which is installed in employees, increasing their \" + \"maximum happiness by 10.\",\n },\n {\n name: \"Market-TA.I\",\n cost: 20e3,\n desc:\n \"Develop advanced AI software that uses technical analysis to \" +\n \"help you understand and exploit the market. This research \" +\n \"allows you to know what price to sell your Materials/Products \" +\n \"at in order to avoid losing sales due to having too high of a mark-up. \" +\n \"It also lets you automatically use that sale price.\",\n },\n {\n name: \"Market-TA.II\",\n cost: 50e3,\n desc:\n \"Develop double-advanced AI software that uses technical analysis to \" +\n \"help you understand and exploit the market. This research \" +\n \"allows you to know how many sales of a Material/Product you lose or gain \" +\n \"from having too high or too low or a sale price. It also lets you automatically \" +\n \"set the sale price of your Materials/Products at the optimal price such that \" +\n \"the amount sold matches the amount produced.\",\n },\n {\n name: \"Overclock\",\n cost: 15e3,\n desc:\n \"Equip employees with a headset that uses transcranial direct current \" +\n \"stimulation (tDCS) to increase the speed of their neurotransmitters. \" +\n \"This research increases the intelligence and efficiency of all \" +\n \"employees by 25%.\",\n employeeEffMult: 1.25,\n employeeIntMult: 1.25,\n },\n {\n name: \"Self-Correcting Assemblers\",\n cost: 25e3,\n desc:\n \"Create assemblers that can be used for universal production. \" +\n \"These assemblers use deep learning to improve their efficiency \" +\n \"at their tasks. This research increases all production by 10%\",\n productionMult: 1.1,\n },\n {\n name: \"Sti.mu\",\n cost: 30e3,\n desc:\n \"Upgrade the tDCS headset to stimulate regions of the brain that \" +\n \"control confidence and enthusiasm. This research increases the max \" +\n \"morale of all employees by 10.\",\n },\n {\n name: \"sudo.Assist\",\n cost: 15e3,\n desc: \"Develop a virtual assistant AI to handle and manage administrative \" + \"issues for your corporation.\",\n },\n {\n name: \"uPgrade: Capacity.I\",\n cost: 20e3,\n desc:\n \"Expand the industry's capacity for designing and manufacturing its \" +\n \"various products. This increases the industry's maximum number of products \" +\n \"by 1 (from 3 to 4).\",\n },\n {\n name: \"uPgrade: Capacity.II\",\n cost: 30e3,\n desc:\n \"Expand the industry's capacity for designing and manufacturing its \" +\n \"various products. This increases the industry's maximum number of products \" +\n \"by 1 (from 4 to 5).\",\n },\n {\n name: \"uPgrade: Dashboard\",\n cost: 5e3,\n desc:\n \"Improve the software used to manage the industry's production line \" +\n \"for its various products. This allows you to manage the production and \" +\n \"sale of a product before it's finished being designed.\",\n },\n {\n name: \"uPgrade: Fulcrum\",\n cost: 10e3,\n desc:\n \"Streamline the manufacturing of this industry's various products. \" +\n \"This research increases the production of your products by 5%\",\n productProductionMult: 1.05,\n },\n];\n","/**\n * Object representing an upgrade that can be purchased with hashes\n */\nexport interface IConstructorParams {\n cost?: number;\n costPerLevel: number;\n desc: string;\n hasTargetServer?: boolean;\n name: string;\n value: number;\n effectText?: (level: number) => JSX.Element | null;\n}\n\nexport class HashUpgrade {\n /**\n * If the upgrade has a flat cost (never increases), it goes here\n * Otherwise, this property should be undefined\n *\n * This property overrides the 'costPerLevel' property\n */\n cost?: number;\n\n /**\n * Base cost for this upgrade. Every time the upgrade is purchased,\n * its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.)\n */\n costPerLevel = 0;\n\n /**\n * Description of what the upgrade does\n */\n desc = \"\";\n\n /**\n * Boolean indicating that this upgrade's effect affects a single server,\n * the \"target\" server\n */\n hasTargetServer = false;\n\n // Name of upgrade\n name = \"\";\n\n // Generic value used to indicate the potency/amount of this upgrade's effect\n // The meaning varies between different upgrades\n value = 0;\n\n constructor(p: IConstructorParams) {\n if (p.cost != null) {\n this.cost = p.cost;\n }\n if (p.effectText != null) {\n this.effectText = p.effectText;\n }\n\n this.costPerLevel = p.costPerLevel;\n this.desc = p.desc;\n this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false;\n this.name = p.name;\n this.value = p.value;\n }\n\n // Functions that returns the UI element to display the effect of this upgrade.\n effectText: (level: number) => JSX.Element | null = () => null;\n\n getCost(levels: number): number {\n if (typeof this.cost === \"number\") {\n return this.cost;\n }\n\n return Math.round((levels + 1) * this.costPerLevel);\n }\n}\n","// Metadata used to construct all Hash Upgrades\nimport React from \"react\";\nimport { IConstructorParams } from \"../HashUpgrade\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Money } from \"../../ui/React/Money\";\n\nexport const HashUpgradesMetadata: IConstructorParams[] = [\n {\n cost: 4,\n costPerLevel: 4,\n desc: \"Sell hashes for $1m\",\n name: \"Sell for Money\",\n effectText: (level: number): JSX.Element | null => (\n <>\n Sold for \n \n ),\n value: 1e6,\n },\n {\n costPerLevel: 100,\n desc: \"Sell hashes for $1b in Corporation funds\",\n name: \"Sell for Corporation Funds\",\n effectText: (level: number): JSX.Element | null => (\n <>\n Sold for Corporation funds.\n \n ),\n value: 1e9,\n },\n {\n costPerLevel: 50,\n desc:\n \"Use hashes to decrease the minimum security of a single server by 2%. \" +\n \"Note that a server's minimum security cannot go below 1. This effect persists \" +\n \"until you install Augmentations (since servers are reset at that time).\",\n hasTargetServer: true,\n name: \"Reduce Minimum Security\",\n value: 0.98,\n },\n {\n costPerLevel: 50,\n desc:\n \"Use hashes to increase the maximum amount of money on a single server by 2%. \" +\n \"This effect persists until you install Augmentations (since servers \" +\n \"are reset at that time).\",\n hasTargetServer: true,\n name: \"Increase Maximum Money\",\n value: 1.02,\n },\n {\n costPerLevel: 50,\n desc:\n \"Use hashes to improve the experience earned when studying at a university by 20%. \" +\n \"This effect persists until you install Augmentations\",\n name: \"Improve Studying\",\n effectText: (level: number): JSX.Element | null => <>Improves studying by {level * 20}%,\n value: 20, // Improves studying by value%\n },\n {\n costPerLevel: 50,\n desc:\n \"Use hashes to improve the experience earned when training at the gym by 20%. This effect \" +\n \"persists until you install Augmentations\",\n name: \"Improve Gym Training\",\n effectText: (level: number): JSX.Element | null => <>Improves training by {level * 20}%,\n value: 20, // Improves training by value%\n },\n {\n costPerLevel: 200,\n desc: \"Exchange hashes for 1k Scientific Research in all of your Corporation's Industries\",\n name: \"Exchange for Corporation Research\",\n effectText: (level: number): JSX.Element | null => (\n <>Acquired a total of {level}k Scientific Research in your industries.\n ),\n value: 1000,\n },\n {\n costPerLevel: 250,\n desc: \"Exchange hashes for 100 Bladeburner Rank\",\n name: \"Exchange for Bladeburner Rank\",\n effectText: (level: number): JSX.Element | null => (\n <>Acquired a total of {numeralWrapper.format(100 * level, \"0a\")} Bladeburner rank\n ),\n value: 100,\n },\n {\n costPerLevel: 250,\n desc: \"Exchanges hashes for 10 Bladeburner Skill Points\",\n name: \"Exchange for Bladeburner SP\",\n effectText: (level: number): JSX.Element | null => (\n <>Acquired a total of {numeralWrapper.format(10 * level, \"0a\")} Bladeburner Skill Points\n ),\n value: 10,\n },\n {\n costPerLevel: 200,\n desc: \"Generate a random Coding Contract somewhere on the network\",\n name: \"Generate Coding Contract\",\n effectText: (level: number): JSX.Element | null => <>Generated {level} contracts.,\n value: 1,\n },\n];\n","/**\n * The environment in which a script runs. The environment holds\n * Netscript functions and arguments for that script.\n */\nimport { IMap } from \"../types\";\n\nexport class Environment {\n /**\n * Parent environment. Used to implement \"scope\"\n */\n parent: Environment | null = null;\n\n /**\n * Whether or not the script that uses this Environment should stop running\n */\n stopFlag = false;\n\n /**\n * Environment variables (currently only Netscript functions)\n */\n vars: IMap = {};\n\n constructor(parent: Environment | null) {\n if (parent instanceof Environment) {\n this.vars = Object.assign({}, parent.vars);\n }\n\n this.parent = parent;\n }\n\n /**\n * Finds the scope where the variable with the given name is defined\n */\n lookup(name: string): Environment | null {\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n let scope: Environment | null = this;\n while (scope) {\n if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {\n return scope;\n }\n scope = scope.parent;\n }\n\n return null;\n }\n\n //Get the current value of a variable\n get(name: string): any {\n if (name in this.vars) {\n return this.vars[name];\n }\n\n throw new Error(`Undefined variable ${name}`);\n }\n\n //Sets the value of a variable in any scope\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n set(name: string, value: any): any {\n const scope = this.lookup(name);\n\n //If scope has a value, then this variable is already set in a higher scope, so\n //set is there. Otherwise, create a new variable in the local scope\n if (scope !== null) {\n return (scope.vars[name] = value);\n } else {\n return (this.vars[name] = value);\n }\n }\n\n //Creates (or overwrites) a variable in the current scope\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n def(name: string, value: any): any {\n return (this.vars[name] = value);\n }\n}\n","// Metadata used for constructing Company Positions\nimport { IConstructorParams } from \"../CompanyPosition\";\nimport * as posNames from \"./companypositionnames\";\n\nexport const companyPositionMetadata: IConstructorParams[] = [\n {\n name: posNames.SoftwareCompanyPositions[0], // Software Enginering Intern\n nextPosition: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer\n baseSalary: 33,\n charismaEffectiveness: 15,\n charismaExpGain: 0.02,\n hackingEffectiveness: 85,\n hackingExpGain: 0.05,\n reqdHacking: 1,\n repMultiplier: 0.9,\n },\n {\n name: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer\n nextPosition: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer\n baseSalary: 80,\n charismaEffectiveness: 15,\n charismaExpGain: 0.05,\n hackingEffectiveness: 85,\n hackingExpGain: 0.1,\n reqdHacking: 51,\n reqdReputation: 8e3,\n repMultiplier: 1.1,\n },\n {\n name: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer\n nextPosition: posNames.SoftwareCompanyPositions[3], // Lead Software Developer\n baseSalary: 165,\n charismaEffectiveness: 20,\n charismaExpGain: 0.08,\n hackingEffectiveness: 80,\n hackingExpGain: 0.4,\n reqdCharisma: 51,\n reqdHacking: 251,\n reqdReputation: 40e3,\n repMultiplier: 1.3,\n },\n {\n name: posNames.SoftwareCompanyPositions[3], // Lead Software Developer\n nextPosition: posNames.SoftwareCompanyPositions[4], // Head of Software\n baseSalary: 500,\n charismaEffectiveness: 25,\n charismaExpGain: 0.1,\n hackingEffectiveness: 75,\n hackingExpGain: 0.8,\n reqdCharisma: 151,\n reqdHacking: 401,\n reqdReputation: 200e3,\n repMultiplier: 1.5,\n },\n {\n name: posNames.SoftwareCompanyPositions[4], // Head of Software\n nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering\n baseSalary: 800,\n charismaEffectiveness: 25,\n charismaExpGain: 0.5,\n hackingEffectiveness: 75,\n hackingExpGain: 1,\n reqdCharisma: 251,\n reqdHacking: 501,\n reqdReputation: 400e3,\n repMultiplier: 1.6,\n },\n {\n name: posNames.SoftwareCompanyPositions[5], // Head of Engineering\n nextPosition: posNames.SoftwareCompanyPositions[6], // Vice President of Technology\n baseSalary: 1650,\n charismaEffectiveness: 25,\n charismaExpGain: 0.5,\n hackingEffectiveness: 75,\n hackingExpGain: 1.1,\n reqdCharisma: 251,\n reqdHacking: 501,\n reqdReputation: 800e3,\n repMultiplier: 1.6,\n },\n {\n name: posNames.SoftwareCompanyPositions[6], // Vice President of Technology\n nextPosition: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer\n baseSalary: 2310,\n charismaEffectiveness: 30,\n charismaExpGain: 0.6,\n hackingEffectiveness: 70,\n hackingExpGain: 1.2,\n reqdCharisma: 401,\n reqdHacking: 601,\n reqdReputation: 1.6e6,\n repMultiplier: 1.75,\n },\n {\n name: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer\n nextPosition: null,\n baseSalary: 2640,\n charismaEffectiveness: 35,\n charismaExpGain: 1,\n hackingEffectiveness: 65,\n hackingExpGain: 1.5,\n reqdCharisma: 501,\n reqdHacking: 751,\n reqdReputation: 3.2e6,\n repMultiplier: 2,\n },\n {\n name: posNames.ITCompanyPositions[0], // IT Intern\n nextPosition: posNames.ITCompanyPositions[1], // IT Analyst\n baseSalary: 26,\n charismaEffectiveness: 10,\n charismaExpGain: 0.01,\n hackingEffectiveness: 90,\n hackingExpGain: 0.04,\n reqdHacking: 1,\n repMultiplier: 0.9,\n },\n {\n name: posNames.ITCompanyPositions[1], // IT Analyst\n nextPosition: posNames.ITCompanyPositions[2], // IT Manager\n baseSalary: 66,\n charismaEffectiveness: 15,\n charismaExpGain: 0.02,\n hackingEffectiveness: 85,\n hackingExpGain: 0.08,\n reqdHacking: 26,\n reqdReputation: 7e3,\n repMultiplier: 1.1,\n },\n {\n name: posNames.ITCompanyPositions[2], // IT Manager\n nextPosition: posNames.ITCompanyPositions[3], // Systems Administrator\n baseSalary: 132,\n charismaEffectiveness: 20,\n charismaExpGain: 0.1,\n hackingEffectiveness: 80,\n hackingExpGain: 0.3,\n reqdCharisma: 51,\n reqdHacking: 151,\n reqdReputation: 35e3,\n repMultiplier: 1.3,\n },\n {\n name: posNames.ITCompanyPositions[3], // Systems Administrator\n nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering\n baseSalary: 410,\n charismaEffectiveness: 20,\n charismaExpGain: 0.2,\n hackingEffectiveness: 80,\n hackingExpGain: 0.5,\n reqdCharisma: 76,\n reqdHacking: 251,\n reqdReputation: 175e3,\n repMultiplier: 1.4,\n },\n {\n name: posNames.SecurityEngineerCompanyPositions[0], // Security Engineer\n nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering\n baseSalary: 121,\n charismaEffectiveness: 15,\n charismaExpGain: 0.05,\n hackingEffectiveness: 85,\n hackingExpGain: 0.4,\n reqdCharisma: 26,\n reqdHacking: 151,\n reqdReputation: 35e3,\n repMultiplier: 1.2,\n },\n {\n name: posNames.NetworkEngineerCompanyPositions[0], // Network Engineer\n nextPosition: posNames.NetworkEngineerCompanyPositions[1], // Network Adminsitrator\n baseSalary: 121,\n charismaEffectiveness: 15,\n charismaExpGain: 0.05,\n hackingEffectiveness: 85,\n hackingExpGain: 0.4,\n reqdCharisma: 26,\n reqdHacking: 151,\n reqdReputation: 35e3,\n repMultiplier: 1.2,\n },\n {\n name: posNames.NetworkEngineerCompanyPositions[1], // Network Administrator\n nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering\n baseSalary: 410,\n charismaEffectiveness: 20,\n charismaExpGain: 0.1,\n hackingEffectiveness: 80,\n hackingExpGain: 0.5,\n reqdCharisma: 76,\n reqdHacking: 251,\n reqdReputation: 175e3,\n repMultiplier: 1.3,\n },\n {\n name: posNames.BusinessCompanyPositions[0], // Business Intern\n nextPosition: posNames.BusinessCompanyPositions[1], // Business Analyst\n baseSalary: 46,\n charismaEffectiveness: 90,\n charismaExpGain: 0.08,\n hackingEffectiveness: 10,\n hackingExpGain: 0.01,\n reqdCharisma: 1,\n reqdHacking: 1,\n repMultiplier: 0.9,\n },\n {\n name: posNames.BusinessCompanyPositions[1], // Business Analyst\n nextPosition: posNames.BusinessCompanyPositions[2], // Business Manager\n baseSalary: 100,\n charismaEffectiveness: 85,\n charismaExpGain: 0.15,\n hackingEffectiveness: 15,\n hackingExpGain: 0.02,\n reqdCharisma: 51,\n reqdHacking: 6,\n reqdReputation: 8e3,\n repMultiplier: 1.1,\n },\n {\n name: posNames.BusinessCompanyPositions[2], // Business Manager\n nextPosition: posNames.BusinessCompanyPositions[3], // Operations Manager\n baseSalary: 200,\n charismaEffectiveness: 85,\n charismaExpGain: 0.3,\n hackingEffectiveness: 15,\n hackingExpGain: 0.02,\n reqdCharisma: 101,\n reqdHacking: 51,\n reqdReputation: 40e3,\n repMultiplier: 1.3,\n },\n {\n name: posNames.BusinessCompanyPositions[3], // Operations Manager\n nextPosition: posNames.BusinessCompanyPositions[4], // Chief Financial Officer\n baseSalary: 660,\n charismaEffectiveness: 85,\n charismaExpGain: 0.4,\n hackingEffectiveness: 15,\n hackingExpGain: 0.02,\n reqdCharisma: 226,\n reqdHacking: 51,\n reqdReputation: 200e3,\n repMultiplier: 1.5,\n },\n {\n name: posNames.BusinessCompanyPositions[4], // Chief Financial Officer\n nextPosition: posNames.BusinessCompanyPositions[5], // Chief Executive Officer\n baseSalary: 1950,\n charismaEffectiveness: 90,\n charismaExpGain: 1,\n hackingEffectiveness: 10,\n hackingExpGain: 0.05,\n reqdCharisma: 501,\n reqdHacking: 76,\n reqdReputation: 800e3,\n repMultiplier: 1.6,\n },\n {\n name: posNames.BusinessCompanyPositions[5], // Chief Executive Officer\n nextPosition: null,\n baseSalary: 3900,\n charismaEffectiveness: 90,\n charismaExpGain: 1.5,\n hackingEffectiveness: 10,\n hackingExpGain: 0.05,\n reqdCharisma: 751,\n reqdHacking: 101,\n reqdReputation: 3.2e6,\n repMultiplier: 1.75,\n },\n {\n name: posNames.SecurityCompanyPositions[0], // Police Officer\n nextPosition: posNames.SecurityCompanyPositions[1], // Police Chief\n baseSalary: 82,\n hackingEffectiveness: 5,\n strengthEffectiveness: 20,\n defenseEffectiveness: 20,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 15,\n hackingExpGain: 0.02,\n strengthExpGain: 0.08,\n defenseExpGain: 0.08,\n dexterityExpGain: 0.08,\n agilityExpGain: 0.08,\n charismaExpGain: 0.04,\n reqdHacking: 11,\n reqdStrength: 101,\n reqdDefense: 101,\n reqdDexterity: 101,\n reqdAgility: 101,\n reqdCharisma: 51,\n reqdReputation: 8e3,\n repMultiplier: 1,\n },\n {\n name: posNames.SecurityCompanyPositions[1], // Police Chief\n nextPosition: null,\n baseSalary: 460,\n hackingEffectiveness: 5,\n strengthEffectiveness: 20,\n defenseEffectiveness: 20,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 15,\n hackingExpGain: 0.02,\n strengthExpGain: 0.1,\n defenseExpGain: 0.1,\n dexterityExpGain: 0.1,\n agilityExpGain: 0.1,\n charismaExpGain: 0.1,\n reqdHacking: 101,\n reqdStrength: 301,\n reqdDefense: 301,\n reqdDexterity: 301,\n reqdAgility: 301,\n reqdCharisma: 151,\n reqdReputation: 36e3,\n repMultiplier: 1.25,\n },\n {\n name: posNames.SecurityCompanyPositions[2], // Security Guard\n nextPosition: posNames.SecurityCompanyPositions[3], // Security Officer\n baseSalary: 50,\n hackingEffectiveness: 5,\n strengthEffectiveness: 20,\n defenseEffectiveness: 20,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 15,\n hackingExpGain: 0.01,\n strengthExpGain: 0.04,\n defenseExpGain: 0.04,\n dexterityExpGain: 0.04,\n agilityExpGain: 0.04,\n charismaExpGain: 0.02,\n reqdStrength: 51,\n reqdDefense: 51,\n reqdDexterity: 51,\n reqdAgility: 51,\n reqdCharisma: 1,\n repMultiplier: 1,\n },\n {\n name: posNames.SecurityCompanyPositions[3], // Security Officer\n nextPosition: posNames.SecurityCompanyPositions[4], // Security Supervisor\n baseSalary: 195,\n hackingEffectiveness: 10,\n strengthEffectiveness: 20,\n defenseEffectiveness: 20,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 10,\n hackingExpGain: 0.02,\n strengthExpGain: 0.1,\n defenseExpGain: 0.1,\n dexterityExpGain: 0.1,\n agilityExpGain: 0.1,\n charismaExpGain: 0.05,\n reqdHacking: 26,\n reqdStrength: 151,\n reqdDefense: 151,\n reqdDexterity: 151,\n reqdAgility: 151,\n reqdCharisma: 51,\n reqdReputation: 8e3,\n repMultiplier: 1.1,\n },\n {\n name: posNames.SecurityCompanyPositions[4], // Security Supervisor\n nextPosition: posNames.SecurityCompanyPositions[5], // Head of Security\n baseSalary: 660,\n hackingEffectiveness: 10,\n strengthEffectiveness: 15,\n defenseEffectiveness: 15,\n dexterityEffectiveness: 15,\n agilityEffectiveness: 15,\n charismaEffectiveness: 30,\n hackingExpGain: 0.02,\n strengthExpGain: 0.12,\n defenseExpGain: 0.12,\n dexterityExpGain: 0.12,\n agilityExpGain: 0.12,\n charismaExpGain: 0.1,\n reqdHacking: 26,\n reqdStrength: 251,\n reqdDefense: 251,\n reqdDexterity: 251,\n reqdAgility: 251,\n reqdCharisma: 101,\n reqdReputation: 36e3,\n repMultiplier: 1.25,\n },\n {\n name: posNames.SecurityCompanyPositions[5], // Head of Security\n nextPosition: null,\n baseSalary: 1320,\n hackingEffectiveness: 10,\n strengthEffectiveness: 15,\n defenseEffectiveness: 15,\n dexterityEffectiveness: 15,\n agilityEffectiveness: 15,\n charismaEffectiveness: 30,\n hackingExpGain: 0.05,\n strengthExpGain: 0.15,\n defenseExpGain: 0.15,\n dexterityExpGain: 0.15,\n agilityExpGain: 0.15,\n charismaExpGain: 0.15,\n reqdHacking: 51,\n reqdStrength: 501,\n reqdDefense: 501,\n reqdDexterity: 501,\n reqdAgility: 501,\n reqdCharisma: 151,\n reqdReputation: 144e3,\n repMultiplier: 1.4,\n },\n {\n name: posNames.AgentCompanyPositions[0], // Field Agent\n nextPosition: posNames.AgentCompanyPositions[1], // Secret Agent\n baseSalary: 330,\n hackingEffectiveness: 10,\n strengthEffectiveness: 15,\n defenseEffectiveness: 15,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 20,\n hackingExpGain: 0.04,\n strengthExpGain: 0.08,\n defenseExpGain: 0.08,\n dexterityExpGain: 0.08,\n agilityExpGain: 0.08,\n charismaExpGain: 0.05,\n reqdHacking: 101,\n reqdStrength: 101,\n reqdDefense: 101,\n reqdDexterity: 101,\n reqdAgility: 101,\n reqdCharisma: 101,\n reqdReputation: 8e3,\n repMultiplier: 1,\n },\n {\n name: posNames.AgentCompanyPositions[1], // Secret Agent\n nextPosition: posNames.AgentCompanyPositions[2], // Special Operative\n baseSalary: 990,\n hackingEffectiveness: 15,\n strengthEffectiveness: 15,\n defenseEffectiveness: 15,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 15,\n hackingExpGain: 0.1,\n strengthExpGain: 0.15,\n defenseExpGain: 0.15,\n dexterityExpGain: 0.15,\n agilityExpGain: 0.15,\n charismaExpGain: 0.1,\n reqdHacking: 201,\n reqdStrength: 251,\n reqdDefense: 251,\n reqdDexterity: 251,\n reqdAgility: 251,\n reqdCharisma: 201,\n reqdReputation: 32e3,\n repMultiplier: 1.25,\n },\n {\n name: posNames.AgentCompanyPositions[2], // Special Operative\n nextPosition: null,\n baseSalary: 2000,\n hackingEffectiveness: 15,\n strengthEffectiveness: 15,\n defenseEffectiveness: 15,\n dexterityEffectiveness: 20,\n agilityEffectiveness: 20,\n charismaEffectiveness: 15,\n hackingExpGain: 0.15,\n strengthExpGain: 0.2,\n defenseExpGain: 0.2,\n dexterityExpGain: 0.2,\n agilityExpGain: 0.2,\n charismaExpGain: 0.15,\n reqdHacking: 251,\n reqdStrength: 501,\n reqdDefense: 501,\n reqdDexterity: 501,\n reqdAgility: 501,\n reqdCharisma: 251,\n reqdReputation: 162e3,\n repMultiplier: 1.5,\n },\n {\n name: posNames.MiscCompanyPositions[0], // Waiter\n nextPosition: null,\n baseSalary: 22,\n strengthEffectiveness: 10,\n dexterityEffectiveness: 10,\n agilityEffectiveness: 10,\n charismaEffectiveness: 70,\n strengthExpGain: 0.02,\n defenseExpGain: 0.02,\n dexterityExpGain: 0.02,\n agilityExpGain: 0.02,\n charismaExpGain: 0.05,\n repMultiplier: 1,\n },\n {\n name: posNames.MiscCompanyPositions[1], // Employee\n nextPosition: null,\n baseSalary: 22,\n strengthEffectiveness: 10,\n dexterityEffectiveness: 10,\n agilityEffectiveness: 10,\n charismaEffectiveness: 70,\n strengthExpGain: 0.02,\n defenseExpGain: 0.02,\n dexterityExpGain: 0.02,\n agilityExpGain: 0.02,\n charismaExpGain: 0.04,\n repMultiplier: 1,\n },\n {\n name: posNames.SoftwareConsultantCompanyPositions[0], // Software Consultant\n nextPosition: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant\n baseSalary: 66,\n hackingEffectiveness: 80,\n charismaEffectiveness: 20,\n hackingExpGain: 0.08,\n charismaExpGain: 0.03,\n reqdHacking: 51,\n repMultiplier: 1,\n },\n {\n name: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant\n nextPosition: null,\n baseSalary: 132,\n hackingEffectiveness: 75,\n charismaEffectiveness: 25,\n hackingExpGain: 0.25,\n charismaExpGain: 0.06,\n reqdHacking: 251,\n reqdCharisma: 51,\n repMultiplier: 1.2,\n },\n {\n name: posNames.BusinessConsultantCompanyPositions[0], // Business Consultant\n nextPosition: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant\n baseSalary: 66,\n hackingEffectiveness: 20,\n charismaEffectiveness: 80,\n hackingExpGain: 0.015,\n charismaExpGain: 0.15,\n reqdHacking: 6,\n reqdCharisma: 51,\n repMultiplier: 1,\n },\n {\n name: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant\n nextPosition: null,\n baseSalary: 525,\n hackingEffectiveness: 15,\n charismaEffectiveness: 85,\n hackingExpGain: 0.015,\n charismaExpGain: 0.3,\n reqdHacking: 51,\n reqdCharisma: 226,\n repMultiplier: 1.2,\n },\n {\n name: posNames.PartTimeCompanyPositions[0], // Part-time waiter\n nextPosition: null,\n baseSalary: 20,\n strengthEffectiveness: 10,\n dexterityEffectiveness: 10,\n agilityEffectiveness: 10,\n charismaEffectiveness: 70,\n strengthExpGain: 0.0075,\n defenseExpGain: 0.0075,\n dexterityExpGain: 0.0075,\n agilityExpGain: 0.0075,\n charismaExpGain: 0.04,\n repMultiplier: 1,\n },\n {\n name: posNames.PartTimeCompanyPositions[1], // Part-time employee\n nextPosition: null,\n baseSalary: 20,\n strengthEffectiveness: 10,\n dexterityEffectiveness: 10,\n agilityEffectiveness: 10,\n charismaEffectiveness: 70,\n strengthExpGain: 0.0075,\n defenseExpGain: 0.0075,\n dexterityExpGain: 0.0075,\n agilityExpGain: 0.0075,\n charismaExpGain: 0.03,\n repMultiplier: 1,\n },\n];\n","import { Reviver, Generic_toJSON, Generic_fromJSON } from \"../utils/JSONReviver\";\nimport { CityName } from \"../Locations/data/CityNames\";\nimport Decimal from \"decimal.js\";\nimport { Industries, IndustryStartingCosts, IndustryResearchTrees } from \"./IndustryData\";\nimport { CorporationConstants } from \"./data/Constants\";\nimport { EmployeePositions } from \"./EmployeePositions\";\nimport { Material } from \"./Material\";\nimport { getRandomInt } from \"../utils/helpers/getRandomInt\";\nimport { calculateEffectWithFactors } from \"../utils/calculateEffectWithFactors\";\nimport { OfficeSpace } from \"./OfficeSpace\";\nimport { Product } from \"./Product\";\nimport { dialogBoxCreate } from \"../ui/React/DialogBox\";\nimport { isString } from \"../utils/helpers/isString\";\nimport { MaterialSizes } from \"./MaterialSizes\";\nimport { Warehouse } from \"./Warehouse\";\nimport { ICorporation } from \"./ICorporation\";\nimport { IIndustry } from \"./IIndustry\";\nimport { IndustryUpgrade, IndustryUpgrades } from \"./IndustryUpgrades\";\n\ninterface IParams {\n name?: string;\n corp?: ICorporation;\n type?: string;\n}\n\nexport class Industry implements IIndustry {\n name = \"\";\n type = Industries.Agriculture;\n sciResearch = new Material({ name: \"Scientific Research\" });\n researched: { [key: string]: boolean | undefined } = {};\n reqMats: { [key: string]: number | undefined } = {};\n\n //An array of the name of materials being produced\n prodMats: string[] = [];\n\n products: { [key: string]: Product | undefined } = {};\n makesProducts = false;\n\n awareness = 0;\n popularity = 0; //Should always be less than awareness\n startingCost = 0;\n\n /* The following are factors for how much production/other things are increased by\n different factors. The production increase always has diminishing returns,\n and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8)\n The number for these represent the exponential. A lower number means more\n diminishing returns */\n reFac = 0; //Real estate Factor\n sciFac = 0; //Scientific Research Factor, affects quality\n hwFac = 0; //Hardware factor\n robFac = 0; //Robotics Factor\n aiFac = 0; //AI Cores factor;\n advFac = 0; //Advertising factor, affects sales\n\n prodMult = 0; //Production multiplier\n\n //Financials\n lastCycleRevenue: any;\n lastCycleExpenses: any;\n thisCycleRevenue: any;\n thisCycleExpenses: any;\n\n //Upgrades\n upgrades: number[] = Array(Object.keys(IndustryUpgrades).length).fill(0);\n\n state = \"START\";\n newInd = true;\n\n //Maps locations to warehouses. 0 if no warehouse at that location\n warehouses: { [key: string]: Warehouse | 0 };\n\n //Maps locations to offices. 0 if no office at that location\n offices: { [key: string]: OfficeSpace | 0 } = {\n [CityName.Aevum]: 0,\n [CityName.Chongqing]: 0,\n [CityName.Sector12]: new OfficeSpace({\n loc: CityName.Sector12,\n size: CorporationConstants.OfficeInitialSize,\n }),\n [CityName.NewTokyo]: 0,\n [CityName.Ishima]: 0,\n [CityName.Volhaven]: 0,\n };\n\n constructor(params: IParams = {}) {\n this.name = params.name ? params.name : \"\";\n this.type = params.type ? params.type : Industries.Agriculture;\n\n //Financials\n this.lastCycleRevenue = new Decimal(0);\n this.lastCycleExpenses = new Decimal(0);\n this.thisCycleRevenue = new Decimal(0);\n this.thisCycleExpenses = new Decimal(0);\n\n this.warehouses = {\n [CityName.Aevum]: 0,\n [CityName.Chongqing]: 0,\n [CityName.Sector12]: new Warehouse({\n corp: params.corp,\n industry: this,\n loc: CityName.Sector12,\n size: CorporationConstants.WarehouseInitialSize,\n }),\n [CityName.NewTokyo]: 0,\n [CityName.Ishima]: 0,\n [CityName.Volhaven]: 0,\n };\n\n this.init();\n }\n\n init(): void {\n //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.)\n const startingCost = IndustryStartingCosts[this.type];\n if (startingCost === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.startingCost = startingCost;\n switch (this.type) {\n case Industries.Energy:\n this.reFac = 0.65;\n this.sciFac = 0.7;\n this.robFac = 0.05;\n this.aiFac = 0.3;\n this.advFac = 0.08;\n this.reqMats = {\n Hardware: 0.1,\n Metal: 0.2,\n };\n this.prodMats = [\"Energy\"];\n break;\n case Industries.Utilities:\n case \"Utilities\":\n this.reFac = 0.5;\n this.sciFac = 0.6;\n this.robFac = 0.4;\n this.aiFac = 0.4;\n this.advFac = 0.08;\n this.reqMats = {\n Hardware: 0.1,\n Metal: 0.1,\n };\n this.prodMats = [\"Water\"];\n break;\n case Industries.Agriculture:\n this.reFac = 0.72;\n this.sciFac = 0.5;\n this.hwFac = 0.2;\n this.robFac = 0.3;\n this.aiFac = 0.3;\n this.advFac = 0.04;\n this.reqMats = {\n Water: 0.5,\n Energy: 0.5,\n };\n this.prodMats = [\"Plants\", \"Food\"];\n break;\n case Industries.Fishing:\n this.reFac = 0.15;\n this.sciFac = 0.35;\n this.hwFac = 0.35;\n this.robFac = 0.5;\n this.aiFac = 0.2;\n this.advFac = 0.08;\n this.reqMats = {\n Energy: 0.5,\n };\n this.prodMats = [\"Food\"];\n break;\n case Industries.Mining:\n this.reFac = 0.3;\n this.sciFac = 0.26;\n this.hwFac = 0.4;\n this.robFac = 0.45;\n this.aiFac = 0.45;\n this.advFac = 0.06;\n this.reqMats = {\n Energy: 0.8,\n };\n this.prodMats = [\"Metal\"];\n break;\n case Industries.Food:\n //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code?\n this.sciFac = 0.12;\n this.hwFac = 0.15;\n this.robFac = 0.3;\n this.aiFac = 0.25;\n this.advFac = 0.25;\n this.reFac = 0.05;\n this.reqMats = {\n Food: 0.5,\n Water: 0.5,\n Energy: 0.2,\n };\n this.makesProducts = true;\n break;\n case Industries.Tobacco:\n this.reFac = 0.15;\n this.sciFac = 0.75;\n this.hwFac = 0.15;\n this.robFac = 0.2;\n this.aiFac = 0.15;\n this.advFac = 0.2;\n this.reqMats = {\n Plants: 1,\n Water: 0.2,\n };\n this.makesProducts = true;\n break;\n case Industries.Chemical:\n this.reFac = 0.25;\n this.sciFac = 0.75;\n this.hwFac = 0.2;\n this.robFac = 0.25;\n this.aiFac = 0.2;\n this.advFac = 0.07;\n this.reqMats = {\n Plants: 1,\n Energy: 0.5,\n Water: 0.5,\n };\n this.prodMats = [\"Chemicals\"];\n break;\n case Industries.Pharmaceutical:\n this.reFac = 0.05;\n this.sciFac = 0.8;\n this.hwFac = 0.15;\n this.robFac = 0.25;\n this.aiFac = 0.2;\n this.advFac = 0.16;\n this.reqMats = {\n Chemicals: 2,\n Energy: 1,\n Water: 0.5,\n };\n this.prodMats = [\"Drugs\"];\n this.makesProducts = true;\n break;\n case Industries.Computer:\n case \"Computer\":\n this.reFac = 0.2;\n this.sciFac = 0.62;\n this.robFac = 0.36;\n this.aiFac = 0.19;\n this.advFac = 0.17;\n this.reqMats = {\n Metal: 2,\n Energy: 1,\n };\n this.prodMats = [\"Hardware\"];\n this.makesProducts = true;\n break;\n case Industries.Robotics:\n this.reFac = 0.32;\n this.sciFac = 0.65;\n this.aiFac = 0.36;\n this.advFac = 0.18;\n this.hwFac = 0.19;\n this.reqMats = {\n Hardware: 5,\n Energy: 3,\n };\n this.prodMats = [\"Robots\"];\n this.makesProducts = true;\n break;\n case Industries.Software:\n this.sciFac = 0.62;\n this.advFac = 0.16;\n this.hwFac = 0.25;\n this.reFac = 0.15;\n this.aiFac = 0.18;\n this.robFac = 0.05;\n this.reqMats = {\n Hardware: 0.5,\n Energy: 0.5,\n };\n this.prodMats = [\"AICores\"];\n this.makesProducts = true;\n break;\n case Industries.Healthcare:\n this.reFac = 0.1;\n this.sciFac = 0.75;\n this.advFac = 0.11;\n this.hwFac = 0.1;\n this.robFac = 0.1;\n this.aiFac = 0.1;\n this.reqMats = {\n Robots: 10,\n AICores: 5,\n Energy: 5,\n Water: 5,\n };\n this.makesProducts = true;\n break;\n case Industries.RealEstate:\n this.robFac = 0.6;\n this.aiFac = 0.6;\n this.advFac = 0.25;\n this.sciFac = 0.05;\n this.hwFac = 0.05;\n this.reqMats = {\n Metal: 5,\n Energy: 5,\n Water: 2,\n Hardware: 4,\n };\n this.prodMats = [\"RealEstate\"];\n this.makesProducts = true;\n break;\n default:\n console.error(`Invalid Industry Type passed into Industry.init(): ${this.type}`);\n return;\n }\n }\n\n getProductDescriptionText(): string {\n if (!this.makesProducts) return \"\";\n switch (this.type) {\n case Industries.Food:\n return \"create and manage restaurants\";\n case Industries.Tobacco:\n return \"create tobacco and tobacco-related products\";\n case Industries.Pharmaceutical:\n return \"develop new pharmaceutical drugs\";\n case Industries.Computer:\n case \"Computer\":\n return \"create new computer hardware and networking infrastructures\";\n case Industries.Robotics:\n return \"build specialized robots and robot-related products\";\n case Industries.Software:\n return \"develop computer software\";\n case Industries.Healthcare:\n return \"build and manage hospitals\";\n case Industries.RealEstate:\n return \"develop and manage real estate properties\";\n default:\n console.error(\"Invalid industry type in Industry.getProductDescriptionText\");\n return \"\";\n }\n }\n\n getMaximumNumberProducts(): number {\n if (!this.makesProducts) return 0;\n\n // Calculate additional number of allowed Products from Research/Upgrades\n let additional = 0;\n if (this.hasResearch(\"uPgrade: Capacity.I\")) ++additional;\n if (this.hasResearch(\"uPgrade: Capacity.II\")) ++additional;\n\n return CorporationConstants.BaseMaxProducts + additional;\n }\n\n hasMaximumNumberProducts(): boolean {\n return Object.keys(this.products).length >= this.getMaximumNumberProducts();\n }\n\n //Calculates the values that factor into the production and properties of\n //materials/products (such as quality, etc.)\n calculateProductionFactors(): void {\n let multSum = 0;\n for (let i = 0; i < CorporationConstants.Cities.length; ++i) {\n const city = CorporationConstants.Cities[i];\n const warehouse = this.warehouses[city];\n if (!(warehouse instanceof Warehouse)) {\n continue;\n }\n\n const materials = warehouse.materials;\n\n const cityMult =\n Math.pow(0.002 * materials.RealEstate.qty + 1, this.reFac) *\n Math.pow(0.002 * materials.Hardware.qty + 1, this.hwFac) *\n Math.pow(0.002 * materials.Robots.qty + 1, this.robFac) *\n Math.pow(0.002 * materials.AICores.qty + 1, this.aiFac);\n multSum += Math.pow(cityMult, 0.73);\n }\n\n multSum < 1 ? (this.prodMult = 1) : (this.prodMult = multSum);\n }\n\n updateWarehouseSizeUsed(warehouse: Warehouse): void {\n warehouse.updateMaterialSizeUsed();\n\n for (const prodName in this.products) {\n if (this.products.hasOwnProperty(prodName)) {\n const prod = this.products[prodName];\n if (prod === undefined) continue;\n warehouse.sizeUsed += prod.data[warehouse.loc][0] * prod.siz;\n }\n }\n }\n\n process(marketCycles = 1, state: string, corporation: ICorporation): void {\n this.state = state;\n\n //At the start of a cycle, store and reset revenue/expenses\n //Then calculate salaries and processs the markets\n if (state === \"START\") {\n if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) {\n console.error(\"NaN in Corporation's computed revenue/expenses\");\n dialogBoxCreate(\n \"Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer\",\n );\n this.thisCycleRevenue = new Decimal(0);\n this.thisCycleExpenses = new Decimal(0);\n }\n this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * CorporationConstants.SecsPerMarketCycle);\n this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * CorporationConstants.SecsPerMarketCycle);\n this.thisCycleRevenue = new Decimal(0);\n this.thisCycleExpenses = new Decimal(0);\n\n // Once you start making revenue, the player should no longer be\n // considered new, and therefore no longer needs the 'tutorial' UI elements\n if (this.lastCycleRevenue.gt(0)) {\n this.newInd = false;\n }\n\n // Process offices (and the employees in them)\n let employeeSalary = 0;\n for (const officeLoc in this.offices) {\n const office = this.offices[officeLoc];\n if (office === 0) continue;\n if (office instanceof OfficeSpace) {\n employeeSalary += office.process(marketCycles, corporation, this);\n }\n }\n this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary);\n\n // Process change in demand/competition of materials/products\n this.processMaterialMarket();\n this.processProductMarket(marketCycles);\n\n // Process loss of popularity\n this.popularity -= marketCycles * 0.0001;\n this.popularity = Math.max(0, this.popularity);\n\n // Process Dreamsense gains\n const popularityGain = corporation.getDreamSenseGain(),\n awarenessGain = popularityGain * 4;\n if (popularityGain > 0) {\n this.popularity += popularityGain * marketCycles;\n this.awareness += awarenessGain * marketCycles;\n }\n\n return;\n }\n\n // Process production, purchase, and import/export of materials\n let res = this.processMaterials(marketCycles, corporation);\n if (Array.isArray(res)) {\n this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);\n this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);\n }\n\n // Process creation, production & sale of products\n res = this.processProducts(marketCycles, corporation);\n if (Array.isArray(res)) {\n this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);\n this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);\n }\n }\n\n // Process change in demand and competition for this industry's materials\n processMaterialMarket(): void {\n //References to prodMats and reqMats\n const reqMats = this.reqMats,\n prodMats = this.prodMats;\n\n //Only 'process the market' for materials that this industry deals with\n for (let i = 0; i < CorporationConstants.Cities.length; ++i) {\n //If this industry has a warehouse in this city, process the market\n //for every material this industry requires or produces\n if (this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse) {\n const wh = this.warehouses[CorporationConstants.Cities[i]];\n if (wh === 0) continue;\n for (const name in reqMats) {\n if (reqMats.hasOwnProperty(name)) {\n wh.materials[name].processMarket();\n }\n }\n\n //Produced materials are stored in an array\n for (let foo = 0; foo < prodMats.length; ++foo) {\n wh.materials[prodMats[foo]].processMarket();\n }\n\n //Process these twice because these boost production\n wh.materials[\"Hardware\"].processMarket();\n wh.materials[\"Robots\"].processMarket();\n wh.materials[\"AICores\"].processMarket();\n wh.materials[\"RealEstate\"].processMarket();\n }\n }\n }\n\n // Process change in demand and competition for this industry's products\n processProductMarket(marketCycles = 1): void {\n // Demand gradually decreases, and competition gradually increases\n for (const name in this.products) {\n if (this.products.hasOwnProperty(name)) {\n const product = this.products[name];\n if (product === undefined) continue;\n let change = getRandomInt(0, 3) * 0.0004;\n if (change === 0) continue;\n\n if (\n this.type === Industries.Pharmaceutical ||\n this.type === Industries.Software ||\n this.type === Industries.Robotics\n ) {\n change *= 3;\n }\n change *= marketCycles;\n product.dmd -= change;\n product.cmp += change;\n product.cmp = Math.min(product.cmp, 99.99);\n product.dmd = Math.max(product.dmd, 0.001);\n }\n }\n }\n\n //Process production, purchase, and import/export of materials\n processMaterials(marketCycles = 1, corporation: ICorporation): [number, number] {\n let revenue = 0,\n expenses = 0;\n this.calculateProductionFactors();\n\n //At the start of the export state, set the imports of everything to 0\n if (this.state === \"EXPORT\") {\n for (let i = 0; i < CorporationConstants.Cities.length; ++i) {\n const city = CorporationConstants.Cities[i];\n if (!(this.warehouses[city] instanceof Warehouse)) {\n continue;\n }\n const warehouse = this.warehouses[city];\n if (warehouse === 0) continue;\n for (const matName in warehouse.materials) {\n if (warehouse.materials.hasOwnProperty(matName)) {\n const mat = warehouse.materials[matName];\n mat.imp = 0;\n }\n }\n }\n }\n\n for (let i = 0; i < CorporationConstants.Cities.length; ++i) {\n const city = CorporationConstants.Cities[i];\n const office = this.offices[city];\n if (office === 0) continue;\n\n if (this.warehouses[city] instanceof Warehouse) {\n const warehouse = this.warehouses[city];\n if (warehouse === 0) continue;\n\n switch (this.state) {\n case \"PURCHASE\": {\n /* Process purchase of materials */\n for (const matName in warehouse.materials) {\n if (!warehouse.materials.hasOwnProperty(matName)) continue;\n const mat = warehouse.materials[matName];\n let buyAmt = 0;\n let maxAmt = 0;\n if (warehouse.smartSupplyEnabled && Object.keys(this.reqMats).includes(matName)) {\n continue;\n }\n buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;\n\n if (matName == \"RealEstate\") {\n maxAmt = buyAmt;\n } else {\n maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);\n }\n buyAmt = Math.min(buyAmt, maxAmt);\n if (buyAmt > 0) {\n mat.qty += buyAmt;\n expenses += buyAmt * mat.bCost;\n }\n this.updateWarehouseSizeUsed(warehouse);\n } //End process purchase of materials\n\n // smart supply\n const smartBuy: { [key: string]: number | undefined } = {};\n for (const matName in warehouse.materials) {\n if (!warehouse.materials.hasOwnProperty(matName)) continue;\n if (!warehouse.smartSupplyEnabled || !Object.keys(this.reqMats).includes(matName)) continue;\n const mat = warehouse.materials[matName];\n\n //Smart supply tracker is stored as per second rate\n const reqMat = this.reqMats[matName];\n if (reqMat === undefined) throw new Error(`reqMat \"${matName}\" is undefined`);\n mat.buy = reqMat * warehouse.smartSupplyStore;\n let buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;\n const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);\n buyAmt = Math.min(buyAmt, maxAmt);\n if (buyAmt > 0) smartBuy[matName] = buyAmt;\n }\n\n // Find which material were trying to create the least amount of product with.\n let worseAmt = 1e99;\n for (const matName in smartBuy) {\n const buyAmt = smartBuy[matName];\n if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);\n const reqMat = this.reqMats[matName];\n if (reqMat === undefined) throw new Error(`reqMat \"${matName}\" is undefined`);\n const amt = buyAmt / reqMat;\n if (amt < worseAmt) worseAmt = amt;\n }\n\n // Align all the materials to the smallest amount.\n for (const matName in smartBuy) {\n const reqMat = this.reqMats[matName];\n if (reqMat === undefined) throw new Error(`reqMat \"${matName}\" is undefined`);\n smartBuy[matName] = worseAmt * reqMat;\n }\n\n // Calculate the total size of all things were trying to buy\n let totalSize = 0;\n for (const matName in smartBuy) {\n const buyAmt = smartBuy[matName];\n if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);\n totalSize += buyAmt * MaterialSizes[matName];\n }\n\n // Shrink to the size of available space.\n const freeSpace = warehouse.size - warehouse.sizeUsed;\n if (totalSize > freeSpace) {\n for (const matName in smartBuy) {\n const buyAmt = smartBuy[matName];\n if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);\n smartBuy[matName] = Math.floor((buyAmt * freeSpace) / totalSize);\n }\n }\n\n // Use the materials already in the warehouse if the option is on.\n for (const matName in smartBuy) {\n if (!warehouse.smartSupplyUseLeftovers[matName]) continue;\n const mat = warehouse.materials[matName];\n const buyAmt = smartBuy[matName];\n if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);\n smartBuy[matName] = Math.max(0, buyAmt - mat.qty);\n }\n\n // buy them\n for (const matName in smartBuy) {\n const mat = warehouse.materials[matName];\n const buyAmt = smartBuy[matName];\n if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);\n mat.qty += buyAmt;\n expenses += buyAmt * mat.bCost;\n }\n break;\n }\n case \"PRODUCTION\":\n warehouse.smartSupplyStore = 0; //Reset smart supply amount\n\n /* Process production of materials */\n if (this.prodMats.length > 0) {\n const mat = warehouse.materials[this.prodMats[0]];\n //Calculate the maximum production of this material based\n //on the office's productivity\n const maxProd =\n this.getOfficeProductivity(office) *\n this.prodMult * // Multiplier from materials\n corporation.getProductionMultiplier() *\n this.getProductionMultiplier(); // Multiplier from Research\n let prod;\n\n if (mat.prdman[0]) {\n //Production is manually limited\n prod = Math.min(maxProd, mat.prdman[1]);\n } else {\n prod = maxProd;\n }\n prod *= CorporationConstants.SecsPerMarketCycle * marketCycles; //Convert production from per second to per market cycle\n\n // Calculate net change in warehouse storage making the produced materials will cost\n let totalMatSize = 0;\n for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {\n totalMatSize += MaterialSizes[this.prodMats[tmp]];\n }\n for (const reqMatName in this.reqMats) {\n const normQty = this.reqMats[reqMatName];\n if (normQty === undefined) continue;\n totalMatSize -= MaterialSizes[reqMatName] * normQty;\n }\n // If not enough space in warehouse, limit the amount of produced materials\n if (totalMatSize > 0) {\n const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);\n prod = Math.min(maxAmt, prod);\n }\n\n if (prod < 0) {\n prod = 0;\n }\n\n // Keep track of production for smart supply (/s)\n warehouse.smartSupplyStore += prod / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n\n // Make sure we have enough resource to make our materials\n let producableFrac = 1;\n for (const reqMatName in this.reqMats) {\n if (this.reqMats.hasOwnProperty(reqMatName)) {\n const reqMat = this.reqMats[reqMatName];\n if (reqMat === undefined) continue;\n const req = reqMat * prod;\n if (warehouse.materials[reqMatName].qty < req) {\n producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req);\n }\n }\n }\n if (producableFrac <= 0) {\n producableFrac = 0;\n prod = 0;\n }\n\n // Make our materials if they are producable\n if (producableFrac > 0 && prod > 0) {\n for (const reqMatName in this.reqMats) {\n const reqMat = this.reqMats[reqMatName];\n if (reqMat === undefined) continue;\n const reqMatQtyNeeded = reqMat * prod * producableFrac;\n warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;\n warehouse.materials[reqMatName].prd = 0;\n warehouse.materials[reqMatName].prd -=\n reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n }\n for (let j = 0; j < this.prodMats.length; ++j) {\n warehouse.materials[this.prodMats[j]].qty += prod * producableFrac;\n warehouse.materials[this.prodMats[j]].qlt =\n office.employeeProd[EmployeePositions.Engineer] / 90 +\n Math.pow(this.sciResearch.qty, this.sciFac) +\n Math.pow(warehouse.materials[\"AICores\"].qty, this.aiFac) / 10e3;\n }\n } else {\n for (const reqMatName in this.reqMats) {\n if (this.reqMats.hasOwnProperty(reqMatName)) {\n warehouse.materials[reqMatName].prd = 0;\n }\n }\n }\n\n //Per second\n const fooProd = (prod * producableFrac) / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {\n warehouse.materials[this.prodMats[fooI]].prd = fooProd;\n }\n } else {\n //If this doesn't produce any materials, then it only creates\n //Products. Creating products will consume materials. The\n //Production of all consumed materials must be set to 0\n for (const reqMatName in this.reqMats) {\n warehouse.materials[reqMatName].prd = 0;\n }\n }\n break;\n\n case \"SALE\":\n /* Process sale of materials */\n for (const matName in warehouse.materials) {\n if (warehouse.materials.hasOwnProperty(matName)) {\n const mat = warehouse.materials[matName];\n if (mat.sCost < 0 || mat.sllman[0] === false) {\n mat.sll = 0;\n continue;\n }\n\n // Sale multipliers\n const businessFactor = this.getBusinessFactor(office); //Business employee productivity\n const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity\n const marketFactor = this.getMarketFactor(mat); //Competition + demand\n\n // Determine the cost that the material will be sold at\n const markupLimit = mat.getMarkupLimit();\n let sCost;\n if (mat.marketTa2) {\n const prod = mat.prd;\n\n // Reverse engineer the 'maxSell' formula\n // 1. Set 'maxSell' = prod\n // 2. Substitute formula for 'markup'\n // 3. Solve for 'sCost'\n const numerator = markupLimit;\n const sqrtNumerator = prod;\n const sqrtDenominator =\n (mat.qlt + 0.001) *\n marketFactor *\n businessFactor *\n corporation.getSalesMultiplier() *\n advertisingFactor *\n this.getSalesMultiplier();\n const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);\n let optimalPrice;\n if (sqrtDenominator === 0 || denominator === 0) {\n if (sqrtNumerator === 0) {\n optimalPrice = 0; // No production\n } else {\n optimalPrice = mat.bCost + markupLimit;\n console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);\n }\n } else {\n optimalPrice = numerator / denominator + mat.bCost;\n }\n\n // We'll store this \"Optimal Price\" in a property so that we don't have\n // to re-calculate it for the UI\n mat.marketTa2Price = optimalPrice;\n\n sCost = optimalPrice;\n } else if (mat.marketTa1) {\n sCost = mat.bCost + markupLimit;\n } else if (isString(mat.sCost)) {\n sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + \"\");\n sCost = eval(sCost);\n } else {\n sCost = mat.sCost;\n }\n\n // Calculate how much of the material sells (per second)\n let markup = 1;\n if (sCost > mat.bCost) {\n //Penalty if difference between sCost and bCost is greater than markup limit\n if (sCost - mat.bCost > markupLimit) {\n markup = Math.pow(markupLimit / (sCost - mat.bCost), 2);\n }\n } else if (sCost < mat.bCost) {\n if (sCost <= 0) {\n markup = 1e12; //Sell everything, essentially discard\n } else {\n //Lower prices than market increases sales\n markup = mat.bCost / sCost;\n }\n }\n\n const maxSell =\n (mat.qlt + 0.001) *\n marketFactor *\n markup *\n businessFactor *\n corporation.getSalesMultiplier() *\n advertisingFactor *\n this.getSalesMultiplier();\n let sellAmt;\n if (isString(mat.sllman[1])) {\n //Dynamically evaluated\n let tmp = (mat.sllman[1] as string).replace(/MAX/g, maxSell + \"\");\n tmp = tmp.replace(/PROD/g, mat.prd + \"\");\n try {\n sellAmt = eval(tmp);\n } catch (e) {\n dialogBoxCreate(\n \"Error evaluating your sell amount for material \" +\n mat.name +\n \" in \" +\n this.name +\n \"'s \" +\n city +\n \" office. The sell amount \" +\n \"is being set to zero\",\n );\n sellAmt = 0;\n }\n sellAmt = Math.min(maxSell, sellAmt);\n } else if (mat.sllman[1] === -1) {\n //Backwards compatibility, -1 = MAX\n sellAmt = maxSell;\n } else {\n //Player's input value is just a number\n sellAmt = Math.min(maxSell, mat.sllman[1] as number);\n }\n\n sellAmt = sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles;\n sellAmt = Math.min(mat.qty, sellAmt);\n if (sellAmt < 0) {\n console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`);\n mat.sll = 0;\n continue;\n }\n if (sellAmt && sCost >= 0) {\n mat.qty -= sellAmt;\n revenue += sellAmt * sCost;\n mat.sll = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n } else {\n mat.sll = 0;\n }\n }\n } //End processing of sale of materials\n break;\n\n case \"EXPORT\":\n for (const matName in warehouse.materials) {\n if (warehouse.materials.hasOwnProperty(matName)) {\n const mat = warehouse.materials[matName];\n mat.totalExp = 0; //Reset export\n for (let expI = 0; expI < mat.exp.length; ++expI) {\n const exp = mat.exp[expI];\n const amtStr = exp.amt.replace(\n /MAX/g,\n mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles) + \"\",\n );\n let amt = 0;\n try {\n amt = eval(amtStr);\n } catch (e) {\n dialogBoxCreate(\n \"Calculating export for \" +\n mat.name +\n \" in \" +\n this.name +\n \"'s \" +\n city +\n \" division failed with \" +\n \"error: \" +\n e,\n );\n continue;\n }\n if (isNaN(amt)) {\n dialogBoxCreate(\n \"Error calculating export amount for \" +\n mat.name +\n \" in \" +\n this.name +\n \"'s \" +\n city +\n \" division.\",\n );\n continue;\n }\n amt = amt * CorporationConstants.SecsPerMarketCycle * marketCycles;\n\n if (mat.qty < amt) {\n amt = mat.qty;\n }\n if (amt === 0) {\n break; //None left\n }\n for (let foo = 0; foo < corporation.divisions.length; ++foo) {\n if (corporation.divisions[foo].name === exp.ind) {\n const expIndustry = corporation.divisions[foo];\n const expWarehouse = expIndustry.warehouses[exp.city];\n if (!(expWarehouse instanceof Warehouse)) {\n console.error(`Invalid export! ${expIndustry.name} ${exp.city}`);\n break;\n }\n\n // Make sure theres enough space in warehouse\n if (expWarehouse.sizeUsed >= expWarehouse.size) {\n // Warehouse at capacity. Exporting doesnt\n // affect revenue so just return 0's\n return [0, 0];\n } else {\n const maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]);\n amt = Math.min(maxAmt, amt);\n }\n expWarehouse.materials[matName].imp +=\n amt / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n expWarehouse.materials[matName].qty += amt;\n expWarehouse.materials[matName].qlt = mat.qlt;\n mat.qty -= amt;\n mat.totalExp += amt;\n expIndustry.updateWarehouseSizeUsed(expWarehouse);\n break;\n }\n }\n }\n //totalExp should be per second\n mat.totalExp /= CorporationConstants.SecsPerMarketCycle * marketCycles;\n }\n }\n\n break;\n\n case \"START\":\n break;\n default:\n console.error(`Invalid state: ${this.state}`);\n break;\n } //End switch(this.state)\n this.updateWarehouseSizeUsed(warehouse);\n } // End warehouse\n\n //Produce Scientific Research based on R&D employees\n //Scientific Research can be produced without a warehouse\n if (office instanceof OfficeSpace) {\n this.sciResearch.qty +=\n 0.004 *\n Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) *\n corporation.getScientificResearchMultiplier() *\n this.getScientificResearchMultiplier();\n }\n }\n return [revenue, expenses];\n }\n\n //Process production & sale of this industry's FINISHED products (including all of their stats)\n processProducts(marketCycles = 1, corporation: ICorporation): [number, number] {\n let revenue = 0;\n const expenses = 0;\n\n //Create products\n if (this.state === \"PRODUCTION\") {\n for (const prodName in this.products) {\n const prod = this.products[prodName];\n if (prod === undefined) continue;\n if (!prod.fin) {\n const city = prod.createCity;\n const office = this.offices[city];\n if (office === 0) continue;\n\n // Designing/Creating a Product is based mostly off Engineers\n const engrProd = office.employeeProd[EmployeePositions.Engineer];\n const mgmtProd = office.employeeProd[EmployeePositions.Management];\n const opProd = office.employeeProd[EmployeePositions.Operations];\n const total = engrProd + mgmtProd + opProd;\n if (total <= 0) {\n break;\n }\n\n // Management is a multiplier for the production from Engineers\n const mgmtFactor = 1 + mgmtProd / (1.2 * total);\n\n const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;\n\n prod.createProduct(marketCycles, progress);\n if (prod.prog >= 100) {\n prod.finishProduct(office.employeeProd, this);\n }\n break;\n }\n }\n }\n\n //Produce Products\n for (const prodName in this.products) {\n if (this.products.hasOwnProperty(prodName)) {\n const prod = this.products[prodName];\n if (prod instanceof Product && prod.fin) {\n revenue += this.processProduct(marketCycles, prod, corporation);\n }\n }\n }\n return [revenue, expenses];\n }\n\n //Processes FINISHED products\n processProduct(marketCycles = 1, product: Product, corporation: ICorporation): number {\n let totalProfit = 0;\n for (let i = 0; i < CorporationConstants.Cities.length; ++i) {\n const city = CorporationConstants.Cities[i];\n const office = this.offices[city];\n if (office === 0) continue;\n const warehouse = this.warehouses[city];\n if (warehouse instanceof Warehouse) {\n switch (this.state) {\n case \"PRODUCTION\": {\n //Calculate the maximum production of this material based\n //on the office's productivity\n const maxProd =\n this.getOfficeProductivity(office, { forProduct: true }) *\n corporation.getProductionMultiplier() *\n this.prodMult * // Multiplier from materials\n this.getProductionMultiplier() * // Multiplier from research\n this.getProductProductionMultiplier(); // Multiplier from research\n let prod;\n\n //Account for whether production is manually limited\n if (product.prdman[city][0]) {\n prod = Math.min(maxProd, product.prdman[city][1]);\n } else {\n prod = maxProd;\n }\n prod *= CorporationConstants.SecsPerMarketCycle * marketCycles;\n\n //Calculate net change in warehouse storage making the Products will cost\n let netStorageSize = product.siz;\n for (const reqMatName in product.reqMats) {\n if (product.reqMats.hasOwnProperty(reqMatName)) {\n const normQty = product.reqMats[reqMatName];\n netStorageSize -= MaterialSizes[reqMatName] * normQty;\n }\n }\n\n //If there's not enough space in warehouse, limit the amount of Product\n if (netStorageSize > 0) {\n const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize);\n prod = Math.min(maxAmt, prod);\n }\n\n warehouse.smartSupplyStore += prod / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n\n //Make sure we have enough resources to make our Products\n let producableFrac = 1;\n for (const reqMatName in product.reqMats) {\n if (product.reqMats.hasOwnProperty(reqMatName)) {\n const req = product.reqMats[reqMatName] * prod;\n if (warehouse.materials[reqMatName].qty < req) {\n producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req);\n }\n }\n }\n\n //Make our Products if they are producable\n if (producableFrac > 0 && prod > 0) {\n for (const reqMatName in product.reqMats) {\n if (product.reqMats.hasOwnProperty(reqMatName)) {\n const reqMatQtyNeeded = product.reqMats[reqMatName] * prod * producableFrac;\n warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;\n warehouse.materials[reqMatName].prd -=\n reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n }\n }\n //Quantity\n product.data[city][0] += prod * producableFrac;\n }\n\n //Keep track of production Per second\n product.data[city][1] = (prod * producableFrac) / (CorporationConstants.SecsPerMarketCycle * marketCycles);\n break;\n }\n case \"SALE\": {\n //Process sale of Products\n product.pCost = 0; //Estimated production cost\n for (const reqMatName in product.reqMats) {\n if (product.reqMats.hasOwnProperty(reqMatName)) {\n product.pCost += product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost;\n }\n }\n\n // Since its a product, its production cost is increased for labor\n product.pCost *= CorporationConstants.ProductProductionCostRatio;\n\n // Sale multipliers\n const businessFactor = this.getBusinessFactor(office); //Business employee productivity\n const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity\n const marketFactor = this.getMarketFactor(product); //Competition + demand\n\n // Calculate Sale Cost (sCost), which could be dynamically evaluated\n const markupLimit = product.rat / product.mku;\n let sCost;\n if (product.marketTa2) {\n const prod = product.data[city][1];\n\n // Reverse engineer the 'maxSell' formula\n // 1. Set 'maxSell' = prod\n // 2. Substitute formula for 'markup'\n // 3. Solve for 'sCost'roduct.pCost = sCost\n const numerator = markupLimit;\n const sqrtNumerator = prod;\n const sqrtDenominator =\n 0.5 *\n Math.pow(product.rat, 0.65) *\n marketFactor *\n corporation.getSalesMultiplier() *\n businessFactor *\n advertisingFactor *\n this.getSalesMultiplier();\n const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);\n let optimalPrice;\n if (sqrtDenominator === 0 || denominator === 0) {\n if (sqrtNumerator === 0) {\n optimalPrice = 0; // No production\n } else {\n optimalPrice = product.pCost + markupLimit;\n console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);\n }\n } else {\n optimalPrice = numerator / denominator + product.pCost;\n }\n\n // Store this \"optimal Price\" in a property so we don't have to re-calculate for UI\n product.marketTa2Price[city] = optimalPrice;\n sCost = optimalPrice;\n } else if (product.marketTa1) {\n sCost = product.pCost + markupLimit;\n } else if (isString(product.sCost)) {\n const sCostString = product.sCost as string;\n if (product.mku === 0) {\n console.error(`mku is zero, reverting to 1 to avoid Infinity`);\n product.mku = 1;\n }\n sCost = sCostString.replace(/MP/g, product.pCost + product.rat / product.mku + \"\");\n sCost = eval(sCost);\n } else {\n sCost = product.sCost;\n }\n\n let markup = 1;\n if (sCost > product.pCost) {\n if (sCost - product.pCost > markupLimit) {\n markup = markupLimit / (sCost - product.pCost);\n }\n }\n\n const maxSell =\n 0.5 *\n Math.pow(product.rat, 0.65) *\n marketFactor *\n corporation.getSalesMultiplier() *\n Math.pow(markup, 2) *\n businessFactor *\n advertisingFactor *\n this.getSalesMultiplier();\n let sellAmt;\n if (product.sllman[city][0] && isString(product.sllman[city][1])) {\n //Sell amount is dynamically evaluated\n let tmp = product.sllman[city][1].replace(/MAX/g, maxSell);\n tmp = tmp.replace(/PROD/g, product.data[city][1]);\n try {\n tmp = eval(tmp);\n } catch (e) {\n dialogBoxCreate(\n \"Error evaluating your sell price expression for \" +\n product.name +\n \" in \" +\n this.name +\n \"'s \" +\n city +\n \" office. Sell price is being set to MAX\",\n );\n tmp = maxSell;\n }\n sellAmt = Math.min(maxSell, tmp);\n } else if (product.sllman[city][0] && product.sllman[city][1] > 0) {\n //Sell amount is manually limited\n sellAmt = Math.min(maxSell, product.sllman[city][1]);\n } else if (product.sllman[city][0] === false) {\n sellAmt = 0;\n } else {\n sellAmt = maxSell;\n }\n if (sellAmt < 0) {\n sellAmt = 0;\n }\n sellAmt = sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles;\n sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty\n if (sellAmt && sCost) {\n product.data[city][0] -= sellAmt; //data[0] is qty\n totalProfit += sellAmt * sCost;\n product.data[city][2] = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles); //data[2] is sell property\n } else {\n product.data[city][2] = 0; //data[2] is sell property\n }\n break;\n }\n case \"START\":\n case \"PURCHASE\":\n case \"EXPORT\":\n break;\n default:\n console.error(`Invalid State: ${this.state}`);\n break;\n } //End switch(this.state)\n }\n }\n return totalProfit;\n }\n\n discontinueProduct(product: Product): void {\n for (const productName in this.products) {\n if (this.products.hasOwnProperty(productName)) {\n if (product === this.products[productName]) {\n delete this.products[productName];\n }\n }\n }\n }\n\n upgrade(upgrade: IndustryUpgrade, refs: { corporation: ICorporation; office: OfficeSpace }): void {\n const corporation = refs.corporation;\n const office = refs.office;\n const upgN = upgrade[0];\n while (this.upgrades.length <= upgN) {\n this.upgrades.push(0);\n }\n ++this.upgrades[upgN];\n\n switch (upgN) {\n case 0: {\n //Coffee, 5% energy per employee\n for (let i = 0; i < office.employees.length; ++i) {\n office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne);\n }\n break;\n }\n case 1: {\n //AdVert.Inc,\n const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();\n this.awareness += 3 * advMult;\n this.popularity += 1 * advMult;\n this.awareness *= 1.01 * advMult;\n this.popularity *= (1 + getRandomInt(1, 3) / 100) * advMult;\n break;\n }\n default: {\n console.error(`Un-implemented function index: ${upgN}`);\n break;\n }\n }\n }\n\n // Returns how much of a material can be produced based of office productivity (employee stats)\n getOfficeProductivity(office: OfficeSpace, params: { forProduct?: boolean } = {}): number {\n const opProd = office.employeeProd[EmployeePositions.Operations];\n const engrProd = office.employeeProd[EmployeePositions.Engineer];\n const mgmtProd = office.employeeProd[EmployeePositions.Management];\n const total = opProd + engrProd + mgmtProd;\n\n if (total <= 0) return 0;\n\n // Management is a multiplier for the production from Operations and Engineers\n const mgmtFactor = 1 + mgmtProd / (1.2 * total);\n\n // For production, Operations is slightly more important than engineering\n // Both Engineering and Operations have diminishing returns\n const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor;\n\n // Generic multiplier for the production. Used for game-balancing purposes\n const balancingMult = 0.05;\n\n if (params && params.forProduct) {\n // Products are harder to create and therefore have less production\n return 0.5 * balancingMult * prod;\n } else {\n return balancingMult * prod;\n }\n }\n\n // Returns a multiplier based on the office' 'Business' employees that affects sales\n getBusinessFactor(office: OfficeSpace): number {\n const businessProd = 1 + office.employeeProd[EmployeePositions.Business];\n\n return calculateEffectWithFactors(businessProd, 0.26, 10e3);\n }\n\n //Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This\n //multiplier affects sales. The result is:\n // [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult]\n getAdvertisingFactors(): [number, number, number, number] {\n const awarenessFac = Math.pow(this.awareness + 1, this.advFac);\n const popularityFac = Math.pow(this.popularity + 1, this.advFac);\n const ratioFac = this.awareness === 0 ? 0.01 : Math.max((this.popularity + 0.001) / this.awareness, 0.01);\n const totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85);\n return [totalFac, awarenessFac, popularityFac, ratioFac];\n }\n\n //Returns a multiplier based on a materials demand and competition that affects sales\n getMarketFactor(mat: { dmd: number; cmp: number }): number {\n return Math.max(0.1, (mat.dmd * (100 - mat.cmp)) / 100);\n }\n\n // Returns a boolean indicating whether this Industry has the specified Research\n hasResearch(name: string): boolean {\n return this.researched[name] === true;\n }\n\n updateResearchTree(): void {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry \"${this.type}\"`);\n\n // Since ResearchTree data isnt saved, we'll update the Research Tree data\n // based on the stored 'researched' property in the Industry object\n if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) {\n for (const research in this.researched) {\n researchTree.research(research);\n }\n }\n }\n\n // Get multipliers from Research\n getAdvertisingMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getAdvertisingMultiplier();\n }\n\n getEmployeeChaMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getEmployeeChaMultiplier();\n }\n\n getEmployeeCreMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getEmployeeCreMultiplier();\n }\n\n getEmployeeEffMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getEmployeeEffMultiplier();\n }\n\n getEmployeeIntMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getEmployeeIntMultiplier();\n }\n\n getProductionMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getProductionMultiplier();\n }\n\n getProductProductionMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getProductProductionMultiplier();\n }\n\n getSalesMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getSalesMultiplier();\n }\n\n getScientificResearchMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getScientificResearchMultiplier();\n }\n\n getStorageMultiplier(): number {\n const researchTree = IndustryResearchTrees[this.type];\n if (researchTree === undefined) throw new Error(`Invalid industry: \"${this.type}\"`);\n this.updateResearchTree();\n return researchTree.getStorageMultiplier();\n }\n\n copy(): Industry {\n // products: { [key: string]: Product | undefined } = {};\n\n // //Maps locations to warehouses. 0 if no warehouse at that location\n // warehouses: { [key: string]: Warehouse | 0 };\n\n // //Maps locations to offices. 0 if no office at that location\n // offices: { [key: string]: OfficeSpace | 0 } = {\n // [CityName.Aevum]: 0,\n // [CityName.Chongqing]: 0,\n // [CityName.Sector12]: new OfficeSpace({\n // loc: CityName.Sector12,\n // size: CorporationConstants.OfficeInitialSize,\n // }),\n // [CityName.NewTokyo]: 0,\n // [CityName.Ishima]: 0,\n // [CityName.Volhaven]: 0,\n // };\n\n const division = new Industry();\n division.sciResearch = this.sciResearch.copy();\n division.researched = {};\n for (const x of Object.keys(this.researched)) {\n division.researched[x] = this.researched[x];\n }\n division.reqMats = {};\n for (const x of Object.keys(this.reqMats)) {\n division.reqMats[x] = this.reqMats[x];\n }\n division.name = this.name;\n division.type = this.type;\n division.makesProducts = this.makesProducts;\n division.awareness = this.awareness;\n division.popularity = this.popularity;\n division.startingCost = this.startingCost;\n division.reFac = this.reFac;\n division.sciFac = this.sciFac;\n division.hwFac = this.hwFac;\n division.robFac = this.robFac;\n division.aiFac = this.aiFac;\n division.advFac = this.advFac;\n division.prodMult = this.prodMult;\n division.state = this.state;\n division.newInd = this.newInd;\n division.lastCycleRevenue = this.lastCycleRevenue.plus(0);\n division.lastCycleExpenses = this.lastCycleExpenses.plus(0);\n division.thisCycleRevenue = this.thisCycleRevenue.plus(0);\n division.thisCycleExpenses = this.thisCycleExpenses.plus(0);\n division.upgrades = this.upgrades.slice();\n division.prodMats = this.prodMats.slice();\n return division;\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"Industry\", this);\n }\n\n /**\n * Initiatizes a Industry object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Industry {\n return Generic_fromJSON(Industry, value.data);\n }\n}\n\nReviver.constructors.Industry = Industry;\n","/**\n * This is a component that implements a mathematical formula used commonly throughout the\n * game. This formula is (typically) used to calculate the effect that various statistics\n * have on a game mechanic. It looks something like:\n *\n * (stat ^ exponential factor) + (stat / linear factor)\n *\n * where the exponential factor is a number between 0 and 1 and the linear factor\n * is typically a relatively larger number.\n *\n * This formula ensures that the effects of the statistic that is being processed\n * has diminishing returns, but never loses its effectiveness as you continue\n * to raise it.\n */\nexport function calculateEffectWithFactors(n: number, expFac: number, linearFac: number): number {\n if (expFac <= 0 || expFac >= 1) {\n console.warn(`Exponential factor is ${expFac}. This is not an intended value for it`);\n }\n if (linearFac < 1) {\n console.warn(`Linear factor is ${linearFac}. This is not an intended value for it`);\n }\n\n return Math.pow(n, expFac) + n / linearFac;\n}\n","import { CorporationConstants } from \"./data/Constants\";\nimport { getRandomInt } from \"../utils/helpers/getRandomInt\";\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../utils/JSONReviver\";\nimport { EmployeePositions } from \"./EmployeePositions\";\nimport { ICorporation } from \"./ICorporation\";\nimport { OfficeSpace } from \"./OfficeSpace\";\nimport { IIndustry } from \"./IIndustry\";\n\ninterface IParams {\n name?: string;\n morale?: number;\n happiness?: number;\n energy?: number;\n intelligence?: number;\n charisma?: number;\n experience?: number;\n creativity?: number;\n efficiency?: number;\n salary?: number;\n loc?: string;\n}\n\nexport class Employee {\n name: string;\n mor: number;\n hap: number;\n ene: number;\n int: number;\n cha: number;\n exp: number;\n cre: number;\n eff: number;\n sal: number;\n pro = 0;\n cyclesUntilRaise = CorporationConstants.CyclesPerEmployeeRaise;\n loc: string;\n pos: string;\n\n constructor(params: IParams = {}) {\n this.name = params.name ? params.name : \"Bobby\";\n\n //Morale, happiness, and energy are 0-100\n this.mor = params.morale ? params.morale : getRandomInt(50, 100);\n this.hap = params.happiness ? params.happiness : getRandomInt(50, 100);\n this.ene = params.energy ? params.energy : getRandomInt(50, 100);\n\n this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50);\n this.cha = params.charisma ? params.charisma : getRandomInt(10, 50);\n this.exp = params.experience ? params.experience : getRandomInt(10, 50);\n this.cre = params.creativity ? params.creativity : getRandomInt(10, 50);\n this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50);\n this.sal = params.salary ? params.salary : getRandomInt(0.1, 5);\n\n this.loc = params.loc ? params.loc : \"\";\n this.pos = EmployeePositions.Unassigned;\n }\n\n //Returns the amount the employee needs to be paid\n process(marketCycles = 1, office: OfficeSpace): number {\n const gain = 0.003 * marketCycles,\n det = gain * Math.random();\n this.exp += gain;\n\n // Employee salaries slowly go up over time\n this.cyclesUntilRaise -= marketCycles;\n if (this.cyclesUntilRaise <= 0) {\n this.sal += CorporationConstants.EmployeeRaiseAmount;\n this.cyclesUntilRaise += CorporationConstants.CyclesPerEmployeeRaise;\n }\n\n //Training\n const trainingEff = gain * Math.random();\n if (this.pos === EmployeePositions.Training) {\n //To increase creativity and intelligence special upgrades are needed\n this.cha += trainingEff;\n this.exp += trainingEff;\n this.eff += trainingEff;\n }\n\n this.ene -= det;\n this.hap -= det;\n\n if (this.ene < office.minEne) {\n this.ene = office.minEne;\n }\n if (this.hap < office.minHap) {\n this.hap = office.minHap;\n }\n const salary = this.sal * marketCycles * CorporationConstants.SecsPerMarketCycle;\n return salary;\n }\n\n calculateProductivity(corporation: ICorporation, industry: IIndustry): number {\n const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),\n effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),\n effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),\n effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();\n const prodBase = this.mor * this.hap * this.ene * 1e-6;\n let prodMult = 0;\n switch (this.pos) {\n //Calculate productivity based on position. This is multipled by prodBase\n //to get final value\n case EmployeePositions.Operations:\n prodMult = 0.6 * effInt + 0.1 * effCha + this.exp + 0.5 * effCre + effEff;\n break;\n case EmployeePositions.Engineer:\n prodMult = effInt + 0.1 * effCha + 1.5 * this.exp + effEff;\n break;\n case EmployeePositions.Business:\n prodMult = 0.4 * effInt + effCha + 0.5 * this.exp;\n break;\n case EmployeePositions.Management:\n prodMult = 2 * effCha + this.exp + 0.2 * effCre + 0.7 * effEff;\n break;\n case EmployeePositions.RandD:\n prodMult = 1.5 * effInt + 0.8 * this.exp + effCre + 0.5 * effEff;\n break;\n case EmployeePositions.Unassigned:\n case EmployeePositions.Training:\n prodMult = 0;\n break;\n default:\n console.error(`Invalid employee position: ${this.pos}`);\n break;\n }\n return prodBase * prodMult;\n }\n\n //Process benefits from having an office party thrown\n throwParty(money: number): number {\n const mult = 1 + money / 10e6;\n this.mor *= mult;\n this.mor = Math.min(100, this.mor);\n this.hap *= mult;\n this.hap = Math.min(100, this.hap);\n return mult;\n }\n\n copy(): Employee {\n const employee = new Employee();\n employee.name = this.name;\n employee.mor = this.mor;\n employee.hap = this.hap;\n employee.ene = this.ene;\n employee.int = this.int;\n employee.cha = this.cha;\n employee.exp = this.exp;\n employee.cre = this.cre;\n employee.eff = this.eff;\n employee.sal = this.sal;\n employee.pro = this.pro;\n employee.cyclesUntilRaise = this.cyclesUntilRaise;\n employee.loc = this.loc;\n employee.pos = this.pos;\n return employee;\n }\n\n toJSON(): any {\n return Generic_toJSON(\"Employee\", this);\n }\n\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Employee {\n return Generic_fromJSON(Employee, value.data);\n }\n}\n\nReviver.constructors.Employee = Employee;\n","import { Industries } from \"./IndustryData\";\nimport { IMap } from \"../types\";\n\nexport interface IProductRatingWeight {\n Aesthetics?: number;\n Durability?: number;\n Features?: number;\n Quality?: number;\n Performance?: number;\n Reliability?: number;\n}\n\nexport const ProductRatingWeights: IMap = {\n [Industries.Food]: {\n Quality: 0.7,\n Durability: 0.1,\n Aesthetics: 0.2,\n },\n [Industries.Tobacco]: {\n Quality: 0.4,\n Durability: 0.2,\n Reliability: 0.2,\n Aesthetics: 0.2,\n },\n [Industries.Pharmaceutical]: {\n Quality: 0.2,\n Performance: 0.2,\n Durability: 0.1,\n Reliability: 0.3,\n Features: 0.2,\n },\n [Industries.Computer]: {\n Quality: 0.15,\n Performance: 0.25,\n Durability: 0.25,\n Reliability: 0.2,\n Aesthetics: 0.05,\n Features: 0.1,\n },\n Computer: {\n //Repeat\n Quality: 0.15,\n Performance: 0.25,\n Durability: 0.25,\n Reliability: 0.2,\n Aesthetics: 0.05,\n Features: 0.1,\n },\n [Industries.Robotics]: {\n Quality: 0.1,\n Performance: 0.2,\n Durability: 0.2,\n Reliability: 0.2,\n Aesthetics: 0.1,\n Features: 0.2,\n },\n [Industries.Software]: {\n Quality: 0.2,\n Performance: 0.2,\n Reliability: 0.2,\n Durability: 0.2,\n Features: 0.2,\n },\n [Industries.Healthcare]: {\n Quality: 0.4,\n Performance: 0.1,\n Durability: 0.1,\n Reliability: 0.3,\n Features: 0.1,\n },\n [Industries.RealEstate]: {\n Quality: 0.2,\n Durability: 0.25,\n Reliability: 0.1,\n Aesthetics: 0.35,\n Features: 0.1,\n },\n};\n","import { ITaskParams } from \"../ITaskParams\";\n/* tslint:disable:max-line-length */\n\n/**\n * Defines the parameters that can be used to initialize and describe a GangMemberTask\n * (defined in Gang.js)\n */\nexport interface IGangMemberTaskMetadata {\n /**\n * Description of the task\n */\n desc: string;\n\n /**\n * Whether or not this task is meant for Combat-type gangs\n */\n isCombat: boolean;\n\n /**\n * Whether or not this task is for Hacking-type gangs\n */\n isHacking: boolean;\n\n /**\n * Name of the task\n */\n name: string;\n\n /**\n * An object containing weighting parameters for the task. These parameters are used for\n * various calculations (respect gain, wanted gain, etc.)\n */\n params: ITaskParams;\n}\n\n/**\n * Array of metadata for all Gang Member tasks. Used to construct the global GangMemberTask\n * objects in Gang.js\n */\nexport const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [\n {\n desc: \"This gang member is currently idle\",\n isCombat: true,\n isHacking: true,\n name: \"Unassigned\",\n params: { hackWeight: 100 }, // This is just to get by the weight check in the GangMemberTask constructor\n },\n {\n desc: \"Assign this gang member to create and distribute ransomware

Earns money - Slightly increases respect - Slightly increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Ransomware\",\n params: {\n baseRespect: 0.00005,\n baseWanted: 0.0001,\n baseMoney: 1,\n hackWeight: 100,\n difficulty: 1,\n },\n },\n {\n desc: \"Assign this gang member to attempt phishing scams and attacks

Earns money - Slightly increases respect - Slightly increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Phishing\",\n params: {\n baseRespect: 0.00008,\n baseWanted: 0.003,\n baseMoney: 2.5,\n hackWeight: 85,\n chaWeight: 15,\n difficulty: 3.5,\n },\n },\n {\n desc: \"Assign this gang member to attempt identity theft

Earns money - Increases respect - Increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Identity Theft\",\n params: {\n baseRespect: 0.0001,\n baseWanted: 0.075,\n baseMoney: 6,\n hackWeight: 80,\n chaWeight: 20,\n difficulty: 5,\n },\n },\n {\n desc: \"Assign this gang member to carry out DDoS attacks

Increases respect - Increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"DDoS Attacks\",\n params: {\n baseRespect: 0.0004,\n baseWanted: 0.2,\n hackWeight: 100,\n difficulty: 8,\n },\n },\n {\n desc: \"Assign this gang member to create and distribute malicious viruses

Increases respect - Increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Plant Virus\",\n params: {\n baseRespect: 0.0006,\n baseWanted: 0.4,\n hackWeight: 100,\n difficulty: 12,\n },\n },\n {\n desc: \"Assign this gang member to commit financial fraud and digital counterfeiting

Earns money - Slightly increases respect - Slightly increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Fraud & Counterfeiting\",\n params: {\n baseRespect: 0.0004,\n baseWanted: 0.3,\n baseMoney: 15,\n hackWeight: 80,\n chaWeight: 20,\n difficulty: 20,\n },\n },\n {\n desc: \"Assign this gang member to launder money

Earns money - Increases respect - Increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Money Laundering\",\n params: {\n baseRespect: 0.001,\n baseWanted: 1.25,\n baseMoney: 120,\n hackWeight: 75,\n chaWeight: 25,\n difficulty: 25,\n },\n },\n {\n desc: \"Assign this gang member to commit acts of cyberterrorism

Greatly increases respect - Greatly increases wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Cyberterrorism\",\n params: {\n baseRespect: 0.01,\n baseWanted: 6,\n hackWeight: 80,\n chaWeight: 20,\n difficulty: 36,\n },\n },\n {\n desc: \"Assign this gang member to be an ethical hacker for corporations

Earns money - Lowers wanted level\",\n isCombat: false,\n isHacking: true,\n name: \"Ethical Hacking\",\n params: {\n baseWanted: -0.001,\n baseMoney: 1,\n hackWeight: 90,\n chaWeight: 10,\n difficulty: 1,\n },\n },\n {\n desc: \"Assign this gang member to mug random people on the streets

Earns money - Slightly increases respect - Very slightly increases wanted level\",\n isCombat: true,\n isHacking: false,\n name: \"Mug People\",\n params: {\n baseRespect: 0.00005,\n baseWanted: 0.00005,\n baseMoney: 1.2,\n strWeight: 25,\n defWeight: 25,\n dexWeight: 25,\n agiWeight: 10,\n chaWeight: 15,\n difficulty: 1,\n },\n },\n {\n desc: \"Assign this gang member to sell drugs

Earns money - Slightly increases respect - Slightly increases wanted level - Scales slightly with territory\",\n isCombat: true,\n isHacking: false,\n name: \"Deal Drugs\",\n params: {\n baseRespect: 0.00006,\n baseWanted: 0.002,\n baseMoney: 5,\n agiWeight: 20,\n dexWeight: 20,\n chaWeight: 60,\n difficulty: 3.5,\n territory: {\n money: 1.2,\n respect: 1,\n wanted: 1.15,\n },\n },\n },\n {\n desc: \"Assign this gang member to extort civilians in your territory

Earns money - Slightly increases respect - Increases wanted - Scales heavily with territory\",\n isCombat: true,\n isHacking: false,\n name: \"Strongarm Civilians\",\n params: {\n baseRespect: 0.00004,\n baseWanted: 0.02,\n baseMoney: 2.5,\n hackWeight: 10,\n strWeight: 25,\n defWeight: 25,\n dexWeight: 20,\n agiWeight: 10,\n chaWeight: 10,\n difficulty: 5,\n territory: {\n money: 1.6,\n respect: 1.1,\n wanted: 1.5,\n },\n },\n },\n {\n desc: \"Assign this gang member to run cons

Earns money - Increases respect - Increases wanted level\",\n isCombat: true,\n isHacking: false,\n name: \"Run a Con\",\n params: {\n baseRespect: 0.00012,\n baseWanted: 0.05,\n baseMoney: 15,\n strWeight: 5,\n defWeight: 5,\n agiWeight: 25,\n dexWeight: 25,\n chaWeight: 40,\n difficulty: 14,\n },\n },\n {\n desc: \"Assign this gang member to commit armed robbery on stores, banks and armored cars

Earns money - Increases respect - Increases wanted level\",\n isCombat: true,\n isHacking: false,\n name: \"Armed Robbery\",\n params: {\n baseRespect: 0.00014,\n baseWanted: 0.1,\n baseMoney: 38,\n hackWeight: 20,\n strWeight: 15,\n defWeight: 15,\n agiWeight: 10,\n dexWeight: 20,\n chaWeight: 20,\n difficulty: 20,\n },\n },\n {\n desc: \"Assign this gang member to traffick illegal arms

Earns money - Increases respect - Increases wanted level - Scales heavily with territory\",\n isCombat: true,\n isHacking: false,\n name: \"Traffick Illegal Arms\",\n params: {\n baseRespect: 0.0002,\n baseWanted: 0.24,\n baseMoney: 58,\n hackWeight: 15,\n strWeight: 20,\n defWeight: 20,\n dexWeight: 20,\n chaWeight: 25,\n difficulty: 32,\n territory: {\n money: 1.4,\n respect: 1.3,\n wanted: 1.25,\n },\n },\n },\n {\n desc: \"Assign this gang member to threaten and black mail high-profile targets

Earns money - Slightly increases respect - Slightly increases wanted level\",\n isCombat: true,\n isHacking: false,\n name: \"Threaten & Blackmail\",\n params: {\n baseRespect: 0.0002,\n baseWanted: 0.125,\n baseMoney: 24,\n hackWeight: 25,\n strWeight: 25,\n dexWeight: 25,\n chaWeight: 25,\n difficulty: 28,\n },\n },\n {\n desc: \"Assign this gang member to engage in human trafficking operations

Earns money - Increases respect - Increases wanted level - Scales heavily with territory\",\n isCombat: true,\n isHacking: false,\n name: \"Human Trafficking\",\n params: {\n baseRespect: 0.004,\n baseWanted: 1.25,\n baseMoney: 120,\n hackWeight: 30,\n strWeight: 5,\n defWeight: 5,\n dexWeight: 30,\n chaWeight: 30,\n difficulty: 36,\n territory: {\n money: 1.5,\n respect: 1.5,\n wanted: 1.6,\n },\n },\n },\n {\n desc: \"Assign this gang member to commit acts of terrorism

Greatly increases respect - Greatly increases wanted level - Scales heavily with territory\",\n isCombat: true,\n isHacking: false,\n name: \"Terrorism\",\n params: {\n baseRespect: 0.01,\n baseWanted: 6,\n hackWeight: 20,\n strWeight: 20,\n defWeight: 20,\n dexWeight: 20,\n chaWeight: 20,\n difficulty: 36,\n territory: {\n money: 1,\n respect: 2,\n wanted: 2,\n },\n },\n },\n {\n desc: \"Assign this gang member to be a vigilante and protect the city from criminals

Decreases wanted level\",\n isCombat: true,\n isHacking: true,\n name: \"Vigilante Justice\",\n params: {\n baseWanted: -0.001,\n hackWeight: 20,\n strWeight: 20,\n defWeight: 20,\n dexWeight: 20,\n agiWeight: 20,\n difficulty: 1,\n territory: {\n money: 1,\n respect: 1,\n wanted: 0.9, // Gets harder with more territory\n },\n },\n },\n {\n desc: \"Assign this gang member to increase their combat stats (str, def, dex, agi)\",\n isCombat: true,\n isHacking: true,\n name: \"Train Combat\",\n params: {\n strWeight: 25,\n defWeight: 25,\n dexWeight: 25,\n agiWeight: 25,\n difficulty: 100,\n },\n },\n {\n desc: \"Assign this gang member to train their hacking skills\",\n isCombat: true,\n isHacking: true,\n name: \"Train Hacking\",\n params: { hackWeight: 100, difficulty: 45 },\n },\n {\n desc: \"Assign this gang member to train their charisma\",\n isCombat: true,\n isHacking: true,\n name: \"Train Charisma\",\n params: { chaWeight: 100, difficulty: 8 },\n },\n {\n desc: \"Assign this gang member to engage in territorial warfare with other gangs. Members assigned to this task will help increase your gang's territory and will defend your territory from being taken.\",\n isCombat: true,\n isHacking: true,\n name: \"Territory Warfare\",\n params: {\n hackWeight: 15,\n strWeight: 20,\n defWeight: 20,\n dexWeight: 20,\n agiWeight: 20,\n chaWeight: 5,\n difficulty: 5,\n },\n },\n];\n","import { GangMemberTask } from \"./GangMemberTask\";\nimport { GangMemberTasks } from \"./GangMemberTasks\";\nimport { GangMemberUpgrade } from \"./GangMemberUpgrade\";\nimport { GangMemberUpgrades } from \"./GangMemberUpgrades\";\nimport { IAscensionResult } from \"./IAscensionResult\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { AllGangs } from \"./AllGangs\";\nimport { IGang } from \"./IGang\";\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../utils/JSONReviver\";\n\ninterface IMults {\n hack: number;\n str: number;\n def: number;\n dex: number;\n agi: number;\n cha: number;\n}\n\nexport class GangMember {\n name: string;\n task = \"Unassigned\";\n\n earnedRespect = 0;\n\n hack = 1;\n str = 1;\n def = 1;\n dex = 1;\n agi = 1;\n cha = 1;\n\n hack_exp = 0;\n str_exp = 0;\n def_exp = 0;\n dex_exp = 0;\n agi_exp = 0;\n cha_exp = 0;\n\n hack_mult = 1;\n str_mult = 1;\n def_mult = 1;\n dex_mult = 1;\n agi_mult = 1;\n cha_mult = 1;\n\n hack_asc_points = 0;\n str_asc_points = 0;\n def_asc_points = 0;\n dex_asc_points = 0;\n agi_asc_points = 0;\n cha_asc_points = 0;\n\n upgrades: string[] = []; // Names of upgrades\n augmentations: string[] = []; // Names of augmentations only\n\n constructor(name = \"\") {\n this.name = name;\n }\n\n calculateSkill(exp: number, mult = 1): number {\n return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);\n }\n\n calculateAscensionMult(points: number): number {\n return Math.max(Math.pow(points / 4000, 0.7), 1);\n }\n\n updateSkillLevels(): void {\n this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.calculateAscensionMult(this.hack_asc_points));\n this.str = this.calculateSkill(this.str_exp, this.str_mult * this.calculateAscensionMult(this.str_asc_points));\n this.def = this.calculateSkill(this.def_exp, this.def_mult * this.calculateAscensionMult(this.def_asc_points));\n this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.calculateAscensionMult(this.dex_asc_points));\n this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.calculateAscensionMult(this.agi_asc_points));\n this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.calculateAscensionMult(this.cha_asc_points));\n }\n\n calculatePower(): number {\n return (this.hack + this.str + this.def + this.dex + this.agi + this.cha) / 95;\n }\n\n assignToTask(taskName: string): boolean {\n if (!GangMemberTasks.hasOwnProperty(taskName)) {\n this.task = \"Unassigned\";\n return false;\n }\n this.task = taskName;\n return true;\n }\n\n unassignFromTask(): void {\n this.task = \"Unassigned\";\n }\n\n getTask(): GangMemberTask {\n // TODO(hydroflame): transfer that to a save file migration function\n // Backwards compatibility\n if ((this.task as any) instanceof GangMemberTask) {\n this.task = (this.task as any).name;\n }\n\n if (GangMemberTasks.hasOwnProperty(this.task)) {\n return GangMemberTasks[this.task];\n }\n return GangMemberTasks[\"Unassigned\"];\n }\n\n calculateRespectGain(gang: IGang): number {\n const task = this.getTask();\n if (task.baseRespect === 0) return 0;\n let statWeight =\n (task.hackWeight / 100) * this.hack +\n (task.strWeight / 100) * this.str +\n (task.defWeight / 100) * this.def +\n (task.dexWeight / 100) * this.dex +\n (task.agiWeight / 100) * this.agi +\n (task.chaWeight / 100) * this.cha;\n statWeight -= 4 * task.difficulty;\n if (statWeight <= 0) return 0;\n const territoryMult = Math.max(\n 0.005,\n Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100,\n );\n if (isNaN(territoryMult) || territoryMult <= 0) return 0;\n const respectMult = gang.getWantedPenalty();\n return 11 * task.baseRespect * statWeight * territoryMult * respectMult;\n }\n\n calculateWantedLevelGain(gang: IGang): number {\n const task = this.getTask();\n if (task.baseWanted === 0) return 0;\n let statWeight =\n (task.hackWeight / 100) * this.hack +\n (task.strWeight / 100) * this.str +\n (task.defWeight / 100) * this.def +\n (task.dexWeight / 100) * this.dex +\n (task.agiWeight / 100) * this.agi +\n (task.chaWeight / 100) * this.cha;\n statWeight -= 3.5 * task.difficulty;\n if (statWeight <= 0) return 0;\n const territoryMult = Math.max(\n 0.005,\n Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100,\n );\n if (isNaN(territoryMult) || territoryMult <= 0) return 0;\n if (task.baseWanted < 0) {\n return 0.4 * task.baseWanted * statWeight * territoryMult;\n }\n const calc = (7 * task.baseWanted) / Math.pow(3 * statWeight * territoryMult, 0.8);\n\n // Put an arbitrary cap on this to prevent wanted level from rising too fast if the\n // denominator is very small. Might want to rethink formula later\n return Math.min(100, calc);\n }\n\n calculateMoneyGain(gang: IGang): number {\n const task = this.getTask();\n if (task.baseMoney === 0) return 0;\n let statWeight =\n (task.hackWeight / 100) * this.hack +\n (task.strWeight / 100) * this.str +\n (task.defWeight / 100) * this.def +\n (task.dexWeight / 100) * this.dex +\n (task.agiWeight / 100) * this.agi +\n (task.chaWeight / 100) * this.cha;\n\n statWeight -= 3.2 * task.difficulty;\n if (statWeight <= 0) return 0;\n const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100);\n if (isNaN(territoryMult) || territoryMult <= 0) return 0;\n const respectMult = gang.getWantedPenalty();\n return 5 * task.baseMoney * statWeight * territoryMult * respectMult;\n }\n\n expMult(): IMults {\n return {\n hack: (this.hack_mult - 1) / 4 + 1,\n str: (this.str_mult - 1) / 4 + 1,\n def: (this.def_mult - 1) / 4 + 1,\n dex: (this.dex_mult - 1) / 4 + 1,\n agi: (this.agi_mult - 1) / 4 + 1,\n cha: (this.cha_mult - 1) / 4 + 1,\n };\n }\n\n gainExperience(numCycles = 1): void {\n const task = this.getTask();\n if (task === GangMemberTasks[\"Unassigned\"]) return;\n const difficultyMult = Math.pow(task.difficulty, 0.9);\n const difficultyPerCycles = difficultyMult * numCycles;\n const weightDivisor = 1500;\n const expMult = this.expMult();\n this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack;\n this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str;\n this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def;\n this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex;\n this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi;\n this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha;\n }\n\n recordEarnedRespect(numCycles = 1, gang: IGang): void {\n this.earnedRespect += this.calculateRespectGain(gang) * numCycles;\n }\n\n getGainedAscensionPoints(): IMults {\n return {\n hack: Math.max(this.hack_exp - 1000, 0),\n str: Math.max(this.str_exp - 1000, 0),\n def: Math.max(this.def_exp - 1000, 0),\n dex: Math.max(this.dex_exp - 1000, 0),\n agi: Math.max(this.agi_exp - 1000, 0),\n cha: Math.max(this.cha_exp - 1000, 0),\n };\n }\n\n canAscend(): boolean {\n const points = this.getGainedAscensionPoints();\n return points.hack > 0 || points.str > 0 || points.def > 0 || points.dex > 0 || points.agi > 0 || points.cha > 0;\n }\n\n getCurrentAscensionMults(): IMults {\n return {\n hack: this.calculateAscensionMult(this.hack_asc_points),\n str: this.calculateAscensionMult(this.str_asc_points),\n def: this.calculateAscensionMult(this.def_asc_points),\n dex: this.calculateAscensionMult(this.dex_asc_points),\n agi: this.calculateAscensionMult(this.agi_asc_points),\n cha: this.calculateAscensionMult(this.cha_asc_points),\n };\n }\n\n getAscensionMultsAfterAscend(): IMults {\n const points = this.getGainedAscensionPoints();\n return {\n hack: this.calculateAscensionMult(this.hack_asc_points + points.hack),\n str: this.calculateAscensionMult(this.str_asc_points + points.str),\n def: this.calculateAscensionMult(this.def_asc_points + points.def),\n dex: this.calculateAscensionMult(this.dex_asc_points + points.dex),\n agi: this.calculateAscensionMult(this.agi_asc_points + points.agi),\n cha: this.calculateAscensionMult(this.cha_asc_points + points.cha),\n };\n }\n\n getAscensionResults(): IMults {\n const postAscend = this.getAscensionMultsAfterAscend();\n const preAscend = this.getCurrentAscensionMults();\n\n return {\n hack: postAscend.hack / preAscend.hack,\n str: postAscend.str / preAscend.str,\n def: postAscend.def / preAscend.def,\n dex: postAscend.dex / preAscend.dex,\n agi: postAscend.agi / preAscend.agi,\n cha: postAscend.cha / preAscend.cha,\n };\n }\n\n ascend(): IAscensionResult {\n const res = this.getAscensionResults();\n const points = this.getGainedAscensionPoints();\n this.hack_asc_points += points.hack;\n this.str_asc_points += points.str;\n this.def_asc_points += points.def;\n this.dex_asc_points += points.dex;\n this.agi_asc_points += points.agi;\n this.cha_asc_points += points.cha;\n\n // Remove upgrades. Then re-calculate multipliers and stats\n this.upgrades.length = 0;\n this.hack_mult = 1;\n this.str_mult = 1;\n this.def_mult = 1;\n this.dex_mult = 1;\n this.agi_mult = 1;\n this.cha_mult = 1;\n for (let i = 0; i < this.augmentations.length; ++i) {\n const aug = GangMemberUpgrades[this.augmentations[i]];\n this.applyUpgrade(aug);\n }\n\n // Clear exp and recalculate stats\n this.hack_exp = 0;\n this.str_exp = 0;\n this.def_exp = 0;\n this.dex_exp = 0;\n this.agi_exp = 0;\n this.cha_exp = 0;\n this.updateSkillLevels();\n\n const respectToDeduct = this.earnedRespect;\n this.earnedRespect = 0;\n return {\n respect: respectToDeduct,\n hack: res.hack,\n str: res.str,\n def: res.def,\n dex: res.dex,\n agi: res.agi,\n cha: res.cha,\n };\n }\n\n applyUpgrade(upg: GangMemberUpgrade): void {\n if (upg.mults.str != null) this.str_mult *= upg.mults.str;\n if (upg.mults.def != null) this.def_mult *= upg.mults.def;\n if (upg.mults.dex != null) this.dex_mult *= upg.mults.dex;\n if (upg.mults.agi != null) this.agi_mult *= upg.mults.agi;\n if (upg.mults.cha != null) this.cha_mult *= upg.mults.cha;\n if (upg.mults.hack != null) this.hack_mult *= upg.mults.hack;\n }\n\n buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {\n // Prevent purchasing of already-owned upgrades\n if (this.augmentations.includes(upg.name) || this.upgrades.includes(upg.name)) return false;\n\n if (player.money.lt(gang.getUpgradeCost(upg))) return false;\n player.loseMoney(gang.getUpgradeCost(upg));\n if (upg.type === \"g\") {\n this.augmentations.push(upg.name);\n } else {\n this.upgrades.push(upg.name);\n }\n this.applyUpgrade(upg);\n return true;\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"GangMember\", this);\n }\n\n /**\n * Initiatizes a GangMember object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): GangMember {\n return Generic_fromJSON(GangMember, value.data);\n }\n}\n\nReviver.constructors.GangMember = GangMember;\n","import { IMults, UpgradeType } from \"./data/upgrades\";\nimport { numeralWrapper } from \"../ui/numeralFormat\";\n\nexport class GangMemberUpgrade {\n name: string;\n cost: number;\n type: UpgradeType;\n desc: string;\n mults: IMults;\n\n constructor(name = \"\", cost = 0, type: UpgradeType = UpgradeType.Weapon, mults: IMults = {}) {\n this.name = name;\n this.cost = cost;\n this.type = type;\n this.mults = mults;\n\n this.desc = this.createDescription();\n }\n\n createDescription(): string {\n const lines = [\"Effects:\"];\n if (this.mults.str != null) {\n lines.push(`+${numeralWrapper.formatPercentage(this.mults.str - 1, 0)} strength skill`);\n lines.push(`+${numeralWrapper.formatPercentage((this.mults.str - 1) / 4, 2)} strength exp`);\n }\n if (this.mults.def != null) {\n lines.push(`+${numeralWrapper.formatPercentage(this.mults.def - 1, 0)} defense skill`);\n lines.push(`+${numeralWrapper.formatPercentage((this.mults.def - 1) / 4, 2)} defense exp`);\n }\n if (this.mults.dex != null) {\n lines.push(`+${numeralWrapper.formatPercentage(this.mults.dex - 1, 0)} dexterity skill`);\n lines.push(`+${numeralWrapper.formatPercentage((this.mults.dex - 1) / 4, 2)} dexterity exp`);\n }\n if (this.mults.agi != null) {\n lines.push(`+${numeralWrapper.formatPercentage(this.mults.agi - 1, 0)} agility skill`);\n lines.push(`+${numeralWrapper.formatPercentage((this.mults.agi - 1) / 4, 2)} agility exp`);\n }\n if (this.mults.cha != null) {\n lines.push(`+${numeralWrapper.formatPercentage(this.mults.cha - 1, 0)} charisma skill`);\n lines.push(`+${numeralWrapper.formatPercentage((this.mults.cha - 1) / 4, 2)} charisma exp`);\n }\n if (this.mults.hack != null) {\n lines.push(`+${numeralWrapper.formatPercentage(this.mults.hack - 1, 0)} hacking skill`);\n lines.push(`+${numeralWrapper.formatPercentage((this.mults.hack - 1) / 4, 2)} hacking exp`);\n }\n return lines.join(\"
\");\n }\n\n // User friendly version of type.\n getType(): string {\n switch (this.type) {\n case UpgradeType.Weapon:\n return \"Weapon\";\n case UpgradeType.Armor:\n return \"Armor\";\n case UpgradeType.Vehicle:\n return \"Vehicle\";\n case UpgradeType.Rootkit:\n return \"Rootkit\";\n case UpgradeType.Augmentation:\n return \"Augmentation\";\n default:\n return \"\";\n }\n }\n}\n","/**\n * Determines if the number is a power of 2\n * @param n The number to check.\n */\nexport function isPowerOfTwo(n: number): boolean {\n if (isNaN(n)) {\n return false;\n }\n\n if (n === 0) {\n return false;\n }\n\n // Disabiling the bitwise rule because it's honestly the most effecient way to check for this.\n // tslint:disable-next-line:no-bitwise\n return (n & (n - 1)) === 0;\n}\n","/**\n * Initialization metadata for all Stocks. This is used to generate the\n * stock parameter values upon a reset\n *\n * Some notes:\n * - Megacorporations have better otlkMags\n * - Higher volatility -> Bigger spread\n * - Lower price -> Bigger spread\n * - Share tx required for movement used for balancing\n */\nimport { StockSymbols } from \"./StockSymbols\";\nimport { IConstructorParams } from \"../Stock\";\nimport { LocationName } from \"../../Locations/data/LocationNames\";\n\nexport const InitStockMetadata: IConstructorParams[] = [\n {\n b: true,\n initPrice: {\n max: 28e3,\n min: 17e3,\n },\n marketCap: 2.4e12,\n mv: {\n divisor: 100,\n max: 50,\n min: 40,\n },\n name: LocationName.AevumECorp,\n otlkMag: 19,\n spreadPerc: {\n divisor: 10,\n max: 5,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.AevumECorp],\n },\n\n {\n b: true,\n initPrice: {\n max: 34e3,\n min: 24e3,\n },\n marketCap: 2.4e12,\n mv: {\n divisor: 100,\n max: 50,\n min: 40,\n },\n name: LocationName.Sector12MegaCorp,\n otlkMag: 19,\n spreadPerc: {\n divisor: 10,\n max: 5,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.Sector12MegaCorp],\n },\n\n {\n b: true,\n initPrice: {\n max: 25e3,\n min: 12e3,\n },\n marketCap: 1.6e12,\n mv: {\n divisor: 100,\n max: 80,\n min: 70,\n },\n name: LocationName.Sector12BladeIndustries,\n otlkMag: 13,\n spreadPerc: {\n divisor: 10,\n max: 6,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.Sector12BladeIndustries],\n },\n\n {\n b: true,\n initPrice: {\n max: 25e3,\n min: 10e3,\n },\n marketCap: 1.5e12,\n mv: {\n divisor: 100,\n max: 75,\n min: 65,\n },\n name: LocationName.AevumClarkeIncorporated,\n otlkMag: 12,\n spreadPerc: {\n divisor: 10,\n max: 5,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.AevumClarkeIncorporated],\n },\n\n {\n b: true,\n initPrice: {\n max: 43e3,\n min: 32e3,\n },\n marketCap: 1.8e12,\n mv: {\n divisor: 100,\n max: 70,\n min: 60,\n },\n name: LocationName.VolhavenOmniTekIncorporated,\n otlkMag: 12,\n spreadPerc: {\n divisor: 10,\n max: 6,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated],\n },\n\n {\n b: true,\n initPrice: {\n max: 80e3,\n min: 50e3,\n },\n marketCap: 2e12,\n mv: {\n divisor: 100,\n max: 110,\n min: 100,\n },\n name: LocationName.Sector12FourSigma,\n otlkMag: 17,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.Sector12FourSigma],\n },\n\n {\n b: true,\n initPrice: {\n max: 28e3,\n min: 16e3,\n },\n marketCap: 1.9e12,\n mv: {\n divisor: 100,\n max: 85,\n min: 75,\n },\n name: LocationName.ChongqingKuaiGongInternational,\n otlkMag: 10,\n spreadPerc: {\n divisor: 10,\n max: 7,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational],\n },\n\n {\n b: true,\n initPrice: {\n max: 36e3,\n min: 29e3,\n },\n marketCap: 2e12,\n mv: {\n divisor: 100,\n max: 130,\n min: 120,\n },\n name: LocationName.AevumFulcrumTechnologies,\n otlkMag: 16,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 1,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.AevumFulcrumTechnologies],\n },\n\n {\n b: true,\n initPrice: {\n max: 25e3,\n min: 20e3,\n },\n marketCap: 1.2e12,\n mv: {\n divisor: 100,\n max: 90,\n min: 80,\n },\n name: LocationName.IshimaStormTechnologies,\n otlkMag: 7,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 2,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.IshimaStormTechnologies],\n },\n\n {\n b: true,\n initPrice: {\n max: 19e3,\n min: 6e3,\n },\n marketCap: 900e9,\n mv: {\n divisor: 100,\n max: 70,\n min: 60,\n },\n name: LocationName.NewTokyoDefComm,\n otlkMag: 10,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 2,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.NewTokyoDefComm],\n },\n\n {\n b: true,\n initPrice: {\n max: 18e3,\n min: 10e3,\n },\n marketCap: 825e9,\n mv: {\n divisor: 100,\n max: 65,\n min: 55,\n },\n name: LocationName.VolhavenHeliosLabs,\n otlkMag: 9,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 2,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.VolhavenHeliosLabs],\n },\n\n {\n b: true,\n initPrice: {\n max: 14e3,\n min: 8e3,\n },\n marketCap: 1e12,\n mv: {\n divisor: 100,\n max: 80,\n min: 70,\n },\n name: LocationName.NewTokyoVitaLife,\n otlkMag: 7,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 2,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.NewTokyoVitaLife],\n },\n\n {\n b: true,\n initPrice: {\n max: 24e3,\n min: 12e3,\n },\n marketCap: 800e9,\n mv: {\n divisor: 100,\n max: 70,\n min: 60,\n },\n name: LocationName.Sector12IcarusMicrosystems,\n otlkMag: 7.5,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 3,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems],\n },\n\n {\n b: true,\n initPrice: {\n max: 29e3,\n min: 16e3,\n },\n marketCap: 900e9,\n mv: {\n divisor: 100,\n max: 60,\n min: 50,\n },\n name: LocationName.Sector12UniversalEnergy,\n otlkMag: 10,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 2,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.Sector12UniversalEnergy],\n },\n\n {\n b: true,\n initPrice: {\n max: 17e3,\n min: 8e3,\n },\n marketCap: 640e9,\n mv: {\n divisor: 100,\n max: 65,\n min: 55,\n },\n name: LocationName.AevumAeroCorp,\n otlkMag: 6,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 3,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 42e3,\n },\n symbol: StockSymbols[LocationName.AevumAeroCorp],\n },\n\n {\n b: true,\n initPrice: {\n max: 15e3,\n min: 6e3,\n },\n marketCap: 600e9,\n mv: {\n divisor: 100,\n max: 75,\n min: 65,\n },\n name: LocationName.VolhavenOmniaCybersystems,\n otlkMag: 4.5,\n spreadPerc: {\n divisor: 10,\n max: 11,\n min: 4,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 42e3,\n },\n symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems],\n },\n\n {\n b: true,\n initPrice: {\n max: 28e3,\n min: 14e3,\n },\n marketCap: 705e9,\n mv: {\n divisor: 100,\n max: 80,\n min: 70,\n },\n name: LocationName.ChongqingSolarisSpaceSystems,\n otlkMag: 8.5,\n spreadPerc: {\n divisor: 10,\n max: 12,\n min: 4,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 42e3,\n },\n symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems],\n },\n\n {\n b: true,\n initPrice: {\n max: 30e3,\n min: 12e3,\n },\n marketCap: 695e9,\n mv: {\n divisor: 100,\n max: 65,\n min: 55,\n },\n name: LocationName.NewTokyoGlobalPharmaceuticals,\n otlkMag: 10.5,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 4,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 42e3,\n },\n symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals],\n },\n\n {\n b: true,\n initPrice: {\n max: 27e3,\n min: 15e3,\n },\n marketCap: 600e9,\n mv: {\n divisor: 100,\n max: 80,\n min: 70,\n },\n name: LocationName.IshimaNovaMedical,\n otlkMag: 5,\n spreadPerc: {\n divisor: 10,\n max: 11,\n min: 4,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 42e3,\n },\n symbol: StockSymbols[LocationName.IshimaNovaMedical],\n },\n\n {\n b: true,\n initPrice: {\n max: 8.5e3,\n min: 4e3,\n },\n marketCap: 450e9,\n mv: {\n divisor: 100,\n max: 260,\n min: 240,\n },\n name: LocationName.AevumWatchdogSecurity,\n otlkMag: 1.5,\n spreadPerc: {\n divisor: 10,\n max: 12,\n min: 5,\n },\n shareTxForMovement: {\n max: 54e3,\n min: 12e3,\n },\n symbol: StockSymbols[LocationName.AevumWatchdogSecurity],\n },\n\n {\n b: true,\n initPrice: {\n max: 8e3,\n min: 4.5e3,\n },\n marketCap: 300e9,\n mv: {\n divisor: 100,\n max: 135,\n min: 115,\n },\n name: LocationName.VolhavenLexoCorp,\n otlkMag: 6,\n spreadPerc: {\n divisor: 10,\n max: 12,\n min: 5,\n },\n shareTxForMovement: {\n max: 108e3,\n min: 36e3,\n },\n symbol: StockSymbols[LocationName.VolhavenLexoCorp],\n },\n\n {\n b: true,\n initPrice: {\n max: 7e3,\n min: 2e3,\n },\n marketCap: 180e9,\n mv: {\n divisor: 100,\n max: 70,\n min: 50,\n },\n name: LocationName.AevumRhoConstruction,\n otlkMag: 1,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 3,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 60e3,\n },\n symbol: StockSymbols[LocationName.AevumRhoConstruction],\n },\n\n {\n b: true,\n initPrice: {\n max: 8.5e3,\n min: 4e3,\n },\n marketCap: 240e9,\n mv: {\n divisor: 100,\n max: 205,\n min: 175,\n },\n name: LocationName.Sector12AlphaEnterprises,\n otlkMag: 10,\n spreadPerc: {\n divisor: 10,\n max: 16,\n min: 5,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.Sector12AlphaEnterprises],\n },\n\n {\n b: true,\n initPrice: {\n max: 8e3,\n min: 3e3,\n },\n marketCap: 200e9,\n mv: {\n divisor: 100,\n max: 170,\n min: 150,\n },\n name: LocationName.VolhavenSysCoreSecurities,\n otlkMag: 3,\n spreadPerc: {\n divisor: 10,\n max: 12,\n min: 5,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 15e3,\n },\n symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities],\n },\n\n {\n b: true,\n initPrice: {\n max: 6e3,\n min: 1e3,\n },\n marketCap: 185e9,\n mv: {\n divisor: 100,\n max: 100,\n min: 80,\n },\n name: LocationName.VolhavenCompuTek,\n otlkMag: 4,\n spreadPerc: {\n divisor: 10,\n max: 12,\n min: 4,\n },\n shareTxForMovement: {\n max: 126e3,\n min: 60e3,\n },\n symbol: StockSymbols[LocationName.VolhavenCompuTek],\n },\n\n {\n b: true,\n initPrice: {\n max: 5e3,\n min: 1e3,\n },\n marketCap: 58e9,\n mv: {\n divisor: 100,\n max: 400,\n min: 200,\n },\n name: LocationName.AevumNetLinkTechnologies,\n otlkMag: 1,\n spreadPerc: {\n divisor: 10,\n max: 20,\n min: 5,\n },\n shareTxForMovement: {\n max: 54e3,\n min: 18e3,\n },\n symbol: StockSymbols[LocationName.AevumNetLinkTechnologies],\n },\n\n {\n b: true,\n initPrice: {\n max: 8e3,\n min: 1e3,\n },\n marketCap: 60e9,\n mv: {\n divisor: 100,\n max: 110,\n min: 90,\n },\n name: LocationName.IshimaOmegaSoftware,\n otlkMag: 0.5,\n spreadPerc: {\n divisor: 10,\n max: 13,\n min: 4,\n },\n shareTxForMovement: {\n max: 90e3,\n min: 30e3,\n },\n symbol: StockSymbols[LocationName.IshimaOmegaSoftware],\n },\n\n {\n b: false,\n initPrice: {\n max: 4.5e3,\n min: 500,\n },\n marketCap: 45e9,\n mv: {\n divisor: 100,\n max: 80,\n min: 70,\n },\n name: LocationName.Sector12FoodNStuff,\n otlkMag: 1,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 6,\n },\n shareTxForMovement: {\n max: 180e3,\n min: 60e3,\n },\n symbol: StockSymbols[LocationName.Sector12FoodNStuff],\n },\n\n {\n b: true,\n initPrice: {\n max: 3.5e3,\n min: 1.5e3,\n },\n marketCap: 30e9,\n mv: {\n divisor: 100,\n max: 275,\n min: 100,\n },\n name: \"Sigma Cosmetics\",\n otlkMag: 0,\n spreadPerc: {\n divisor: 10,\n max: 14,\n min: 6,\n },\n shareTxForMovement: {\n max: 70e3,\n min: 20e3,\n },\n symbol: StockSymbols[\"Sigma Cosmetics\"],\n },\n\n {\n b: true,\n initPrice: {\n max: 1.5e3,\n min: 250,\n },\n marketCap: 42e9,\n mv: {\n divisor: 100,\n max: 350,\n min: 200,\n },\n name: \"Joes Guns\",\n otlkMag: 1,\n spreadPerc: {\n divisor: 10,\n max: 14,\n min: 6,\n },\n shareTxForMovement: {\n max: 52e3,\n min: 15e3,\n },\n symbol: StockSymbols[\"Joes Guns\"],\n },\n\n {\n b: true,\n initPrice: {\n max: 1.5e3,\n min: 250,\n },\n marketCap: 100e9,\n mv: {\n divisor: 100,\n max: 175,\n min: 120,\n },\n name: \"Catalyst Ventures\",\n otlkMag: 13.5,\n spreadPerc: {\n divisor: 10,\n max: 14,\n min: 5,\n },\n shareTxForMovement: {\n max: 72e3,\n min: 24e3,\n },\n symbol: StockSymbols[\"Catalyst Ventures\"],\n },\n\n {\n b: true,\n initPrice: {\n max: 30e3,\n min: 15e3,\n },\n marketCap: 360e9,\n mv: {\n divisor: 100,\n max: 80,\n min: 70,\n },\n name: \"Microdyne Technologies\",\n otlkMag: 8,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 3,\n },\n shareTxForMovement: {\n max: 216e3,\n min: 90e3,\n },\n symbol: StockSymbols[\"Microdyne Technologies\"],\n },\n\n {\n b: true,\n initPrice: {\n max: 24e3,\n min: 12e3,\n },\n marketCap: 420e9,\n mv: {\n divisor: 100,\n max: 70,\n min: 50,\n },\n name: \"Titan Laboratories\",\n otlkMag: 11,\n spreadPerc: {\n divisor: 10,\n max: 10,\n min: 2,\n },\n shareTxForMovement: {\n max: 216e3,\n min: 90e3,\n },\n symbol: StockSymbols[\"Titan Laboratories\"],\n },\n];\n","/**\n * Metadata for constructing Location objects for all Locations\n * in the game\n */\nimport { CityName } from \"./CityNames\";\nimport { LocationName } from \"./LocationNames\";\nimport { IConstructorParams } from \"../Location\";\nimport { LocationType } from \"../LocationTypeEnum\";\n\nexport const LocationsMetadata: IConstructorParams[] = [\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 12,\n startingSecurityLevel: 8.18,\n },\n name: LocationName.AevumAeroCorp,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 15,\n startingSecurityLevel: 8.19,\n },\n name: LocationName.AevumBachmanAndAssociates,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 18,\n startingSecurityLevel: 9.55,\n },\n name: LocationName.AevumClarkeIncorporated,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n costMult: 3,\n expMult: 2,\n name: LocationName.AevumCrushFitnessGym,\n types: [LocationType.Gym],\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 37,\n startingSecurityLevel: 17.02,\n },\n name: LocationName.AevumECorp,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 512,\n techVendorMinRam: 128,\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 15.54,\n },\n name: LocationName.AevumFulcrumTechnologies,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 1024,\n techVendorMinRam: 256,\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 12,\n startingSecurityLevel: 7.89,\n },\n name: LocationName.AevumGalacticCybersystems,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 6,\n startingSecurityLevel: 3.29,\n },\n name: LocationName.AevumNetLinkTechnologies,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 64,\n techVendorMinRam: 8,\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 6,\n startingSecurityLevel: 5.35,\n },\n name: LocationName.AevumPolice,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 5,\n startingSecurityLevel: 5.02,\n },\n name: LocationName.AevumRhoConstruction,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n costMult: 10,\n expMult: 5,\n name: LocationName.AevumSnapFitnessGym,\n types: [LocationType.Gym],\n },\n {\n city: CityName.Aevum,\n costMult: 4,\n expMult: 3,\n name: LocationName.AevumSummitUniversity,\n types: [LocationType.University],\n },\n {\n city: CityName.Aevum,\n infiltrationData: {\n maxClearanceLevel: 7,\n startingSecurityLevel: 5.85,\n },\n name: LocationName.AevumWatchdogSecurity,\n types: [LocationType.Company],\n },\n {\n city: CityName.Aevum,\n name: LocationName.AevumCasino,\n types: [LocationType.Casino],\n },\n {\n city: CityName.Chongqing,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 16.25,\n },\n name: LocationName.ChongqingKuaiGongInternational,\n types: [LocationType.Company],\n },\n {\n city: CityName.Chongqing,\n infiltrationData: {\n maxClearanceLevel: 18,\n startingSecurityLevel: 12.59,\n },\n name: LocationName.ChongqingSolarisSpaceSystems,\n types: [LocationType.Company],\n },\n {\n city: CityName.Ishima,\n infiltrationData: {\n maxClearanceLevel: 12,\n startingSecurityLevel: 5.02,\n },\n name: LocationName.IshimaNovaMedical,\n types: [LocationType.Company],\n },\n {\n city: CityName.Ishima,\n infiltrationData: {\n maxClearanceLevel: 10,\n startingSecurityLevel: 3.2,\n },\n name: LocationName.IshimaOmegaSoftware,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 128,\n techVendorMinRam: 4,\n },\n {\n city: CityName.Ishima,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 5.38,\n },\n name: LocationName.IshimaStormTechnologies,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 512,\n techVendorMinRam: 32,\n },\n {\n city: CityName.NewTokyo,\n infiltrationData: {\n maxClearanceLevel: 17,\n startingSecurityLevel: 7.18,\n },\n name: LocationName.NewTokyoDefComm,\n types: [LocationType.Company],\n },\n {\n city: CityName.NewTokyo,\n infiltrationData: {\n maxClearanceLevel: 20,\n startingSecurityLevel: 5.9,\n },\n name: LocationName.NewTokyoGlobalPharmaceuticals,\n types: [LocationType.Company],\n },\n {\n city: CityName.NewTokyo,\n infiltrationData: {\n maxClearanceLevel: 5,\n startingSecurityLevel: 2.5,\n },\n name: LocationName.NewTokyoNoodleBar,\n types: [LocationType.Company, LocationType.Special],\n },\n {\n city: CityName.NewTokyo,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 5.52,\n },\n name: LocationName.NewTokyoVitaLife,\n types: [LocationType.Company, LocationType.Special],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 10,\n startingSecurityLevel: 3.62,\n },\n name: LocationName.Sector12AlphaEnterprises,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 8,\n techVendorMinRam: 2,\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 10.59,\n },\n name: LocationName.Sector12BladeIndustries,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n name: LocationName.Sector12CIA,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 15,\n startingSecurityLevel: 4.66,\n },\n name: LocationName.Sector12CarmichaelSecurity,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n name: LocationName.Sector12CityHall,\n types: [LocationType.Special],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 12,\n startingSecurityLevel: 5.9,\n },\n name: LocationName.Sector12DeltaOne,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n name: LocationName.Sector12FoodNStuff,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 8.18,\n },\n name: LocationName.Sector12FourSigma,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 17,\n startingSecurityLevel: 6.02,\n },\n name: LocationName.Sector12IcarusMicrosystems,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n expMult: 1,\n costMult: 1,\n name: LocationName.Sector12IronGym,\n types: [LocationType.Gym],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 5,\n startingSecurityLevel: 3.13,\n },\n name: LocationName.Sector12JoesGuns,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 31,\n startingSecurityLevel: 16.36,\n },\n name: LocationName.Sector12MegaCorp,\n types: [LocationType.Company],\n },\n {\n city: CityName.Sector12,\n name: LocationName.Sector12NSA,\n types: [LocationType.Company, LocationType.Special],\n },\n {\n city: CityName.Sector12,\n costMult: 20,\n expMult: 10,\n name: LocationName.Sector12PowerhouseGym,\n types: [LocationType.Gym],\n },\n {\n city: CityName.Sector12,\n costMult: 3,\n expMult: 2,\n name: LocationName.Sector12RothmanUniversity,\n types: [LocationType.University],\n },\n {\n city: CityName.Sector12,\n infiltrationData: {\n maxClearanceLevel: 12,\n startingSecurityLevel: 5.9,\n },\n name: LocationName.Sector12UniversalEnergy,\n types: [LocationType.Company],\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 15,\n startingSecurityLevel: 3.59,\n },\n name: LocationName.VolhavenCompuTek,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 256,\n techVendorMinRam: 8,\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 18,\n startingSecurityLevel: 7.28,\n },\n name: LocationName.VolhavenHeliosLabs,\n types: [LocationType.Company],\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 15,\n startingSecurityLevel: 4.35,\n },\n name: LocationName.VolhavenLexoCorp,\n types: [LocationType.Company],\n },\n {\n city: CityName.Volhaven,\n costMult: 7,\n expMult: 4,\n name: LocationName.VolhavenMilleniumFitnessGym,\n types: [LocationType.Gym],\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 50,\n startingSecurityLevel: 8.53,\n },\n name: LocationName.VolhavenNWO,\n types: [LocationType.Company],\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 25,\n startingSecurityLevel: 7.74,\n },\n name: LocationName.VolhavenOmniTekIncorporated,\n types: [LocationType.Company, LocationType.TechVendor],\n techVendorMaxRam: 1024,\n techVendorMinRam: 128,\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 22,\n startingSecurityLevel: 6,\n },\n name: LocationName.VolhavenOmniaCybersystems,\n types: [LocationType.Company],\n },\n {\n city: CityName.Volhaven,\n infiltrationData: {\n maxClearanceLevel: 18,\n startingSecurityLevel: 4.77,\n },\n name: LocationName.VolhavenSysCoreSecurities,\n types: [LocationType.Company],\n },\n {\n city: CityName.Volhaven,\n costMult: 5,\n expMult: 4,\n name: LocationName.VolhavenZBInstituteOfTechnology,\n types: [LocationType.University],\n },\n {\n city: null,\n name: LocationName.Hospital,\n types: [LocationType.Hospital],\n },\n {\n city: null,\n name: LocationName.Slums,\n types: [LocationType.Slums],\n },\n {\n city: null,\n name: LocationName.TravelAgency,\n types: [LocationType.TravelAgency],\n },\n {\n city: null,\n name: LocationName.WorldStockExchange,\n types: [LocationType.StockMarket],\n },\n];\n","import React, { useState, useEffect } from \"react\";\n\nimport Paper from \"@mui/material/Paper\";\nimport Typography from \"@mui/material/Typography\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Button from \"@mui/material/Button\";\nimport ArrowForwardIos from \"@mui/icons-material/ArrowForwardIos\";\nimport ArrowBackIos from \"@mui/icons-material/ArrowBackIos\";\nimport { ITutorialEvents } from \"./ITutorialEvents\";\nimport { CopyableText } from \"../React/CopyableText\";\n\nimport ListItem from \"@mui/material/ListItem\";\nimport EqualizerIcon from \"@mui/icons-material/Equalizer\";\nimport LastPageIcon from \"@mui/icons-material/LastPage\";\nimport HelpIcon from \"@mui/icons-material/Help\";\nimport AccountTreeIcon from \"@mui/icons-material/AccountTree\";\nimport StorageIcon from \"@mui/icons-material/Storage\";\nimport LocationCityIcon from \"@mui/icons-material/LocationCity\";\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\n\nimport {\n iTutorialPrevStep,\n iTutorialNextStep,\n ITutorial,\n iTutorialSteps,\n iTutorialEnd,\n} from \"../../InteractiveTutorial\";\n\ninterface IContent {\n content: React.ReactElement;\n canNext: boolean;\n}\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n textfield: {\n borderBottom: \"1px solid \" + theme.palette.primary.main,\n },\n code: {\n whiteSpace: \"pre\",\n backgroundColor: theme.palette.background.paper,\n },\n }),\n);\n\nexport function InteractiveTutorialRoot(): React.ReactElement {\n const classes = useStyles();\n\n const contents: { [number: string]: IContent | undefined } = {\n [iTutorialSteps.Start as number]: {\n content: (\n <>\n \n Welcome to Bitburner, a cyberpunk-themed incremental RPG! The game takes place in a dark, dystopian\n future... The year is 2077...\n
\n
\n This tutorial will show you the basics of the game. You may skip the tutorial at any time.\n
\n \n ),\n canNext: true,\n },\n [iTutorialSteps.GoToCharacterPage as number]: {\n content: (\n <>\n Let's start by heading to the Stats page. Click\n \n \n Stats\n \n\n on the main navigation menu (left-hand side of the screen)\n \n ),\n canNext: false,\n },\n [iTutorialSteps.CharacterPage as number]: {\n content: (\n <>\n \n \n Stats\n \n \n shows a lot of important information about your progress, such as your skills, money, and bonuses.\n \n \n ),\n canNext: true,\n },\n [iTutorialSteps.CharacterGoToTerminalPage as number]: {\n content: (\n <>\n Let's head to your computer's terminal by clicking\n \n \n Terminal\n \n on the main navigation menu.\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalIntro as number]: {\n content: (\n <>\n \n \n Terminal\n \n \n is used to interface with your home computer as well as all of the other machines around the world.\n \n \n ),\n canNext: true,\n },\n [iTutorialSteps.TerminalHelp as number]: {\n content: (\n <>\n Let's try it out. Start by entering\n {\"[home ~/]> help\"}\n (Don't forget to press Enter after typing the command)\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalLs as number]: {\n content: (\n <>\n {\"[home ~/]> help\"}\n \n displays a list of all available Terminal commands, how to use them, and a description of what they do.{\" \"}\n
\n
\n Let's try another command. Enter\n
\n\n {\"[home ~/]> ls\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalScan as number]: {\n content: (\n <>\n {\"[home ~/]> ls\"}\n \n {\" \"}\n is a basic command that shows files on the computer. Right now, it shows that you have a program called{\" \"}\n NUKE.exe on your computer. We'll get to what this does later.
\n
\n Using your home computer's terminal, you can connect to other machines throughout the world. Let's do that\n now by first entering\n
\n {\"[home ~/]> scan\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalScanAnalyze1 as number]: {\n content: (\n <>\n {\"[home ~/]> scan\"}\n \n shows all available network connections. In other words, it displays a list of all servers that can be\n connected to from your current machine. A server is identified by its hostname.
\n
\n That's great and all, but there's so many servers. Which one should you go to?{\" \"}\n
\n\n {\"[home ~/]> scan-analyze\"}\n gives some more detailed information about servers on the network. Try it now!\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalScanAnalyze2 as number]: {\n content: (\n <>\n {\"[home ~/]> scan-analyze\"}\n \n shows more detailed information about each server that you can connect to (servers that are a distance of\n one node away).
\n
It is also possible to run scan-analyze with a higher depth. Let's try a depth of two with the\n following command:{\" \"}\n
\n\n {\"[home ~/]> scan-analyze 2\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalConnect as number]: {\n content: (\n <>\n \n Now you can see information about all servers that are up to two nodes away, as well as figure out how to\n navigate to those servers through the network. You can only connect to a server that is one node away. To\n connect to a machine, use\n \n {\"[home ~/]> connect hostname\"}\n\n From the results of \n {\"[home ~/]> scan-analyze 2\"}\n\n \n {\" \"}\n we can see that the n00dles server is only one node away. Let's connect so it now using:\n \n\n {\"[home ~/]> connect n00dles\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalAnalyze as number]: {\n content: (\n <>\n \n You are now connected to another machine! What can you do now? You can hack it!\n
\n
In the year 2077, currency has become digital and decentralized. People and corporations store their\n money on servers and computers. Using your hacking abilities, you can hack servers to steal money and gain\n experience.
\n
\n Before you try to hack a server, you should run diagnostics using{\" \"}\n
\n {\"[n00dles ~/]> analyze\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalNuke as number]: {\n content: (\n <>\n When \n {\"[n00dles ~/]> analyze\"}\n\n \n finishes running it will show useful information about hacking the server.
\n
For this server, the required hacking skill is only 1, which means you can hack it right now.\n However, in order to hack a server you must first gain root access. The NUKE.exe program that we saw earlier\n on your home computer is a virus that will grant you root access to a machine if there are enough open\n ports.\n
\n {\"[n00dles ~/]> analyze\"}\n\n \n {\" \"}\n shows that there do not need to be any open ports on this machine for the NUKE virus to work, so go ahead\n and run the virus using{\" \"}\n \n {\"[n00dles ~/]> run NUKE.exe\"}\n\n \n \n ),\n canNext: true,\n },\n [iTutorialSteps.TerminalManualHack as number]: {\n content: (\n <>\n You now have root access! You can hack the server using \n {\"[home ~/]> hack\"}\n\n Try doing that now.\n \n ),\n canNext: true,\n },\n [iTutorialSteps.TerminalHackingMechanics as number]: {\n content: (\n \n You are now attempting to hack the server. Performing a hack takes time and only has a certain percentage\n chance of success. This time and success chance is determined by a variety of factors, including your hacking\n skill and the server's security level.\n
\n
\n If your attempt to hack the server is successful, you will steal a certain percentage of the server's total\n money. This percentage is affected by your hacking skill and the server's security level.\n
\n
\n The amount of money on a server is not limitless. So, if you constantly hack a server and deplete its money,\n then you will encounter diminishing returns in your hacking.\n
\n ),\n canNext: true,\n },\n [iTutorialSteps.TerminalGoHome as number]: {\n content: (\n <>\n From any server you can get back home using\n {\"[home ~/]> home\"}\n\n Let's head home before creating our first script!\n \n ),\n canNext: true,\n },\n [iTutorialSteps.TerminalCreateScript as number]: {\n content: (\n <>\n \n Hacking is the core mechanic of the game and is necessary for progressing. However, you don't want to be\n hacking manually the entire time. You can automate your hacking by writing scripts!\n
\n
\n To create a new script or edit an existing one, you can use{\" \"}\n
\n {\"[home ~/]> nano\"}\n\n Scripts must end with the .script extension. Let's make a script now by entering \n {\"[home ~/]> nano n00dles.script\"}\n\n \n after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)\n \n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalTypeScript as number]: {\n content: (\n <>\n \n This is the script editor. You can use it to program your scripts. Scripts are written in a simplified\n version of javascript. Copy and paste the following code into the script editor:
\n
\n\n \n \n \n \n For anyone with basic programming experience, this code should be straightforward. This script will\n continuously hack the n00dles server.\n
\n
\n To save and close the script editor, press the button in the bottom left, or press ctrl + b.\n
\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalFree as number]: {\n content: (\n <>\n \n Now we'll run the script. Scripts require a certain amount of RAM to run, and can be run on any machine\n which you have root access to. Different servers have different amounts of RAM. You can also purchase more\n RAM for your home server.\n
\n
\n To check how much RAM is available on this machine, enter\n
\n {\"[home ~/]> free\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalRunScript as number]: {\n content: (\n <>\n \n We have 8GB of free RAM on this machine, which is enough to run our script. Let's run our script using\n \n {\"[home ~/]> run n00dles.script\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalGoToActiveScriptsPage as number]: {\n content: (\n <>\n \n Your script is now running! It will continuously run in the background and will automatically stop if the\n code ever completes (the n00dles.script will never complete because it runs an infinite loop).
\n
\n These scripts can passively earn you income and hacking experience. Your scripts will also earn money and\n experience while you are offline, although at a slightly slower rate.
\n
\n Let's check out some statistics for our running scripts by clicking{\" \"}\n
\n \n \n Active Scripts\n \n \n ),\n canNext: false,\n },\n [iTutorialSteps.ActiveScriptsPage as number]: {\n content: (\n <>\n \n This page displays information about all of your scripts that are running across every server. You can use\n this to gauge how well your scripts are doing. Let's go back to\n \n \n \n Terminal\n \n \n ),\n canNext: false,\n },\n [iTutorialSteps.ActiveScriptsToTerminal as number]: {\n content: (\n <>\n \n One last thing about scripts, each active script contains logs that detail what it's doing. We can check\n these logs using the tail command. Do that now for the script we just ran by typing{\" \"}\n \n {\"[home ~/]> tail n00dles.script\"}\n \n ),\n canNext: false,\n },\n [iTutorialSteps.TerminalTailScript as number]: {\n content: (\n <>\n \n The log for this script won't show much right now (it might show nothing at all) because it just started\n running...but check back again in a few minutes!
\n
\n This covers the basics of hacking. To learn more about writing scripts, select\n
\n \n \n Tutorial\n \n \n in the main navigation menu to look at the documentation. If you are an experienced JavaScript developer, I\n would highly suggest you check out the section on NetscriptJS/Netscript 2.0, it's faster and more powerful.\n
\n
\n For now, let's move on to something else!\n
\n \n ),\n canNext: true,\n },\n [iTutorialSteps.GoToHacknetNodesPage as number]: {\n content: (\n <>\n \n Hacking is not the only way to earn money. One other way to passively earn money is by purchasing and\n upgrading Hacknet Nodes. Let's go to\n \n \n \n Hacknet\n \n through the main navigation menu now.\n \n ),\n canNext: true,\n },\n [iTutorialSteps.HacknetNodesIntroduction as number]: {\n content: (\n \n here you can purchase new Hacknet Nodes and upgrade your existing ones. Let's purchase a new one now.\n \n ),\n canNext: true,\n },\n [iTutorialSteps.HacknetNodesGoToWorldPage as number]: {\n content: (\n <>\n \n You just purchased a Hacknet Node! This Hacknet Node will passively earn you money over time, both online\n and offline. When you get enough money, you can upgrade your newly-purchased Hacknet Node below.\n
\n
\n Let's go to\n
\n \n \n City\n \n \n ),\n canNext: true,\n },\n [iTutorialSteps.WorldDescription as number]: {\n content: (\n <>\n \n This page lists all of the different locations you can currently travel to. Each location has something that\n you can do. There's a lot of content out in the world, make sure you explore and discover!\n
\n
\n Lastly, click on\n
\n \n \n Tutorial\n \n \n ),\n canNext: true,\n },\n [iTutorialSteps.TutorialPageInfo as number]: {\n content: (\n \n This page contains a lot of different documentation about the game's content and mechanics. I know it's a lot,\n but I highly suggest you read (or at least skim) through this before you start playing . That's the end of the\n tutorial. Hope you enjoy the game!\n \n ),\n canNext: true,\n },\n [iTutorialSteps.End as number]: {\n content: ,\n canNext: true,\n },\n };\n\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n return ITutorialEvents.subscribe(rerender);\n }, []);\n const step = ITutorial.currStep;\n const content = contents[step];\n if (content === undefined) throw new Error(\"error in the tutorial\");\n return (\n \n {content.content}\n {step !== iTutorialSteps.TutorialPageInfo && (\n <>\n \n \n \n {content.canNext && (\n \n \n \n )}\n \n )}\n
\n
\n \n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport Box from \"@mui/material/Box\";\nimport Collapse from \"@mui/material/Collapse\";\nimport Fab from \"@mui/material/Fab\";\nimport VisibilityOffIcon from \"@mui/icons-material/VisibilityOff\";\nimport VisibilityIcon from \"@mui/icons-material/Visibility\";\nimport { use } from \"../Context\";\nimport { Page } from \"../Router\";\n\nconst useStyles = makeStyles({\n nobackground: {\n backgroundColor: \"#0000\",\n },\n});\n\ninterface IProps {\n children: JSX.Element[] | JSX.Element | React.ReactElement[] | React.ReactElement;\n}\n\nexport function Overview({ children }: IProps): React.ReactElement {\n const [open, setOpen] = useState(true);\n const classes = useStyles();\n const router = use.Router();\n if (router.page() === Page.BitVerse || router.page() === Page.Loading) return <>;\n let icon;\n if (open) {\n icon = ;\n } else {\n icon = ;\n }\n return (\n
\n \n {children}\n \n setOpen((old) => !old)}>\n {icon}\n \n \n \n
\n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport clsx from \"clsx\";\nimport { styled, Theme, CSSObject } from \"@mui/material/styles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport MuiDrawer from \"@mui/material/Drawer\";\nimport List from \"@mui/material/List\";\nimport Divider from \"@mui/material/Divider\";\nimport ChevronLeftIcon from \"@mui/icons-material/ChevronLeft\";\nimport ChevronRightIcon from \"@mui/icons-material/ChevronRight\";\nimport ListItem from \"@mui/material/ListItem\";\nimport ListItemIcon from \"@mui/material/ListItemIcon\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport Typography from \"@mui/material/Typography\";\nimport Collapse from \"@mui/material/Collapse\";\nimport Badge from \"@mui/material/Badge\";\n\nimport ComputerIcon from \"@mui/icons-material/Computer\";\nimport LastPageIcon from \"@mui/icons-material/LastPage\"; // Terminal\nimport CreateIcon from \"@mui/icons-material/Create\"; // Create Script\nimport StorageIcon from \"@mui/icons-material/Storage\"; // Active Scripts\nimport BugReportIcon from \"@mui/icons-material/BugReport\"; // Create Program\nimport EqualizerIcon from \"@mui/icons-material/Equalizer\"; // Stats\nimport ContactsIcon from \"@mui/icons-material/Contacts\"; // Factions\nimport DoubleArrowIcon from \"@mui/icons-material/DoubleArrow\"; // Augmentations\nimport AccountTreeIcon from \"@mui/icons-material/AccountTree\"; // Hacknet\nimport PeopleAltIcon from \"@mui/icons-material/PeopleAlt\"; // Sleeves\nimport LocationCityIcon from \"@mui/icons-material/LocationCity\"; // City\nimport AirplanemodeActiveIcon from \"@mui/icons-material/AirplanemodeActive\"; // Travel\nimport WorkIcon from \"@mui/icons-material/Work\"; // Job\nimport TrendingUpIcon from \"@mui/icons-material/TrendingUp\"; // Stock Market\nimport FormatBoldIcon from \"@mui/icons-material/FormatBold\"; // Bladeburner\nimport BusinessIcon from \"@mui/icons-material/Business\"; // Corp\nimport SportsMmaIcon from \"@mui/icons-material/SportsMma\"; // Gang\nimport CheckIcon from \"@mui/icons-material/Check\"; // Milestones\nimport HelpIcon from \"@mui/icons-material/Help\"; // Tutorial\nimport SettingsIcon from \"@mui/icons-material/Settings\"; // options\nimport DeveloperBoardIcon from \"@mui/icons-material/DeveloperBoard\"; // Dev\nimport AccountBoxIcon from \"@mui/icons-material/AccountBox\";\nimport PublicIcon from \"@mui/icons-material/Public\";\nimport LiveHelpIcon from \"@mui/icons-material/LiveHelp\";\nimport ExpandLessIcon from \"@mui/icons-material/ExpandLess\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport { IRouter, Page } from \"../../ui/Router\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { CONSTANTS } from \"../../Constants\";\nimport { iTutorialSteps, iTutorialNextStep, ITutorial } from \"../../InteractiveTutorial\";\nimport { getAvailableCreatePrograms } from \"../../Programs/ProgramHelpers\";\nimport { Settings } from \"../../Settings/Settings\";\nimport { redPillFlag } from \"../../RedPill\";\n\nimport { KEY } from \"../../utils/helpers/keyCodes\";\n\nconst openedMixin = (theme: Theme): CSSObject => ({\n width: theme.spacing(31),\n transition: theme.transitions.create(\"width\", {\n easing: theme.transitions.easing.sharp,\n duration: theme.transitions.duration.enteringScreen,\n }),\n overflowX: \"hidden\",\n});\n\nconst closedMixin = (theme: Theme): CSSObject => ({\n transition: theme.transitions.create(\"width\", {\n easing: theme.transitions.easing.sharp,\n duration: theme.transitions.duration.leavingScreen,\n }),\n overflowX: \"hidden\",\n width: `calc(${theme.spacing(2)} + 1px)`,\n [theme.breakpoints.up(\"sm\")]: {\n width: `calc(${theme.spacing(7)} + 1px)`,\n },\n});\n\nconst Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== \"open\" })(({ theme, open }) => ({\n width: theme.spacing(31),\n whiteSpace: \"nowrap\",\n boxSizing: \"border-box\",\n ...(open && {\n ...openedMixin(theme),\n \"& .MuiDrawer-paper\": openedMixin(theme),\n }),\n ...(!open && {\n ...closedMixin(theme),\n \"& .MuiDrawer-paper\": closedMixin(theme),\n }),\n}));\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n active: {\n borderLeft: \"3px solid \" + theme.palette.primary.main,\n },\n listitem: {},\n }),\n);\n\ninterface IProps {\n player: IPlayer;\n router: IRouter;\n page: Page;\n}\n\nexport function SidebarRoot(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n const [hackingOpen, setHackingOpen] = useState(true);\n const [characterOpen, setCharacterOpen] = useState(true);\n const [worldOpen, setWorldOpen] = useState(true);\n const [helpOpen, setHelpOpen] = useState(true);\n\n const flashTerminal =\n ITutorial.currStep === iTutorialSteps.CharacterGoToTerminalPage ||\n ITutorial.currStep === iTutorialSteps.ActiveScriptsPage;\n\n const flashStats = ITutorial.currStep === iTutorialSteps.GoToCharacterPage;\n\n const flashActiveScripts = ITutorial.currStep === iTutorialSteps.TerminalGoToActiveScriptsPage;\n\n const flashHacknet = ITutorial.currStep === iTutorialSteps.GoToHacknetNodesPage;\n\n const flashCity = ITutorial.currStep === iTutorialSteps.HacknetNodesGoToWorldPage;\n\n const flashTutorial = ITutorial.currStep === iTutorialSteps.WorldDescription;\n\n const augmentationCount = props.player.queuedAugmentations.length;\n const invitationsCount = props.player.factionInvitations.length;\n const programCount = getAvailableCreatePrograms(props.player).length;\n const canCreateProgram =\n programCount > 0 ||\n props.player.augmentations.length > 0 ||\n props.player.queuedAugmentations.length > 0 ||\n props.player.sourceFiles.length > 0;\n\n const canOpenFactions =\n props.player.factionInvitations.length > 0 ||\n props.player.factions.length > 0 ||\n props.player.augmentations.length > 0 ||\n props.player.queuedAugmentations.length > 0 ||\n props.player.sourceFiles.length > 0;\n\n const canOpenAugmentations =\n props.player.augmentations.length > 0 ||\n props.player.queuedAugmentations.length > 0 ||\n props.player.sourceFiles.length > 0;\n\n const canOpenSleeves = props.player.sleeves.length > 0;\n\n // TODO(hydroflame): these should not as any but right now the def is that it\n // can only be defined;\n const canCorporation = !!(props.player.corporation as any);\n const canGang = !!(props.player.gang as any);\n const canJob = props.player.companyName !== \"\";\n const canStockMarket = props.player.hasWseAccount;\n const canBladeburner = !!(props.player.bladeburner as any);\n\n function clickTerminal(): void {\n props.router.toTerminal();\n if (flashTerminal) iTutorialNextStep();\n }\n\n function clickCreateScripts(): void {\n props.router.toScriptEditor();\n }\n\n function clickStats(): void {\n props.router.toStats();\n if (flashStats) iTutorialNextStep();\n }\n\n function clickActiveScripts(): void {\n props.router.toActiveScripts();\n if (flashActiveScripts) iTutorialNextStep();\n }\n\n function clickCreateProgram(): void {\n props.router.toCreateProgram();\n }\n\n function clickFactions(): void {\n props.router.toFactions();\n }\n\n function clickAugmentations(): void {\n props.router.toAugmentations();\n }\n\n function clickSleeves(): void {\n props.router.toSleeves();\n }\n\n function clickHacknet(): void {\n props.router.toHacknetNodes();\n if (flashHacknet) iTutorialNextStep();\n }\n\n function clickCity(): void {\n props.router.toCity();\n if (flashCity) iTutorialNextStep();\n }\n\n function clickTravel(): void {\n props.router.toTravel();\n }\n\n function clickJob(): void {\n props.router.toJob();\n }\n\n function clickStockMarket(): void {\n props.router.toStockMarket();\n }\n\n function clickBladeburner(): void {\n props.router.toBladeburner();\n }\n\n function clickCorp(): void {\n props.router.toCorporation();\n }\n\n function clickGang(): void {\n props.router.toGang();\n }\n\n function clickTutorial(): void {\n props.router.toTutorial();\n if (flashTutorial) iTutorialNextStep();\n }\n\n function clickMilestones(): void {\n props.router.toMilestones();\n }\n function clickOptions(): void {\n props.router.toGameOptions();\n }\n\n function clickDev(): void {\n props.router.toDevMenu();\n }\n\n useEffect(() => {\n // Shortcuts to navigate through the game\n // Alt-t - Terminal\n // Alt-c - Character\n // Alt-e - Script editor\n // Alt-s - Active scripts\n // Alt-h - Hacknet Nodes\n // Alt-w - City\n // Alt-j - Job\n // Alt-r - Travel Agency of current city\n // Alt-p - Create program\n // Alt-f - Factions\n // Alt-a - Augmentations\n // Alt-u - Tutorial\n // Alt-o - Options\n function handleShortcuts(this: Document, event: KeyboardEvent): any {\n if (Settings.DisableHotkeys) return;\n if (props.player.isWorking || redPillFlag) return;\n if (event.keyCode == KEY.T && event.altKey) {\n event.preventDefault();\n clickTerminal();\n } else if (event.keyCode === KEY.C && event.altKey) {\n event.preventDefault();\n clickStats();\n } else if (event.keyCode === KEY.E && event.altKey) {\n event.preventDefault();\n clickCreateScripts();\n } else if (event.keyCode === KEY.S && event.altKey) {\n event.preventDefault();\n clickActiveScripts();\n } else if (event.keyCode === KEY.H && event.altKey) {\n event.preventDefault();\n clickHacknet();\n } else if (event.keyCode === KEY.W && event.altKey) {\n event.preventDefault();\n clickCity();\n } else if (event.keyCode === KEY.J && event.altKey) {\n event.preventDefault();\n clickJob();\n } else if (event.keyCode === KEY.R && event.altKey) {\n event.preventDefault();\n clickTravel();\n } else if (event.keyCode === KEY.P && event.altKey) {\n event.preventDefault();\n clickCreateProgram();\n } else if (event.keyCode === KEY.F && event.altKey) {\n if (props.page == Page.Terminal && Settings.EnableBashHotkeys) {\n return;\n }\n event.preventDefault();\n clickFactions();\n } else if (event.keyCode === KEY.A && event.altKey) {\n event.preventDefault();\n clickAugmentations();\n } else if (event.keyCode === KEY.U && event.altKey) {\n event.preventDefault();\n clickTutorial();\n } else if (event.keyCode === KEY.B && event.altKey) {\n event.preventDefault();\n clickBladeburner();\n } else if (event.keyCode === KEY.G && event.altKey) {\n event.preventDefault();\n clickGang();\n }\n // if (event.keyCode === KEY.O && event.altKey) {\n // event.preventDefault();\n // gameOptionsBoxOpen();\n // }\n }\n\n document.addEventListener(\"keypress\", handleShortcuts);\n return () => document.removeEventListener(\"keypress\", handleShortcuts);\n }, []);\n\n const classes = useStyles();\n const [open, setOpen] = useState(true);\n const toggleDrawer = (): void => setOpen((old) => !old);\n return (\n \n \n \n {!open ? : }\n \n Bitburner v{CONSTANTS.Version}} />\n \n \n \n setHackingOpen((old) => !old)}>\n \n \n \n Hacking} />\n {hackingOpen ? : }\n \n \n \n \n \n \n \n \n \n Terminal\n \n \n \n \n \n \n \n \n \n Create Script\n \n \n \n \n \n \n \n \n \n Active Scripts\n \n \n \n {canCreateProgram && (\n \n \n 0 ? programCount : undefined} color=\"error\">\n \n \n \n \n \n Create Program\n \n \n \n )}\n \n \n\n \n setCharacterOpen((old) => !old)}>\n \n \n \n Character} />\n {characterOpen ? : }\n \n \n \n \n \n \n \n \n Stats\n \n \n \n {canOpenFactions && (\n \n \n \n \n \n \n \n \n Factions\n \n \n \n )}\n {canOpenAugmentations && (\n \n \n \n \n \n \n \n \n Augmentations\n \n \n \n )}\n \n \n \n \n \n \n Hacknet\n \n \n \n {canOpenSleeves && (\n \n \n \n \n \n Sleeves\n \n \n )}\n \n\n \n setWorldOpen((old) => !old)}>\n \n \n \n World} />\n {worldOpen ? : }\n \n \n \n \n \n \n \n \n City\n \n \n \n \n \n \n \n \n Travel\n \n \n {canJob && (\n \n \n \n \n \n Job\n \n \n )}\n {canStockMarket && (\n \n \n \n \n \n Stock Market\n \n \n )}\n {canBladeburner && (\n \n \n \n \n \n Bladeburner\n \n \n )}\n {canCorporation && (\n \n \n \n \n \n Corp\n \n \n )}\n {canGang && (\n \n \n \n \n \n Gang\n \n \n )}\n \n\n \n setHelpOpen((old) => !old)}>\n \n \n \n Help} />\n {helpOpen ? : }\n \n \n \n \n \n \n \n Milestones\n \n \n \n \n \n \n \n \n Tutorial\n \n \n \n \n \n \n \n \n Options\n \n \n {process.env.NODE_ENV === \"development\" && (\n \n \n \n \n \n Dev\n \n \n )}\n \n \n \n );\n}\n","/**\n * Root React component for the Augmentations UI page that display all of your\n * owned and purchased Augmentations and Source-Files.\n */\nimport React, { useState, useEffect } from \"react\";\n\nimport { InstalledAugmentations } from \"./InstalledAugmentations\";\nimport { PlayerMultipliers } from \"./PlayerMultipliers\";\nimport { PurchasedAugmentations } from \"./PurchasedAugmentations\";\nimport { SourceFiles } from \"./SourceFiles\";\n\nimport { canGetBonus } from \"../../ExportBonus\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n exportGameFn: () => void;\n installAugmentationsFn: () => void;\n}\n\nexport function AugmentationsRoot(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((o) => !o);\n }\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n function doExport(): void {\n props.exportGameFn();\n rerender();\n }\n\n function exportBonusStr(): string {\n if (canGetBonus()) return \"(+1 favor to all factions)\";\n return \"\";\n }\n\n return (\n <>\n Augmentations\n \n \n Below is a list of all Augmentations you have purchased but not yet installed. Click the button below to\n install them.\n \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
\n \n Installing Augmentations lets you start over with the perks and benefits granted by all of the Augmentations\n you have ever installed. Also, you will keep any scripts and RAM/Core upgrades on your home computer (but you\n will lose all programs besides NUKE.exe)\n \n
\n \n Purchased Augmentations\n \n \n \n \n \n \n \n \n \n \n Installed Augmentations\n \n \n List of all Augmentations that have been installed. You have gained the effects of these.\n \n \n \n \n \n \n );\n}\n","/**\n * React Component for displaying all of the player's installed Augmentations and\n * Source-Files.\n *\n * It also contains 'configuration' buttons that allow you to change how the\n * Augs/SF's are displayed\n */\nimport React, { useState } from \"react\";\n\nimport { AugmentationAccordion } from \"../../ui/React/AugmentationAccordion\";\nimport { Augmentations } from \"../../Augmentation/Augmentations\";\nimport { AugmentationNames } from \"../../Augmentation/data/AugmentationNames\";\n\nimport { Settings } from \"../../Settings/Settings\";\nimport { use } from \"../../ui/Context\";\nimport { OwnedAugmentationsOrderSetting } from \"../../Settings/SettingEnums\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport List from \"@mui/material/List\";\n\nexport function InstalledAugmentations(): React.ReactElement {\n const setRerender = useState(true)[1];\n const player = use.Player();\n\n const sourceAugs = player.augmentations.slice();\n\n if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {\n sourceAugs.sort((aug1, aug2) => {\n return aug1.name <= aug2.name ? -1 : 1;\n });\n }\n\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n function sortByAcquirementTime(): void {\n Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime;\n rerender();\n }\n\n function sortInOrder(): void {\n Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically;\n rerender();\n }\n\n return (\n <>\n \n \n \n \n \n \n \n {sourceAugs.map((e) => {\n const aug = Augmentations[e.name];\n\n let level = null;\n if (e.name === AugmentationNames.NeuroFluxGovernor) {\n level = e.level;\n }\n\n return ;\n })}\n \n \n );\n}\n","/**\n * React component for displaying the player's multipliers on the Augmentation UI page\n */\nimport * as React from \"react\";\n\nimport { Player } from \"../../Player\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Augmentations } from \"../Augmentations\";\nimport { Table, TableCell } from \"../../ui/React/Table\";\nimport TableBody from \"@mui/material/TableBody\";\nimport TableRow from \"@mui/material/TableRow\";\nimport Typography from \"@mui/material/Typography\";\nimport Box from \"@mui/material/Box\";\n\nfunction calculateAugmentedStats(): any {\n const augP: any = {};\n for (const aug of Player.queuedAugmentations) {\n const augObj = Augmentations[aug.name];\n for (const mult in augObj.mults) {\n const v = augP[mult] ? augP[mult] : 1;\n augP[mult] = v * augObj.mults[mult];\n }\n }\n return augP;\n}\n\nfunction Improvements({ r }: { r: number }): React.ReactElement {\n if (r) {\n return (\n <>\n \n  {\"=>\"} \n \n \n {numeralWrapper.formatPercentage(r)}\n \n \n );\n }\n return <>;\n}\n\nfunction MultiplierTable({ rows }: { rows: [string, number, number][] }): React.ReactElement {\n return (\n \n \n {rows.map((r: any) => (\n \n \n {r[0]} multiplier: \n \n \n {numeralWrapper.formatPercentage(r[1])}\n \n \n \n ))}\n \n
\n );\n}\n\nexport function PlayerMultipliers(): React.ReactElement {\n const mults = calculateAugmentedStats();\n\n function BladeburnerMults(): React.ReactElement {\n if (!Player.canAccessBladeburner()) return <>;\n return (\n <>\n \n
\n \n );\n }\n\n return (\n <>\n Multipliers\n \n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n\n \n
\n \n );\n}\n","/**\n * React component for displaying all of the player's purchased (but not installed)\n * Augmentations on the Augmentations UI.\n */\nimport * as React from \"react\";\n\nimport { Augmentations } from \"../../Augmentation/Augmentations\";\nimport { AugmentationNames } from \"../../Augmentation/data/AugmentationNames\";\nimport { Player } from \"../../Player\";\n\nimport { AugmentationAccordion } from \"../../ui/React/AugmentationAccordion\";\nimport List from \"@mui/material/List\";\n\nexport function PurchasedAugmentations(): React.ReactElement {\n const augs: React.ReactElement[] = [];\n // Only render the last NeuroFlux (there are no findLastIndex btw)\n let nfgIndex = -1;\n for (let i = Player.queuedAugmentations.length - 1; i >= 0; i--) {\n if (Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor) {\n nfgIndex = i;\n break;\n }\n }\n for (let i = 0; i < Player.queuedAugmentations.length; i++) {\n const ownedAug = Player.queuedAugmentations[i];\n if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;\n const aug = Augmentations[ownedAug.name];\n let level = null;\n if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {\n level = ownedAug.level;\n }\n\n augs.push();\n }\n\n return {augs};\n}\n","import React from \"react\";\nimport { SourceFileMinus1 } from \"./SourceFileMinus1\";\nimport { OwnedSourceFiles } from \"./OwnedSourceFiles\";\nimport List from \"@mui/material/List\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Box from \"@mui/material/Box\";\n\nexport function SourceFiles(): React.ReactElement {\n return (\n <>\n Source Files\n \n \n \n \n \n \n \n );\n}\n","/**\n * React Component for displaying a list of the player's Source-Files\n * on the Augmentations UI\n */\nimport React, { useState } from \"react\";\n\nimport { Player } from \"../../Player\";\nimport { Exploit, ExploitName } from \"../../Exploits/Exploit\";\n\nimport ListItemButton from \"@mui/material/ListItemButton\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\nimport Collapse from \"@mui/material/Collapse\";\nimport ExpandMore from \"@mui/icons-material/ExpandMore\";\nimport ExpandLess from \"@mui/icons-material/ExpandLess\";\n\nexport function SourceFileMinus1(): React.ReactElement {\n const [open, setOpen] = useState(false);\n const exploits = Player.exploits;\n\n if (exploits.length === 0) {\n return <>;\n }\n\n return (\n \n setOpen((old) => !old)}>\n \n Source-File -1: Exploits in the BitNodes\n
\n Level {exploits.length} / ?\n \n }\n />\n {open ? : }\n
\n \n \n \n This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem.\n \n It increases all of the player's multipliers by 0.1%\n
\n\n You have found the following exploits:\n \n {exploits.map((c: Exploit) => (\n * {ExploitName(c)}\n ))}\n \n
\n
\n
\n );\n}\n","/**\n * React Component for displaying a list of the player's Source-Files\n * on the Augmentations UI\n */\nimport * as React from \"react\";\n\nimport { Player } from \"../../Player\";\nimport { Settings } from \"../../Settings/Settings\";\nimport { OwnedAugmentationsOrderSetting } from \"../../Settings/SettingEnums\";\nimport { SourceFiles } from \"../../SourceFile/SourceFiles\";\n\nimport { SourceFileAccordion } from \"../../ui/React/SourceFileAccordion\";\n\nexport function OwnedSourceFiles(): React.ReactElement {\n const sourceSfs = Player.sourceFiles.slice();\n\n if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) {\n sourceSfs.sort((sf1, sf2) => {\n return sf1.n - sf2.n;\n });\n }\n\n return (\n <>\n {sourceSfs.map((e) => {\n const srcFileKey = \"SourceFile\" + e.n;\n const sfObj = SourceFiles[srcFileKey];\n if (sfObj == null) {\n console.error(`Invalid source file number: ${e.n}`);\n return null;\n }\n\n return ;\n })}\n \n );\n}\n","/**\n * React Component for displaying a single Source-File as an accordion.\n *\n * The header of the accordion contains the Source-Files's name and level,\n * and the accordion's panel contains the Source-File's description.\n */\nimport React, { useState } from \"react\";\n\nimport { SourceFile } from \"../../SourceFile/SourceFile\";\n\nimport ListItemButton from \"@mui/material/ListItemButton\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\nimport Collapse from \"@mui/material/Collapse\";\nimport ExpandMore from \"@mui/icons-material/ExpandMore\";\nimport ExpandLess from \"@mui/icons-material/ExpandLess\";\n\ntype IProps = {\n level: number;\n sf: SourceFile;\n};\n\nexport function SourceFileAccordion(props: IProps): React.ReactElement {\n const [open, setOpen] = useState(false);\n const maxLevel = props.sf.n === 12 ? \"∞\" : \"3\";\n\n return (\n \n setOpen((old) => !old)}>\n \n {props.sf.name}\n
\n {`Level ${props.level} / ${maxLevel}`}\n \n }\n />\n {open ? : }\n
\n \n \n {props.sf.info}\n \n \n
\n );\n}\n","import { IPlayer } from \"./PersonObjects/IPlayer\";\nimport { Bladeburner } from \"./Bladeburner/Bladeburner\";\nimport { IEngine } from \"./IEngine\";\nimport { IRouter } from \"./ui/Router\";\n\nimport React from \"react\";\n\nimport { General } from \"./DevMenu/ui/General\";\nimport { Stats } from \"./DevMenu/ui/Stats\";\nimport { Factions } from \"./DevMenu/ui/Factions\";\nimport { Augmentations } from \"./DevMenu/ui/Augmentations\";\nimport { SourceFiles } from \"./DevMenu/ui/SourceFiles\";\nimport { Programs } from \"./DevMenu/ui/Programs\";\nimport { Servers } from \"./DevMenu/ui/Servers\";\nimport { Companies } from \"./DevMenu/ui/Companies\";\nimport { Bladeburner as BladeburnerElem } from \"./DevMenu/ui/Bladeburner\";\nimport { Gang } from \"./DevMenu/ui/Gang\";\nimport { Corporation } from \"./DevMenu/ui/Corporation\";\nimport { CodingContracts } from \"./DevMenu/ui/CodingContracts\";\nimport { StockMarket } from \"./DevMenu/ui/StockMarket\";\nimport { Sleeves } from \"./DevMenu/ui/Sleeves\";\nimport { TimeSkip } from \"./DevMenu/ui/TimeSkip\";\n\ninterface IProps {\n player: IPlayer;\n engine: IEngine;\n router: IRouter;\n}\n\nexport function DevMenuRoot(props: IProps): React.ReactElement {\n return (\n <>\n

Development Menu - Only meant to be used for testing/debugging

\n \n \n \n \n \n \n \n \n\n {props.player.bladeburner instanceof Bladeburner && }\n\n {props.player.inGang() && }\n\n {props.player.hasCorporation() && }\n\n \n\n {props.player.hasWseAccount && }\n\n {props.player.sleeves.length > 0 && }\n\n \n \n );\n}\n","import React from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Button from \"@mui/material/Button\";\nimport { Money } from \"../../ui/React/Money\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { IRouter } from \"../../ui/Router\";\n\ninterface IProps {\n player: IPlayer;\n router: IRouter;\n}\n\nexport function General(props: IProps): React.ReactElement {\n function addMoney(n: number) {\n return function () {\n props.player.gainMoney(n);\n };\n }\n\n function upgradeRam(): void {\n props.player.getHomeComputer().maxRam *= 2;\n }\n\n function quickB1tFlum3(): void {\n props.router.toBitVerse(true, true);\n }\n\n function b1tflum3(): void {\n props.router.toBitVerse(true, false);\n }\n\n function quickHackW0r1dD43m0n(): void {\n props.router.toBitVerse(false, true);\n }\n\n function hackW0r1dD43m0n(): void {\n props.router.toBitVerse(false, false);\n }\n\n return (\n \n }>\n

General

\n
\n \n \n \n \n \n \n \n
\n\n \n \n \n \n
\n
\n );\n}\n","import React from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport { Adjuster } from \"./Adjuster\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nconst bigNumber = 1e27;\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Stats(props: IProps): React.ReactElement {\n function modifyExp(stat: string, modifier: number) {\n return function (exp: number) {\n switch (stat) {\n case \"hacking\":\n if (exp) {\n props.player.gainHackingExp(exp * modifier);\n }\n break;\n case \"strength\":\n if (exp) {\n props.player.gainStrengthExp(exp * modifier);\n }\n break;\n case \"defense\":\n if (exp) {\n props.player.gainDefenseExp(exp * modifier);\n }\n break;\n case \"dexterity\":\n if (exp) {\n props.player.gainDexterityExp(exp * modifier);\n }\n break;\n case \"agility\":\n if (exp) {\n props.player.gainAgilityExp(exp * modifier);\n }\n break;\n case \"charisma\":\n if (exp) {\n props.player.gainCharismaExp(exp * modifier);\n }\n break;\n case \"intelligence\":\n if (exp) {\n props.player.gainIntelligenceExp(exp * modifier);\n }\n break;\n }\n props.player.updateSkillLevels();\n };\n }\n\n function modifyKarma(modifier: number) {\n return function (amt: number) {\n props.player.karma += amt * modifier;\n };\n }\n\n function tonsOfExp(): void {\n props.player.gainHackingExp(bigNumber);\n props.player.gainStrengthExp(bigNumber);\n props.player.gainDefenseExp(bigNumber);\n props.player.gainDexterityExp(bigNumber);\n props.player.gainAgilityExp(bigNumber);\n props.player.gainCharismaExp(bigNumber);\n props.player.gainIntelligenceExp(bigNumber);\n props.player.updateSkillLevels();\n }\n\n function resetAllExp(): void {\n props.player.hacking_exp = 0;\n props.player.strength_exp = 0;\n props.player.defense_exp = 0;\n props.player.dexterity_exp = 0;\n props.player.agility_exp = 0;\n props.player.charisma_exp = 0;\n props.player.intelligence_exp = 0;\n props.player.updateSkillLevels();\n }\n\n function resetExperience(stat: string): () => void {\n return function () {\n switch (stat) {\n case \"hacking\":\n props.player.hacking_exp = 0;\n break;\n case \"strength\":\n props.player.strength_exp = 0;\n break;\n case \"defense\":\n props.player.defense_exp = 0;\n break;\n case \"dexterity\":\n props.player.dexterity_exp = 0;\n break;\n case \"agility\":\n props.player.agility_exp = 0;\n break;\n case \"charisma\":\n props.player.charisma_exp = 0;\n break;\n case \"intelligence\":\n props.player.intelligence_exp = 0;\n break;\n }\n props.player.updateSkillLevels();\n };\n }\n\n function resetKarma(): () => void {\n return function () {\n props.player.karma = 0;\n };\n }\n\n function enableIntelligence(): void {\n if (props.player.intelligence === 0) {\n props.player.intelligence = 1;\n props.player.updateSkillLevels();\n }\n }\n\n function disableIntelligence(): void {\n props.player.intelligence_exp = 0;\n props.player.intelligence = 0;\n props.player.updateSkillLevels();\n }\n\n return (\n \n }>\n

Experience / Stats

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n All:\n \n \n \n
\n Hacking:\n \n modifyExp(\"hacking\", 1)(bigNumber)}\n add={modifyExp(\"hacking\", 1)}\n subtract={modifyExp(\"hacking\", -1)}\n reset={resetExperience(\"hacking\")}\n />\n
\n Strength:\n \n modifyExp(\"strength\", 1)(bigNumber)}\n add={modifyExp(\"strength\", 1)}\n subtract={modifyExp(\"strength\", -1)}\n reset={resetExperience(\"strength\")}\n />\n
\n Defense:\n \n modifyExp(\"defense\", 1)(bigNumber)}\n add={modifyExp(\"defense\", 1)}\n subtract={modifyExp(\"defense\", -1)}\n reset={resetExperience(\"defense\")}\n />\n
\n Dexterity:\n \n modifyExp(\"dexterity\", 1)(bigNumber)}\n add={modifyExp(\"dexterity\", 1)}\n subtract={modifyExp(\"dexterity\", -1)}\n reset={resetExperience(\"dexterity\")}\n />\n
\n Agility:\n \n modifyExp(\"agility\", 1)(bigNumber)}\n add={modifyExp(\"agility\", 1)}\n subtract={modifyExp(\"agility\", -1)}\n reset={resetExperience(\"agility\")}\n />\n
\n Charisma:\n \n modifyExp(\"charisma\", 1)(bigNumber)}\n add={modifyExp(\"charisma\", 1)}\n subtract={modifyExp(\"charisma\", -1)}\n reset={resetExperience(\"charisma\")}\n />\n
\n Intelligence:\n \n modifyExp(\"intelligence\", 1)(bigNumber)}\n add={modifyExp(\"intelligence\", 1)}\n subtract={modifyExp(\"intelligence\", -1)}\n reset={resetExperience(\"intelligence\")}\n />\n \n \n \n \n
\n Karma:\n \n modifyExp(\"intelligence\", 1)(100000)}\n add={modifyKarma(1)}\n subtract={modifyKarma(-1)}\n reset={resetKarma()}\n />\n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport { Adjuster } from \"./Adjuster\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { Factions as AllFaction } from \"../../Faction/Factions\";\nimport FormControl from \"@mui/material/FormControl\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ReplyAllIcon from \"@mui/icons-material/ReplyAll\";\nimport ReplyIcon from \"@mui/icons-material/Reply\";\nimport InputLabel from \"@mui/material/InputLabel\";\n\nconst bigNumber = 1e12;\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Factions(props: IProps): React.ReactElement {\n const [faction, setFaction] = useState(\"Illuminati\");\n\n function setFactionDropdown(event: SelectChangeEvent): void {\n setFaction(event.target.value as string);\n }\n\n function receiveInvite(): void {\n props.player.receiveInvite(faction);\n }\n\n function receiveAllInvites(): void {\n for (const i in AllFaction) {\n props.player.receiveInvite(AllFaction[i].name);\n }\n }\n\n function modifyFactionRep(modifier: number): (x: number) => void {\n return function (reputation: number): void {\n const fac = AllFaction[faction];\n if (fac != null && !isNaN(reputation)) {\n fac.playerReputation += reputation * modifier;\n }\n };\n }\n\n function resetFactionRep(): void {\n const fac = AllFaction[faction];\n if (fac != null) {\n fac.playerReputation = 0;\n }\n }\n\n function modifyFactionFavor(modifier: number): (x: number) => void {\n return function (favor: number): void {\n const fac = AllFaction[faction];\n if (fac != null && !isNaN(favor)) {\n fac.favor += favor * modifier;\n }\n };\n }\n\n function resetFactionFavor(): void {\n const fac = AllFaction[faction];\n if (fac != null) {\n fac.favor = 0;\n }\n }\n\n function tonsOfRep(): void {\n for (const i in AllFaction) {\n AllFaction[i].playerReputation = bigNumber;\n }\n }\n\n function resetAllRep(): void {\n for (const i in AllFaction) {\n AllFaction[i].playerReputation = 0;\n }\n }\n\n function tonsOfFactionFavor(): void {\n for (const i in AllFaction) {\n AllFaction[i].favor = bigNumber;\n }\n }\n\n function resetAllFactionFavor(): void {\n for (const i in AllFaction) {\n AllFaction[i].favor = 0;\n }\n }\n\n return (\n \n }>\n

Factions

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Faction:\n \n \n Faction\n \n \n \n \n \n \n \n \n }\n >\n {Object.values(AllFaction).map((faction) => (\n \n {faction.name}\n \n ))}\n \n \n
\n Reputation:\n \n modifyFactionRep(1)(bigNumber)}\n add={modifyFactionRep(1)}\n subtract={modifyFactionRep(-1)}\n reset={resetFactionRep}\n />\n
\n Favor:\n \n modifyFactionFavor(1)(2000)}\n add={modifyFactionFavor(1)}\n subtract={modifyFactionFavor(-1)}\n reset={resetFactionFavor}\n />\n
\n All Reputation:\n \n \n \n
\n All Favor:\n \n \n \n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { AugmentationNames } from \"../../Augmentation/data/AugmentationNames\";\nimport Typography from \"@mui/material/Typography\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ReplyAllIcon from \"@mui/icons-material/ReplyAll\";\nimport ReplyIcon from \"@mui/icons-material/Reply\";\nimport ClearIcon from \"@mui/icons-material/Clear\";\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Augmentations(props: IProps): React.ReactElement {\n const [augmentation, setAugmentation] = useState(\"Augmented Targeting I\");\n\n function setAugmentationDropdown(event: SelectChangeEvent): void {\n setAugmentation(event.target.value as string);\n }\n function queueAug(): void {\n props.player.queueAugmentation(augmentation);\n }\n\n function queueAllAugs(): void {\n for (const i in AugmentationNames) {\n const augName = AugmentationNames[i];\n props.player.queueAugmentation(augName);\n }\n }\n\n function clearAugs(): void {\n props.player.augmentations = [];\n }\n\n return (\n \n }>\n

Augmentations

\n
\n \n \n \n \n \n \n \n \n
\n Aug:\n \n \n \n \n \n \n \n \n \n }\n endAdornment={\n <>\n \n \n \n \n }\n >\n {Object.values(AugmentationNames).map((aug) => (\n \n {aug}\n \n ))}\n \n
\n
\n
\n );\n}\n","import React from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport { PlayerOwnedSourceFile } from \"../../SourceFile/PlayerOwnedSourceFile\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport ButtonGroup from \"@mui/material/ButtonGroup\";\n\n// Update as additional BitNodes get implemented\nconst validSFN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function SourceFiles(props: IProps): React.ReactElement {\n function setSF(sfN: number, sfLvl: number) {\n return function () {\n if (sfLvl === 0) {\n props.player.sourceFiles = props.player.sourceFiles.filter((sf) => sf.n !== sfN);\n return;\n }\n\n if (!props.player.sourceFiles.some((sf) => sf.n === sfN)) {\n props.player.sourceFiles.push(new PlayerOwnedSourceFile(sfN, sfLvl));\n return;\n }\n\n for (let i = 0; i < props.player.sourceFiles.length; i++) {\n if (props.player.sourceFiles[i].n === sfN) {\n props.player.sourceFiles[i].lvl = sfLvl;\n }\n }\n };\n }\n\n function setAllSF(sfLvl: number) {\n return () => {\n for (let i = 0; i < validSFN.length; i++) {\n setSF(validSFN[i], sfLvl)();\n }\n };\n }\n\n function clearExploits(): void {\n props.player.exploits = [];\n }\n\n return (\n \n }>\n

Source-Files

\n
\n \n \n \n \n \n \n \n \n \n \n \n {validSFN.map((i) => (\n \n \n \n \n ))}\n \n
\n Exploits:\n \n \n
\n All:\n \n \n \n \n \n \n \n
\n SF-{i}:\n \n \n \n \n \n \n \n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { Programs as AllPrograms } from \"../../Programs/Programs\";\nimport MenuItem from \"@mui/material/MenuItem\";\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Programs(props: IProps): React.ReactElement {\n const [program, setProgram] = useState(\"NUKE.exe\");\n function setProgramDropdown(event: SelectChangeEvent): void {\n setProgram(event.target.value as string);\n }\n function addProgram(): void {\n if (!props.player.hasProgram(program)) {\n props.player.getHomeComputer().programs.push(program);\n }\n }\n\n function addAllPrograms(): void {\n for (const i in AllPrograms) {\n if (!props.player.hasProgram(AllPrograms[i].name)) {\n props.player.getHomeComputer().programs.push(AllPrograms[i].name);\n }\n }\n }\n\n return (\n \n }>\n

Programs

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n
\n Program:\n \n \n
\n Add:\n \n \n \n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport { AllServers } from \"../../Server/AllServers\";\nimport { HacknetServer } from \"../../Hacknet/HacknetServer\";\nimport { GetServerByHostname } from \"../../Server/ServerHelpers\";\nimport MenuItem from \"@mui/material/MenuItem\";\n\nexport function Servers(): React.ReactElement {\n const [server, setServer] = useState(\"home\");\n function setServerDropdown(event: SelectChangeEvent): void {\n setServer(event.target.value as string);\n }\n function rootServer(): void {\n const s = GetServerByHostname(server);\n if (s === null) return;\n if (s instanceof HacknetServer) return;\n s.hasAdminRights = true;\n s.sshPortOpen = true;\n s.ftpPortOpen = true;\n s.smtpPortOpen = true;\n s.httpPortOpen = true;\n s.sqlPortOpen = true;\n s.openPortCount = 5;\n }\n\n function rootAllServers(): void {\n for (const i in AllServers) {\n const s = AllServers[i];\n if (s instanceof HacknetServer) return;\n s.hasAdminRights = true;\n s.sshPortOpen = true;\n s.ftpPortOpen = true;\n s.smtpPortOpen = true;\n s.httpPortOpen = true;\n s.sqlPortOpen = true;\n s.openPortCount = 5;\n }\n }\n\n function minSecurity(): void {\n const s = GetServerByHostname(server);\n if (s === null) return;\n if (s instanceof HacknetServer) return;\n s.hackDifficulty = s.minDifficulty;\n }\n\n function minAllSecurity(): void {\n for (const i in AllServers) {\n const server = AllServers[i];\n if (server instanceof HacknetServer) continue;\n server.hackDifficulty = server.minDifficulty;\n }\n }\n\n function maxMoney(): void {\n const s = GetServerByHostname(server);\n if (s === null) return;\n if (s instanceof HacknetServer) return;\n s.moneyAvailable = s.moneyMax;\n }\n\n function maxAllMoney(): void {\n for (const i in AllServers) {\n const server = AllServers[i];\n if (server instanceof HacknetServer) continue;\n server.moneyAvailable = server.moneyMax;\n }\n }\n\n return (\n \n }>\n

Servers

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Server:\n \n \n
\n Root:\n \n \n \n \n
\n Security:\n \n \n \n \n
\n Money:\n \n \n \n \n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport { Companies as AllCompanies } from \"../../Company/Companies\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport { Adjuster } from \"./Adjuster\";\n\nconst bigNumber = 1e12;\n\nexport function Companies(): React.ReactElement {\n const [company, setCompany] = useState(\"ECorp\");\n function setCompanyDropdown(event: SelectChangeEvent): void {\n setCompany(event.target.value as string);\n }\n function resetCompanyRep(): void {\n AllCompanies[company].playerReputation = 0;\n }\n\n function modifyCompanyRep(modifier: number): (x: number) => void {\n return function (reputation: number): void {\n const c = AllCompanies[company];\n if (c != null && !isNaN(reputation)) {\n c.playerReputation += reputation * modifier;\n }\n };\n }\n\n function modifyCompanyFavor(modifier: number): (x: number) => void {\n return function (favor: number): void {\n const c = AllCompanies[company];\n if (c != null && !isNaN(favor)) {\n c.favor += favor * modifier;\n }\n };\n }\n\n function resetCompanyFavor(): void {\n AllCompanies[company].favor = 0;\n }\n\n function tonsOfRepCompanies(): void {\n for (const c in AllCompanies) {\n AllCompanies[c].playerReputation = bigNumber;\n }\n }\n\n function resetAllRepCompanies(): void {\n for (const c in AllCompanies) {\n AllCompanies[c].playerReputation = 0;\n }\n }\n\n function tonsOfFavorCompanies(): void {\n for (const c in AllCompanies) {\n AllCompanies[c].favor = bigNumber;\n }\n }\n\n function resetAllFavorCompanies(): void {\n for (const c in AllCompanies) {\n AllCompanies[c].favor = 0;\n }\n }\n\n return (\n \n }>\n

Companies

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Company:\n \n \n
\n Reputation:\n \n modifyCompanyRep(1)(bigNumber)}\n add={modifyCompanyRep(1)}\n subtract={modifyCompanyRep(-1)}\n reset={resetCompanyRep}\n />\n
\n Favor:\n \n modifyCompanyFavor(1)(2000)}\n add={modifyCompanyFavor(1)}\n subtract={modifyCompanyFavor(-1)}\n reset={resetCompanyFavor}\n />\n
\n All Reputation:\n \n \n \n
\n All Favor:\n \n \n \n
\n
\n
\n );\n}\n","import React from \"react\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport { Adjuster } from \"./Adjuster\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nconst bigNumber = 1e27;\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Bladeburner(props: IProps): React.ReactElement {\n const bladeburner = props.player.bladeburner;\n if (bladeburner === null) return <>;\n function modifyBladeburnerRank(modify: number): (x: number) => void {\n return function (rank: number): void {\n if (!bladeburner) return;\n bladeburner.changeRank(props.player, rank * modify);\n };\n }\n\n function resetBladeburnerRank(): void {\n if (!bladeburner) return;\n bladeburner.rank = 0;\n bladeburner.maxRank = 0;\n }\n\n function addTonsBladeburnerRank(): void {\n if (!bladeburner) return;\n\n bladeburner.changeRank(props.player, bigNumber);\n }\n\n function modifyBladeburnerCycles(modify: number): (x: number) => void {\n return function (cycles: number): void {\n if (!bladeburner) return;\n bladeburner.storedCycles += cycles * modify;\n };\n }\n\n function resetBladeburnerCycles(): void {\n if (!bladeburner) return;\n bladeburner.storedCycles = 0;\n }\n\n function addTonsBladeburnerCycles(): void {\n if (!bladeburner) return;\n bladeburner.storedCycles += bigNumber;\n }\n\n return (\n \n }>\n

Bladeburner

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n
\n Rank:\n \n \n
\n Cycles:\n \n \n
\n
\n
\n );\n}\n","import React from \"react\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport { Adjuster } from \"./Adjuster\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nconst bigNumber = 1e27;\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Gang(props: IProps): React.ReactElement {\n function addTonsGangCycles(): void {\n if (props.player.gang) {\n props.player.gang.storedCycles = bigNumber;\n }\n }\n\n function modifyGangCycles(modify: number): (x: number) => void {\n return function (cycles: number): void {\n if (props.player.gang) {\n props.player.gang.storedCycles += cycles * modify;\n }\n };\n }\n\n function resetGangCycles(): void {\n if (props.player.gang) {\n props.player.gang.storedCycles = 0;\n }\n }\n\n return (\n \n }>\n

Gang

\n
\n \n \n \n \n \n \n \n \n
\n Cycles:\n \n \n
\n
\n
\n );\n}\n","import React from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport { Adjuster } from \"./Adjuster\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nconst bigNumber = 1e27;\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Corporation(props: IProps): React.ReactElement {\n function addTonsCorporationFunds(): void {\n if (props.player.corporation) {\n props.player.corporation.funds = props.player.corporation.funds.plus(1e99);\n }\n }\n\n function resetCorporationFunds(): void {\n if (props.player.corporation) {\n props.player.corporation.funds = props.player.corporation.funds.minus(props.player.corporation.funds);\n }\n }\n\n function addTonsCorporationCycles(): void {\n if (props.player.corporation) {\n props.player.corporation.storedCycles = bigNumber;\n }\n }\n\n function modifyCorporationCycles(modify: number): (x: number) => void {\n return function (cycles: number): void {\n if (props.player.corporation) {\n props.player.corporation.storedCycles += cycles * modify;\n }\n };\n }\n\n function resetCorporationCycles(): void {\n if (props.player.corporation) {\n props.player.corporation.storedCycles = 0;\n }\n }\n\n function finishCorporationProducts(): void {\n if (!props.player.corporation) return;\n props.player.corporation.divisions.forEach((div) => {\n Object.keys(div.products).forEach((prod) => {\n const product = div.products[prod];\n if (product === undefined) throw new Error(\"Impossible product undefined\");\n product.prog = 99.9;\n });\n });\n }\n\n function addCorporationResearch(): void {\n if (!props.player.corporation) return;\n props.player.corporation.divisions.forEach((div) => {\n div.sciResearch.qty += 1e10;\n });\n }\n\n return (\n \n }>\n

Corporation

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n \n
\n Cycles:\n \n \n
\n \n
\n \n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Button from \"@mui/material/Button\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport { generateContract, generateRandomContract, generateRandomContractOnHome } from \"../../CodingContractGenerator\";\nimport { CodingContractTypes } from \"../../CodingContracts\";\n\nexport function CodingContracts(): React.ReactElement {\n const [codingcontract, setCodingcontract] = useState(\"Find Largest Prime Factor\");\n function setCodingcontractDropdown(event: SelectChangeEvent): void {\n setCodingcontract(event.target.value as string);\n }\n\n function specificContract(): void {\n generateContract({\n problemType: codingcontract,\n server: \"home\",\n });\n }\n\n return (\n \n }>\n

Coding Contracts

\n
\n \n \n \n \n \n \n \n \n \n \n
\n \n \n
\n \n \n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\nimport { Money } from \"../../ui/React/Money\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { StockMarket as SM } from \"../../StockMarket/StockMarket\";\nimport { Stock } from \"../../StockMarket/Stock\";\n\nexport function StockMarket(): React.ReactElement {\n const [stockPrice, setStockPrice] = useState(0);\n const [stockSymbol, setStockSymbol] = useState(\"\");\n\n function setStockPriceField(event: React.ChangeEvent): void {\n setStockPrice(parseFloat(event.target.value));\n }\n\n function setStockSymbolField(event: React.ChangeEvent): void {\n setStockSymbol(event.target.value);\n }\n\n function processStocks(sub: (arg0: Stock) => void): void {\n const inputSymbols = stockSymbol.replace(/\\s/g, \"\");\n\n let match: (symbol: string) => boolean = (): boolean => {\n return true;\n };\n\n if (inputSymbols !== \"\" && inputSymbols !== \"all\") {\n match = function (symbol: string): boolean {\n return inputSymbols.split(\",\").includes(symbol);\n };\n }\n\n for (const name in SM) {\n if (SM.hasOwnProperty(name)) {\n const stock = SM[name];\n if (stock instanceof Stock && match(stock.symbol)) {\n sub(stock);\n }\n }\n }\n }\n\n function doSetStockPrice(): void {\n if (!isNaN(stockPrice)) {\n processStocks((stock: Stock) => {\n stock.price = stockPrice;\n });\n }\n }\n\n function viewStockCaps(): void {\n const stocks: JSX.Element[] = [];\n processStocks((stock: Stock) => {\n stocks.push(\n \n {stock.symbol}\n \n \n \n ,\n );\n });\n dialogBoxCreate(\n \n \n \n \n \n \n {stocks}\n \n
StockPrice cap
,\n );\n }\n return (\n \n }>\n

Stock Market

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Symbol:\n \n \n
\n Price:\n \n \n \n
\n Caps:\n \n \n
\n
\n
\n );\n}\n","import React from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function Sleeves(props: IProps): React.ReactElement {\n function sleeveMaxAllShock(): void {\n for (let i = 0; i < props.player.sleeves.length; ++i) {\n props.player.sleeves[i].shock = 0;\n }\n }\n\n function sleeveClearAllShock(): void {\n for (let i = 0; i < props.player.sleeves.length; ++i) {\n props.player.sleeves[i].shock = 100;\n }\n }\n\n function sleeveSyncMaxAll(): void {\n for (let i = 0; i < props.player.sleeves.length; ++i) {\n props.player.sleeves[i].sync = 100;\n }\n }\n\n function sleeveSyncClearAll(): void {\n for (let i = 0; i < props.player.sleeves.length; ++i) {\n props.player.sleeves[i].sync = 0;\n }\n }\n\n return (\n \n }>\n

Sleeves

\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n Shock:\n \n \n \n \n
\n Sync:\n \n \n \n \n
\n
\n
\n );\n}\n","import React from \"react\";\n\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\nimport Button from \"@mui/material/Button\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { saveObject } from \"../../SaveObject\";\nimport { IEngine } from \"../../IEngine\";\n\n// Update as additional BitNodes get implemented\n\ninterface IProps {\n player: IPlayer;\n engine: IEngine;\n}\n\nexport function TimeSkip(props: IProps): React.ReactElement {\n function timeskip(time: number) {\n return () => {\n props.player.lastUpdate -= time;\n props.engine._lastUpdate -= time;\n saveObject.saveGame();\n setTimeout(() => location.reload(), 1000);\n };\n }\n\n return (\n \n }>\n

Time skip

\n
\n \n \n \n \n \n
\n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport { Stats } from \"./Stats\";\nimport { Console } from \"./Console\";\nimport { AllPages } from \"./AllPages\";\n\nimport { use } from \"../../ui/Context\";\nimport Grid from \"@mui/material/Grid\";\nimport Box from \"@mui/material/Box\";\n\nexport function BladeburnerRoot(): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n const bladeburner = player.bladeburner;\n if (bladeburner === null) return <>;\n return (\n \n \n \n \n \n \n \n \n \n\n \n \n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport { formatNumber, convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { BladeburnerConstants } from \"../data/Constants\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { Money } from \"../../ui/React/Money\";\nimport { StatsTable } from \"../../ui/React/StatsTable\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Factions } from \"../../Faction/Factions\";\nimport { IRouter } from \"../../ui/Router\";\nimport { joinFaction } from \"../../Faction/FactionHelpers\";\nimport { IBladeburner } from \"../IBladeburner\";\n\nimport { TravelModal } from \"./TravelModal\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport Paper from \"@mui/material/Paper\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n router: IRouter;\n player: IPlayer;\n}\n\nexport function Stats(props: IProps): React.ReactElement {\n const [travelOpen, setTravelOpen] = useState(false);\n const setRerender = useState(false)[1];\n\n const inFaction = props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction;\n useEffect(() => {\n const id = setInterval(() => setRerender((old) => !old), 1000);\n return () => clearInterval(id);\n }, []);\n\n function openFaction(): void {\n if (!inFaction) return;\n const faction = Factions[\"Bladeburners\"];\n if (!faction.isMember) {\n joinFaction(faction);\n }\n\n props.router.toFaction(faction);\n }\n\n return (\n \n \n Your rank within the Bladeburner division.}>\n Rank: {formatNumber(props.bladeburner.rank, 2)}\n \n \n
\n \n \n Performing actions will use up your stamina.\n
\n
\n Your max stamina is determined primarily by your agility stat.\n
\n
\n Your stamina gain rate is determined by both your agility and your max stamina. Higher max stamina leads\n to a higher gain rate.\n
\n
\n Once your stamina falls below 50% of its max value, it begins to negatively affect the success rate of\n your contracts/operations. This penalty is shown in the overview panel. If the penalty is 15%, then this\n means your success rate would be multipled by 85% (100 - 15).\n
\n
\n Your max stamina and stamina gain rate can also be increased by training, or through skills and\n Augmentation upgrades.\n \n }\n >\n \n Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}\n \n \n
\n
\n \n Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%\n \n
\n Team Size: {formatNumber(props.bladeburner.teamSize, 0)}\n Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}\n
\n Num Times Hospitalized: {props.bladeburner.numHosp}\n \n Money Lost From Hospitalizations: \n \n
\n Current City: {props.bladeburner.city}\n \n \n This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An accurate\n population count increases success rate estimates.\n \n }\n >\n \n Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}\n \n \n \n
\n \n \n This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.\n \n }\n >\n \n Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}\n \n \n \n
\n \n \n The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a\n chaos level can make contracts and operations harder.\n \n }\n >\n City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}\n \n \n
\n {(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (\n <>\n \n \n You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by\n browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.\n \n }\n >\n \n Bonus time:{\" \"}\n {convertTimeMsToTimeElapsedString(\n (props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,\n )}\n \n \n \n
\n \n )}\n Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}\n
\n \n
\n \n Rank 25 required. : \"\"}>\n \n \n \n \n setTravelOpen(false)} bladeburner={props.bladeburner} />\n
\n );\n}\n","import React from \"react\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { WorldMap } from \"../../ui/React/WorldMap\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { CityName } from \"../../Locations/data/CityNames\";\nimport { Settings } from \"../../Settings/Settings\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n open: boolean;\n onClose: () => void;\n}\n\nexport function TravelModal(props: IProps): React.ReactElement {\n function travel(city: string): void {\n props.bladeburner.city = city;\n props.onClose();\n }\n\n return (\n \n <>\n \n Travel to a different city for your Bladeburner activities. This does not cost any money. The city you are in\n for your Bladeburner duties does not affect your location in the game otherwise.\n \n {Settings.DisableASCIIArt ? (\n Object.values(CityName).map((city: CityName) => (\n \n ))\n ) : (\n travel(city)} />\n )}\n \n \n );\n}\n","import React, { useState, useRef, useEffect } from \"react\";\nimport { IBladeburner } from \"../IBladeburner\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport Paper from \"@mui/material/Paper\";\nimport List from \"@mui/material/List\";\nimport ListItem from \"@mui/material/ListItem\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\n\ninterface ILineProps {\n content: any;\n}\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n textfield: {\n margin: theme.spacing(0),\n width: \"100%\",\n },\n input: {\n backgroundColor: \"#000\",\n },\n nopadding: {\n padding: theme.spacing(0),\n },\n preformatted: {\n whiteSpace: \"pre-wrap\",\n margin: theme.spacing(0),\n },\n list: {\n padding: theme.spacing(0),\n height: \"100%\",\n },\n }),\n);\n\nfunction Line(props: ILineProps): React.ReactElement {\n return (\n \n {props.content}\n \n );\n}\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function Console(props: IProps): React.ReactElement {\n const classes = useStyles();\n const scrollHook = useRef(null);\n const [command, setCommand] = useState(\"\");\n const setRerender = useState(false)[1];\n\n function handleCommandChange(event: React.ChangeEvent): void {\n setCommand(event.target.value);\n }\n\n const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);\n\n // TODO: Figure out how to actually make the scrolling work correctly.\n function scrollToBottom(): void {\n if (!scrollHook.current) return;\n scrollHook.current.scrollTop = scrollHook.current.scrollHeight;\n }\n\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 1000);\n const id2 = setInterval(scrollToBottom, 100);\n return () => {\n clearInterval(id);\n clearInterval(id2);\n };\n }, []);\n\n function handleKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) {\n event.preventDefault();\n if (command.length > 0) {\n props.bladeburner.postToConsole(\"> \" + command);\n props.bladeburner.executeConsoleCommands(props.player, command);\n setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);\n setCommand(\"\");\n }\n }\n\n const consoleHistory = props.bladeburner.consoleHistory;\n\n if (event.keyCode === 38) {\n // up\n let i = consoleHistoryIndex;\n const len = consoleHistory.length;\n if (len === 0) {\n return;\n }\n if (i < 0 || i > len) {\n setConsoleHistoryIndex(len);\n }\n\n if (i !== 0) {\n i = i - 1;\n }\n setConsoleHistoryIndex(i);\n const prevCommand = consoleHistory[i];\n event.currentTarget.value = prevCommand;\n }\n\n if (event.keyCode === 40) {\n const i = consoleHistoryIndex;\n const len = consoleHistory.length;\n\n if (len == 0) {\n return;\n }\n if (i < 0 || i > len) {\n setConsoleHistoryIndex(len);\n }\n\n // Latest command, put nothing\n if (i == len || i == len - 1) {\n setConsoleHistoryIndex(len);\n event.currentTarget.value = \"\";\n } else {\n setConsoleHistoryIndex(consoleHistoryIndex + 1);\n const prevCommand = consoleHistory[consoleHistoryIndex + 1];\n event.currentTarget.value = prevCommand;\n }\n }\n }\n\n return (\n \n \n \n {props.bladeburner.consoleLogs.map((log: any, i: number) => (\n \n ))}\n \n \n \n ),\n spellCheck: false,\n }}\n />\n \n
\n
\n
\n );\n}\n","import React from \"react\";\nimport { GeneralActionPage } from \"./GeneralActionPage\";\nimport { ContractPage } from \"./ContractPage\";\nimport { OperationPage } from \"./OperationPage\";\nimport { BlackOpPage } from \"./BlackOpPage\";\nimport { SkillPage } from \"./SkillPage\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nimport Tabs from \"@mui/material/Tabs\";\nimport Tab from \"@mui/material/Tab\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function AllPages(props: IProps): React.ReactElement {\n const [value, setValue] = React.useState(0);\n\n function handleChange(event: React.SyntheticEvent, tab: number): void {\n setValue(tab);\n }\n\n return (\n <>\n \n \n \n \n \n \n \n \n {value === 0 && }\n {value === 1 && }\n {value === 2 && }\n {value === 3 && }\n {value === 4 && }\n \n \n );\n}\n","import * as React from \"react\";\nimport { GeneralActionList } from \"./GeneralActionList\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function GeneralActionPage(props: IProps): React.ReactElement {\n return (\n <>\n These are generic actions that will assist you in your Bladeburner duties.\n \n \n );\n}\n","import React from \"react\";\nimport { GeneralActionElem } from \"./GeneralActionElem\";\nimport { Action } from \"../Action\";\nimport { GeneralActions } from \"../GeneralActions\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function GeneralActionList(props: IProps): React.ReactElement {\n const actions: Action[] = [];\n for (const name in GeneralActions) {\n if (GeneralActions.hasOwnProperty(name)) {\n actions.push(GeneralActions[name]);\n }\n }\n return (\n <>\n {actions.map((action: Action) => (\n \n ))}\n \n );\n}\n","import React, { useState } from \"react\";\nimport { ActionTypes } from \"../data/ActionTypes\";\nimport { createProgressBarText } from \"../../utils/helpers/createProgressBarText\";\nimport { formatNumber, convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IAction } from \"../IAction\";\nimport { GeneralActions } from \"../data/GeneralActions\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { CopyableText } from \"../../ui/React/CopyableText\";\n\nimport { StartButton } from \"./StartButton\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Box from \"@mui/material/Box\";\nimport Paper from \"@mui/material/Paper\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n action: IAction;\n}\n\nexport function GeneralActionElem(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const isActive = props.action.name === props.bladeburner.action.name;\n const computedActionTimeCurrent = Math.min(\n props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,\n props.bladeburner.actionTimeToComplete,\n );\n const actionTime = (function (): number {\n switch (props.action.name) {\n case \"Training\":\n case \"Field Analysis\":\n return 30;\n case \"Diplomacy\":\n case \"Hyperbolic Regeneration Chamber\":\n return 60;\n case \"Recruitment\":\n return props.bladeburner.getRecruitmentTime(props.player);\n }\n return -1; // dead code\n })();\n const successChance =\n props.action.name === \"Recruitment\"\n ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1))\n : -1;\n\n const actionData = GeneralActions[props.action.name];\n if (actionData === undefined) {\n throw new Error(`Cannot find data for ${props.action.name}`);\n }\n\n return (\n \n {isActive ? (\n <>\n \n (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{\" \"}\n {formatNumber(props.bladeburner.actionTimeToComplete, 0)})\n \n \n {createProgressBarText({\n progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,\n })}\n \n \n ) : (\n \n \n \n \n \n \n )}\n
\n
\n {actionData.desc}\n
\n
\n \n Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}\n {successChance !== -1 && (\n <>\n
\n Estimated success chance: {formatNumber(successChance * 100, 1)}%\n \n )}\n
\n
\n );\n}\n","import React from \"react\";\n\ninterface IContract {\n desc: JSX.Element;\n}\n\nexport const GeneralActions: {\n [key: string]: IContract | undefined;\n} = {\n Training: {\n desc: (\n <>\n Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for\n all combat stats and also increases your max stamina.\n \n ),\n },\n\n \"Field Analysis\": {\n desc: (\n <>\n Mine and analyze Synthoid-related data. This improves the Bladeburner's unit intelligence on Synthoid locations\n and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the\n current city.\n
\n
\n Does NOT require stamina.\n \n ),\n },\n\n Recruitment: {\n desc: (\n <>\n Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.\n
\n
\n Does NOT require stamina.\n \n ),\n },\n\n Diplomacy: {\n desc: (\n <>\n Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in\n your current city.\n
\n
\n Does NOT require stamina.\n \n ),\n },\n\n \"Hyperbolic Regeneration Chamber\": {\n desc: (\n <>\n Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your\n wounds and slightly increase your stamina.\n
\n
\n \n ),\n },\n};\n","import * as React from \"react\";\nimport { ContractList } from \"./ContractList\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function ContractPage(props: IProps): React.ReactElement {\n return (\n <>\n \n Complete contracts in order to increase your Bladeburner rank and earn money. Failing a contract will cause you\n to lose HP, which can lead to hospitalization.\n
\n
\n You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more\n difficult, but grant more rank, experience, and money.\n
\n \n \n );\n}\n","import React from \"react\";\nimport { ContractElem } from \"./ContractElem\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function ContractList(props: IProps): React.ReactElement {\n const names = Object.keys(props.bladeburner.contracts);\n const contracts = props.bladeburner.contracts;\n return (\n <>\n {names.map((name: string) => (\n \n ))}\n \n );\n}\n","import React, { useState } from \"react\";\nimport { ActionTypes } from \"../data/ActionTypes\";\nimport { createProgressBarText } from \"../../utils/helpers/createProgressBarText\";\nimport { formatNumber, convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { Contracts } from \"../data/Contracts\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IAction } from \"../IAction\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { SuccessChance } from \"./SuccessChance\";\nimport { CopyableText } from \"../../ui/React/CopyableText\";\nimport { ActionLevel } from \"./ActionLevel\";\nimport { Autolevel } from \"./Autolevel\";\nimport { StartButton } from \"./StartButton\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n action: IAction;\n}\n\nexport function ContractElem(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const isActive =\n props.bladeburner.action.type === ActionTypes[\"Contract\"] && props.action.name === props.bladeburner.action.name;\n const computedActionTimeCurrent = Math.min(\n props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,\n props.bladeburner.actionTimeToComplete,\n );\n const actionTime = props.action.getActionTime(props.bladeburner);\n\n const actionData = Contracts[props.action.name];\n if (actionData === undefined) {\n throw new Error(`Cannot find data for ${props.action.name}`);\n }\n\n return (\n \n {isActive ? (\n <>\n \n (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{\" \"}\n {formatNumber(props.bladeburner.actionTimeToComplete, 0)})\n \n \n {createProgressBarText({\n progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,\n })}\n \n \n ) : (\n <>\n \n \n \n )}\n
\n
\n \n
\n
\n \n {actionData.desc}\n
\n
\n \n
\n Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}\n
\n Contracts remaining: {Math.floor(props.action.count)}\n
\n Successes: {props.action.successes}\n
\n Failures: {props.action.failures}\n
\n
\n \n
\n );\n}\n","import React from \"react\";\n\ninterface IContract {\n desc: JSX.Element;\n}\n\nexport const Contracts: {\n [key: string]: IContract | undefined;\n} = {\n Tracking: {\n desc: (\n <>\n Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT\n engage. Stealth is of the utmost importance.\n
\n
\n Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever\n city you are currently in.\n \n ),\n },\n \"Bounty Hunter\": {\n desc: (\n <>\n Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.\n
\n
\n Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also\n increase its chaos level.\n \n ),\n },\n Retirement: {\n desc: (\n <>\n Hunt down and retire (kill) rogue Synthoids.\n
\n
\n Successfully completing a Retirement contract will lower the population in your current city, and will also\n increase its chaos level.\n \n ),\n },\n};\n","import React from \"react\";\nimport { stealthIcon } from \"../data/Icons\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\nexport function StealthIcon(): React.ReactElement {\n return This action involves stealth}>{stealthIcon};\n}\n","import React from \"react\";\nimport { killIcon } from \"../data/Icons\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\nexport function KillIcon(): React.ReactElement {\n return This action involves retirement}>{killIcon};\n}\n","import * as React from \"react\";\nimport { OperationList } from \"./OperationList\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function OperationPage(props: IProps): React.ReactElement {\n return (\n <>\n \n Carry out operations for the Bladeburner division. Failing an operation will reduce your Bladeburner rank. It\n will also cause you to lose HP, which can lead to hospitalization. In general, operations are harder and more\n punishing than contracts, but are also more rewarding.\n
\n
\n Operations can affect the chaos level and Synthoid population of your current city. The exact effects vary\n between different Operations.\n
\n
\n For operations, you can use a team. You must first recruit team members. Having a larger team will improves your\n chances of success.\n
\n
\n You can unlock higher-level operations by successfully completing them. Higher-level operations are more\n difficult, but grant more rank and experience.\n
\n \n \n );\n}\n","import React from \"react\";\nimport { OperationElem } from \"./OperationElem\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function OperationList(props: IProps): React.ReactElement {\n const names = Object.keys(props.bladeburner.operations);\n const operations = props.bladeburner.operations;\n return (\n <>\n {names.map((name: string) => (\n \n ))}\n \n );\n}\n","import React, { useState } from \"react\";\nimport { ActionTypes } from \"../data/ActionTypes\";\nimport { createProgressBarText } from \"../../utils/helpers/createProgressBarText\";\nimport { formatNumber, convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { SuccessChance } from \"./SuccessChance\";\nimport { ActionLevel } from \"./ActionLevel\";\nimport { Autolevel } from \"./Autolevel\";\nimport { StartButton } from \"./StartButton\";\nimport { TeamSizeButton } from \"./TeamSizeButton\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { Operation } from \"../Operation\";\nimport { Operations } from \"../data/Operations\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { CopyableText } from \"../../ui/React/CopyableText\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n action: Operation;\n}\n\nexport function OperationElem(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const isActive =\n props.bladeburner.action.type === ActionTypes[\"Operation\"] && props.action.name === props.bladeburner.action.name;\n const computedActionTimeCurrent = Math.min(\n props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,\n props.bladeburner.actionTimeToComplete,\n );\n const actionTime = props.action.getActionTime(props.bladeburner);\n\n const actionData = Operations[props.action.name];\n if (actionData === undefined) {\n throw new Error(`Cannot find data for ${props.action.name}`);\n }\n\n return (\n \n {isActive ? (\n <>\n \n (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{\" \"}\n {formatNumber(props.bladeburner.actionTimeToComplete, 0)})\n \n \n {createProgressBarText({\n progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,\n })}\n \n \n ) : (\n <>\n \n \n \n \n )}\n
\n
\n\n \n
\n
\n \n {actionData.desc}\n
\n
\n \n
\n Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}\n
\n Operations remaining: {Math.floor(props.action.count)}\n
\n Successes: {props.action.successes}\n
\n Failures: {props.action.failures}\n
\n
\n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { Action } from \"../Action\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n action: Action;\n open: boolean;\n onClose: () => void;\n}\n\nexport function TeamSizeModal(props: IProps): React.ReactElement {\n const [teamSize, setTeamSize] = useState();\n\n function confirmTeamSize(): void {\n if (teamSize === undefined) return;\n const num = Math.round(teamSize);\n if (isNaN(num) || num < 0) {\n dialogBoxCreate(\"Invalid value entered for number of Team Members (must be numeric, positive)\");\n } else {\n props.action.teamCount = num;\n }\n props.onClose();\n }\n\n function onTeamSize(event: React.ChangeEvent): void {\n const x = parseFloat(event.target.value);\n if (x > props.bladeburner.teamSize) setTeamSize(props.bladeburner.teamSize);\n else setTeamSize(x);\n }\n\n return (\n \n \n Enter the amount of team members you would like to take on this Op. If you do not have the specified number of\n team members, then as many as possible will be used. Note that team members may be lost during operations.\n \n \n \n \n );\n}\n","import React from \"react\";\n\ninterface IOperation {\n desc: JSX.Element;\n}\n\nexport const Operations: {\n [key: string]: IOperation | undefined;\n} = {\n Investigation: {\n desc: (\n <>\n As a field agent, investigate and identify Synthoid populations, movements, and operations.\n
\n
\n Successful Investigation ops will increase the accuracy of your synthoid data.\n
\n
\n You will NOT lose HP from failed Investigation ops.\n \n ),\n },\n \"Undercover Operation\": {\n desc: (\n <>\n Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.\n
\n
\n Successful Undercover ops will increase the accuracy of your synthoid data.\n \n ),\n },\n \"Sting Operation\": {\n desc: <>Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.,\n },\n Raid: {\n desc: (\n <>\n Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your\n current city in order for this Operation to be successful.\n \n ),\n },\n \"Stealth Retirement Operation\": {\n desc: (\n <>\n Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any\n attention. Stealth and discretion are key.\n \n ),\n },\n Assassination: {\n desc: (\n <>\n Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the\n Synthoid communities.\n \n ),\n },\n};\n","import * as React from \"react\";\nimport { BlackOpList } from \"./BlackOpList\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function BlackOpPage(props: IProps): React.ReactElement {\n return (\n <>\n \n Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked\n successively by completing the one before it.\n
\n
\n Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.\n
\n
\n Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank\n losses.\n
\n \n \n );\n}\n","import React from \"react\";\nimport { BlackOperations } from \"../BlackOperations\";\nimport { BlackOperation } from \"../BlackOperation\";\nimport { BlackOpElem } from \"./BlackOpElem\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n}\n\nexport function BlackOpList(props: IProps): React.ReactElement {\n let blackops: BlackOperation[] = [];\n for (const blackopName in BlackOperations) {\n if (BlackOperations.hasOwnProperty(blackopName)) {\n blackops.push(BlackOperations[blackopName]);\n }\n }\n blackops.sort(function (a, b) {\n return a.reqdRank - b.reqdRank;\n });\n\n blackops = blackops.filter(\n (blackop: BlackOperation, i: number) =>\n !(\n props.bladeburner.blackops[blackops[i].name] == null &&\n i !== 0 &&\n props.bladeburner.blackops[blackops[i - 1].name] == null\n ),\n );\n\n blackops = blackops.reverse();\n\n return (\n <>\n {blackops.map((blackop: BlackOperation) => (\n \n ))}\n \n );\n}\n","import React, { useState } from \"react\";\nimport { formatNumber, convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { ActionTypes } from \"../data/ActionTypes\";\nimport { createProgressBarText } from \"../../utils/helpers/createProgressBarText\";\nimport { TeamSizeButton } from \"./TeamSizeButton\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport { BlackOperation } from \"../BlackOperation\";\nimport { BlackOperations } from \"../data/BlackOperations\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { CopyableText } from \"../../ui/React/CopyableText\";\nimport { SuccessChance } from \"./SuccessChance\";\nimport { StartButton } from \"./StartButton\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n player: IPlayer;\n action: BlackOperation;\n}\n\nexport function BlackOpElem(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const isCompleted = props.bladeburner.blackops[props.action.name] != null;\n if (isCompleted) {\n return

{props.action.name} (COMPLETED)

;\n }\n\n const isActive =\n props.bladeburner.action.type === ActionTypes[\"BlackOperation\"] &&\n props.action.name === props.bladeburner.action.name;\n const actionTime = props.action.getActionTime(props.bladeburner);\n const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;\n const computedActionTimeCurrent = Math.min(\n props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,\n props.bladeburner.actionTimeToComplete,\n );\n\n const actionData = BlackOperations[props.action.name];\n if (actionData === undefined) {\n throw new Error(`Cannot find data for ${props.action.name}`);\n }\n\n return (\n \n \n {isActive ? (\n <>\n <>\n (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{\" \"}\n {formatNumber(props.bladeburner.actionTimeToComplete, 0)})\n

\n {createProgressBarText({\n progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,\n })}\n

\n \n \n ) : (\n <>\n \n\n \n \n \n )}\n
\n
\n
\n {actionData.desc}\n
\n
\n \n Required Rank: {formatNumber(props.action.reqdRank, 0)}\n \n
\n \n \n
\n Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}\n
\n
\n );\n}\n","import React from \"react\";\n\ninterface IBlackOp {\n desc: JSX.Element;\n}\n\nexport const BlackOperations: {\n [key: string]: IBlackOp | undefined;\n} = {\n \"Operation Typhoon\": {\n desc: (\n <>\n Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that\n Zenyatta, along with the rest of the PMC, is a Synthoid.\n
\n
\n The goal of Operation Typhoon is to find and eliminate Zenyatta and RedWater by any means necessary. After the\n task is completed, the actions must be covered up from the general public.\n \n ),\n },\n\n \"Operation Zero\": {\n desc: (\n <>\n AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be a\n supporter of Synthoid rights. He must be removed.\n
\n
\n The goal of Operation Zero is to covertly infiltrate AeroCorp and uncover any incriminating evidence or\n information against Watataki that will cause him to be removed from his position at AeroCorp. Incriminating\n evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced security\n measures in the world.\n \n ),\n },\n \"Operation X\": {\n desc: (\n <>\n We have recently discovered an underground publication group called Samizdat. Even though most of their\n publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of\n their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and\n other Eastern countries.\n
\n
\n Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that\n their base of operations is in Ishima's underground sewer systems. Your task is to investigate the sewer\n systems, and eliminate Samizdat. They must never publish anything again.\n \n ),\n },\n \"Operation Titan\": {\n desc: (\n <>\n Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we\n know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to,\n but the research that they could be conducting using Titan Laboraties' vast resources is potentially very\n dangerous.\n
\n
\n Your goal is to enter and destroy the Bioengineering department's facility in Aevum. The task is not just to\n retire the Synthoids there, but also to destroy any information or research at the facility that is relevant to\n the Synthoids and their goals.\n \n ),\n },\n \"Operation Ares\": {\n desc: (\n <>\n One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai\n between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy\n weapons. It is critical for the safety of humanity that this deal does not happen.\n
\n
\n Your task is to intercept the deal. Leave no survivors.\n \n ),\n },\n \"Operation Archangel\": {\n desc: (\n <>\n Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI\n Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms\n trafficking operation.\n
\n
\n The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the\n number of other casualties, but do what you must to complete the mission.\n \n ),\n },\n \"Operation Juggernaut\": {\n desc: (\n <>\n The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls\n himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into Sector-12. We\n also have reason to believe the tried to break into one of Universal Energy's facilities in order to cause a\n city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented Synthoid, and have thus enlisted our\n help.\n
\n
\n Your mission is to eradicate Juggernaut and his followers.\n \n ),\n },\n \"Operation Red Dragon\": {\n desc: (\n <>\n The Tetrads criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. We believe they\n altered and possibly improved the design and began manufacturing their own Synthoid models in order to bolster\n their criminal activities.\n
\n
\n Your task is to infiltrate and destroy the Tetrads' base of operations in Los Angeles. Intelligence tells us\n that their base houses one of their Synthoid manufacturing units.\n \n ),\n },\n \"Operation K\": {\n desc: (\n <>\n CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology.\n This technology is supposedly capable of cloning Synthoid, not only physically but also their advanced AI\n modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any\n Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would\n be catastrophic.\n
\n
\n We do not have the power or jurisdiction to shutdown this down through legal or political means, so we must\n resort to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in\n its creation.\n \n ),\n },\n \"Operation Deckard\": {\n desc: (\n <>\n Despite your success in eliminating VitaLife's new android-replicating technology in Operation K, we've\n discovered that a small group of MK-VI Synthoids were able to make off with the schematics and design of the\n technology before the Operation. It is almost a certainty that these Synthoids are some of the rogue MK-VI ones\n from the Synthoid Uprising.\n
\n
\n The goal of Operation Deckard is to hunt down these Synthoids and retire them. I don't need to tell you how\n critical this mission is.\n \n ),\n },\n \"Operation Tyrell\": {\n desc: (\n <>\n A week ago Blade Industries reported a small break-in at one of their Aevum Augmentation storage facitilities.\n We figured out that The Dark Army was behind the heist, and didn't think any more of it. However, we've just\n discovered that several known MK-VI Synthoids were part of that break-in group.\n
\n
\n We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt\n down the associated Dark Army members and eliminate them.\n \n ),\n },\n \"Operation Wallace\": {\n desc: (\n <>\n Based on information gathered from Operation Tyrell, we've discovered that The Dark Army was well aware that\n there were Synthoids amongst their ranks. Even worse, we believe that The Dark Army is working together with\n other criminal organizations such as The Syndicate and that they are planning some sort of large-scale takeover\n of multiple major cities, most notably Aevum. We suspect that Synthoids have infiltrated the ranks of these\n criminal factions and are trying to stage another Synthoid uprising.\n
\n
\n The best way to deal with this is to prevent it before it even happens. The goal of Operation Wallace is to\n destroy the Dark Army and Syndicate factions in Aevum immediately. Leave no survivors.\n \n ),\n },\n \"Operation Shoulder of Orion\": {\n desc: (\n <>\n China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using\n Synthoids. We believe China is trying to establish the first off-world colonies.\n
\n
\n The mission is to prevent this launch without instigating an international conflict. When you accept this\n mission you will be officially disavowed by the NSA and the national government until after you successfully\n return. In the event of failure, all of the operation's team members must not let themselves be captured alive.\n \n ),\n },\n \"Operation Hyron\": {\n desc: (\n <>\n Our intelligence tells us that Fulcrum Technologies is developing a quantum supercomputer using human brains as\n core processors. This supercomputer is rumored to be able to store vast amounts of data and perform computations\n unmatched by any other supercomputer on the planet. But more importantly, the use of organic human brains means\n that the supercomputer may be able to reason abstractly and become self-aware.\n
\n
\n I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.\n
\n
\n The research for this project is being conducted at one of Fulcrum Technologies secret facilities in Aevum,\n codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, and then find and kill the\n project lead.\n \n ),\n },\n \"Operation Morpheus\": {\n desc: (\n <>\n DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the\n peoples dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our\n agents and informants in Chonqging, we have reason to believe that one of the broadcast towers there has been\n compromised by Synthoids and is being used to spread pro-Synthoid propaganda.\n
\n
\n The mission is to destroy this broadcast tower. Speed and stealth are of the upmost important for this.\n \n ),\n },\n \"Operation Ion Storm\": {\n desc: (\n <>\n Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the Sector-12 Slums.\n We don't know if they are rogue Synthoids from the Uprising, but we do know that they have been stockpiling\n weapons, money, and other resources. This makes them dangerous.\n
\n
\n This is a full-scale assault operation to find and retire all of these Synthoids in the Sector-12 Slums.\n \n ),\n },\n \"Operation Annihilus\": {\n desc: (\n <>\n Our superiors have ordered us to eradicate everything and everyone in an underground facility located in Aevum.\n They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist organization called\n 'The Covenant'. We have no prior intelligence about this organization, so you are going in blind.\n \n ),\n },\n \"Operation Ultron\": {\n desc: (\n <>\n OmniTek Incorporated, the original designer and manufacturer of Synthoids, has notified us of a malfunction in\n their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized and seek out the\n destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue ones from the\n Uprising.\n
\n
\n OmniTek has also told us they they believe someone has triggered this malfunction in a large group of MK-VI\n Synthoids, and that these newly-radicalized Synthoids are now amassing in Volhaven to form a terrorist group\n called Ultron.\n
\n
\n Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making\n moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).\n
\n
\n Your task is to find and destroy Ultron.\n \n ),\n },\n \"Operation Centurion\": {\n desc: (\n <>\n {\"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\"}\n
\n
\n Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its\n advancement became our primary goal. And at the peak of human civilization technology turned into power. Global,\n absolute power.\n
\n
\n It seems that the universe is not without a sense of irony.\n
\n
\n {\"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\"}\n \n ),\n },\n \"Operation Vindictus\": {\n desc: (\n <>\n {\"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\"}\n
\n
\n The bits are all around us. The daemons that hold the Node together can manifest themselves in many different\n ways.\n
\n
\n {\"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)\"}\n \n ),\n },\n \"Operation Daedalus\": {\n desc: <> Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.,\n },\n};\n","import React, { useState } from \"react\";\nimport { SkillList } from \"./SkillList\";\nimport { BladeburnerConstants } from \"../data/Constants\";\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { IBladeburner } from \"../IBladeburner\";\nimport Typography from \"@mui/material/Typography\";\nimport { BitNodeMultipliers } from \"../../BitNode/BitNodeMultipliers\";\ninterface IProps {\n bladeburner: IBladeburner;\n}\n\nexport function SkillPage(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n const mults = props.bladeburner.skillMultipliers;\n\n function valid(mult: any): boolean {\n return mult && mult !== 1;\n }\n\n return (\n <>\n \n Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}\n \n \n You will gain one skill point every{\" \"}\n {BladeburnerConstants.RanksPerSkillPoint * BitNodeMultipliers.BladeburnerSkillCost} ranks.\n
\n Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different\n skills with each other is multiplicative.\n
\n {valid(mults[\"successChanceAll\"]) && (\n Total Success Chance: x{formatNumber(mults[\"successChanceAll\"], 3)}\n )}\n {valid(mults[\"successChanceStealth\"]) && (\n Stealth Success Chance: x{formatNumber(mults[\"successChanceStealth\"], 3)}\n )}\n {valid(mults[\"successChanceKill\"]) && (\n Retirement Success Chance: x{formatNumber(mults[\"successChanceKill\"], 3)}\n )}\n {valid(mults[\"successChanceContract\"]) && (\n Contract Success Chance: x{formatNumber(mults[\"successChanceContract\"], 3)}\n )}\n {valid(mults[\"successChanceOperation\"]) && (\n Operation Success Chance: x{formatNumber(mults[\"successChanceOperation\"], 3)}\n )}\n {valid(mults[\"successChanceEstimate\"]) && (\n Synthoid Data Estimate: x{formatNumber(mults[\"successChanceEstimate\"], 3)}\n )}\n {valid(mults[\"actionTime\"]) && Action Time: x{formatNumber(mults[\"actionTime\"], 3)}}\n {valid(mults[\"effHack\"]) && Hacking Skill: x{formatNumber(mults[\"effHack\"], 3)}}\n {valid(mults[\"effStr\"]) && Strength: x{formatNumber(mults[\"effStr\"], 3)}}\n {valid(mults[\"effDef\"]) && Defense: x{formatNumber(mults[\"effDef\"], 3)}}\n {valid(mults[\"effDex\"]) && Dexterity: x{formatNumber(mults[\"effDex\"], 3)}}\n {valid(mults[\"effAgi\"]) && Agility: x{formatNumber(mults[\"effAgi\"], 3)}}\n {valid(mults[\"effCha\"]) && Charisma: x{formatNumber(mults[\"effCha\"], 3)}}\n {valid(mults[\"effInt\"]) && Intelligence: x{formatNumber(mults[\"effInt\"], 3)}}\n {valid(mults[\"stamina\"]) && Stamina: x{formatNumber(mults[\"stamina\"], 3)}}\n {valid(mults[\"money\"]) && Contract Money: x{formatNumber(mults[\"money\"], 3)}}\n {valid(mults[\"expGain\"]) && Exp Gain: x{formatNumber(mults[\"expGain\"], 3)}}\n setRerender((old) => !old)} />\n \n );\n}\n\n/*\n\n\n\n\nvar multKeys = Object.keys(this.skillMultipliers);\nfor (var i = 0; i < multKeys.length; ++i) {\n var mult = this.skillMultipliers[multKeys[i]];\n if (mult && mult !== 1) {\n mult = formatNumber(mult, 3);\n switch(multKeys[i]) {\n \n }\n }\n}\n*/\n","import * as React from \"react\";\nimport { SkillElem } from \"./SkillElem\";\nimport { Skills } from \"../Skills\";\nimport { IBladeburner } from \"../IBladeburner\";\n\ninterface IProps {\n bladeburner: IBladeburner;\n onUpgrade: () => void;\n}\n\nexport function SkillList(props: IProps): React.ReactElement {\n return (\n <>\n {Object.keys(Skills).map((skill: string) => (\n \n ))}\n \n );\n}\n","import React from \"react\";\nimport { CopyableText } from \"../../ui/React/CopyableText\";\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { IBladeburner } from \"../IBladeburner\";\n\nimport Typography from \"@mui/material/Typography\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Box from \"@mui/material/Box\";\nimport Paper from \"@mui/material/Paper\";\nimport AddIcon from \"@mui/icons-material/Add\";\nimport CloseIcon from \"@mui/icons-material/Close\";\n\ninterface IProps {\n skill: any;\n bladeburner: IBladeburner;\n onUpgrade: () => void;\n}\n\nexport function SkillElem(props: IProps): React.ReactElement {\n const skillName = props.skill.name;\n let currentLevel = 0;\n if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) {\n currentLevel = props.bladeburner.skills[skillName];\n }\n const pointCost = props.skill.calculateCost(currentLevel);\n\n const canLevel = props.bladeburner.skillPoints >= pointCost;\n const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false;\n\n function onClick(): void {\n if (props.bladeburner.skillPoints < pointCost) return;\n props.bladeburner.skillPoints -= pointCost;\n props.bladeburner.upgradeSkill(props.skill);\n props.onUpgrade();\n }\n\n return (\n \n \n \n {!canLevel || maxLvl ? (\n \n \n \n ) : (\n \n \n \n )}\n \n Level: {currentLevel}\n {maxLvl ? (\n MAX LEVEL\n ) : (\n Skill Points required: {formatNumber(pointCost, 0)}\n )}\n {props.skill.desc}\n \n );\n}\n","/**\n * React Component for all the gang stuff.\n */\nimport React, { useState, useEffect } from \"react\";\nimport { ManagementSubpage } from \"./ManagementSubpage\";\nimport { TerritorySubpage } from \"./TerritorySubpage\";\nimport { EquipmentsSubpage } from \"./EquipmentsSubpage\";\nimport { use } from \"../../ui/Context\";\nimport { Context } from \"./Context\";\n\nimport Tabs from \"@mui/material/Tabs\";\nimport Tab from \"@mui/material/Tab\";\n\nexport function GangRoot(): React.ReactElement {\n const player = use.Player();\n const gang = (function () {\n if (player.gang === null) throw new Error(\"Gang should not be null\");\n return player.gang;\n })();\n const [value, setValue] = React.useState(0);\n\n function handleChange(event: React.SyntheticEvent, tab: number): void {\n setValue(tab);\n }\n\n const setRerender = useState(false)[1];\n\n useEffect(() => {\n const id = setInterval(() => setRerender((old) => !old), 200);\n return () => clearInterval(id);\n }, []);\n\n return (\n \n \n \n \n \n \n {value === 0 && }\n {value === 1 && }\n {value === 2 && }\n \n );\n}\n","/**\n * React Component for the subpage that manages gang members, the main page.\n */\nimport React from \"react\";\nimport { GangStats } from \"./GangStats\";\nimport { GangMemberList } from \"./GangMemberList\";\nimport { useGang } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\n\nexport function ManagementSubpage(): React.ReactElement {\n const gang = useGang();\n return (\n <>\n \n This page is used to manage your gang members and get an overview of your gang's stats.\n
\n
\n If a gang member is not earning much money or respect, the task that you have assigned to that member might be\n too difficult. Consider training that member's stats or choosing an easier task. The tasks closer to the top of\n the dropdown list are generally easier. Alternatively, the gang member's low production might be due to the fact\n that your wanted level is too high. Consider assigning a few members to the '\n {gang.isHackingGang ? \"Ethical Hacking\" : \"Vigilante Justice\"}' task to lower your wanted level.\n
\n
\n Installing Augmentations does NOT reset your progress with your Gang. Furthermore, after installing\n Augmentations, you will automatically be a member of whatever Faction you created your gang with.\n
\n
\n You can also manage your gang programmatically through Netscript using the Gang API\n
\n
\n \n
\n \n \n );\n}\n","/**\n * React Component for the stats related to the gang, like total respect and\n * money per second.\n */\nimport React from \"react\";\nimport { Factions } from \"../../Faction/Factions\";\n\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { MoneyRate } from \"../../ui/React/MoneyRate\";\nimport { Reputation } from \"../../ui/React/Reputation\";\nimport { AllGangs } from \"../AllGangs\";\nimport { BonusTime } from \"./BonusTime\";\nimport { useGang } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\n\nexport function GangStats(): React.ReactElement {\n const gang = useGang();\n const territoryMult = AllGangs[gang.facName].territory * 100;\n let territoryStr;\n if (territoryMult <= 0) {\n territoryStr = formatNumber(0, 2);\n } else if (territoryMult >= 100) {\n territoryStr = formatNumber(100, 2);\n } else {\n territoryStr = formatNumber(territoryMult, 2);\n }\n\n return (\n <>\n \n \n Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect\n affects the amount of money your gang members will earn, and also determines how much reputation you are\n earning with your gang's corresponding Faction.\n \n }\n >\n \n Respect: {numeralWrapper.formatRespect(gang.respect)} (\n {numeralWrapper.formatRespect(5 * gang.respectGainRate)} / sec)\n \n \n \n\n \n \n Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder\n it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.\n \n }\n >\n \n Wanted Level: {numeralWrapper.formatWanted(gang.wanted)} (\n {numeralWrapper.formatWanted(5 * gang.wantedGainRate)} / sec)\n \n \n \n\n \n Penalty for respect and money gain rates due to Wanted Level}>\n Wanted Level Penalty: -{formatNumber((1 - gang.getWantedPenalty()) * 100, 2)}%\n \n \n\n \n Money gain rate: \n \n\n \n The percentage of total territory your Gang controls}>\n Territory: {territoryStr}%\n \n \n \n Faction reputation: \n \n\n \n \n );\n}\n","/**\n * React Component for displaying the bonus time remaining.\n */\nimport * as React from \"react\";\nimport { Gang } from \"../Gang\";\nimport { CONSTANTS } from \"../../Constants\";\nimport { convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n gang: Gang;\n}\n\nexport function BonusTime(props: IProps): React.ReactElement {\n const CyclerPerSecond = 1000 / CONSTANTS._idleSpeed;\n if ((props.gang.storedCycles / CyclerPerSecond) * 1000 <= 5000) return <>;\n const bonusMillis = (props.gang.storedCycles / CyclerPerSecond) * 1000;\n return (\n \n \n You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the\n browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed.\n \n }\n >\n Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)}\n \n \n );\n}\n","/**\n * React Component for the list of gang members on the management subpage.\n */\nimport React, { useState } from \"react\";\nimport { GangMemberAccordion } from \"./GangMemberAccordion\";\nimport { GangMember } from \"../GangMember\";\nimport { RecruitButton } from \"./RecruitButton\";\nimport { useGang } from \"./Context\";\n\nexport function GangMemberList(): React.ReactElement {\n const gang = useGang();\n const setRerender = useState(false)[1];\n\n return (\n <>\n setRerender((old) => !old)} />\n
    \n {gang.members.map((member: GangMember) => (\n \n ))}\n
\n \n );\n}\n","/**\n * React Component for a gang member on the management subpage.\n */\nimport React, { useState } from \"react\";\nimport { GangMember } from \"../GangMember\";\nimport { GangMemberAccordionContent } from \"./GangMemberAccordionContent\";\n\nimport Box from \"@mui/material/Box\";\n\nimport Typography from \"@mui/material/Typography\";\nimport ListItemButton from \"@mui/material/ListItemButton\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport Paper from \"@mui/material/Paper\";\nimport Collapse from \"@mui/material/Collapse\";\nimport ExpandMore from \"@mui/icons-material/ExpandMore\";\nimport ExpandLess from \"@mui/icons-material/ExpandLess\";\ninterface IProps {\n member: GangMember;\n}\n\nexport function GangMemberAccordion(props: IProps): React.ReactElement {\n const [open, setOpen] = useState(true);\n return (\n \n setOpen((old) => !old)}>\n {props.member.name}} />\n {open ? : }\n \n \n \n \n \n \n \n );\n}\n","/**\n * React Component for the content of the accordion of gang members on the\n * management subpage.\n */\nimport React, { useState } from \"react\";\nimport { GangMemberStats } from \"./GangMemberStats\";\nimport { TaskSelector } from \"./TaskSelector\";\nimport { TaskDescription } from \"./TaskDescription\";\nimport { GangMember } from \"../GangMember\";\nimport Grid from \"@mui/material/Grid\";\n\ninterface IProps {\n member: GangMember;\n}\n\nexport function GangMemberAccordionContent(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n return (\n \n \n setRerender((old) => !old)} member={props.member} />\n \n \n setRerender((old) => !old)} member={props.member} />\n \n \n \n \n \n );\n}\n","/**\n * React Component for the first part of a gang member details.\n * Contains skills and exp.\n */\nimport React, { useState } from \"react\";\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { GangMember } from \"../GangMember\";\nimport { AscensionModal } from \"./AscensionModal\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Button from \"@mui/material/Button\";\nimport { StaticModal } from \"../../ui/React/StaticModal\";\nimport IconButton from \"@mui/material/IconButton\";\nimport HelpIcon from \"@mui/icons-material/Help\";\n\ninterface IProps {\n member: GangMember;\n onAscend: () => void;\n}\n\nexport function GangMemberStats(props: IProps): React.ReactElement {\n const [helpOpen, setHelpOpen] = useState(false);\n const [ascendOpen, setAscendOpen] = useState(false);\n\n const asc = {\n hack: props.member.calculateAscensionMult(props.member.hack_asc_points),\n str: props.member.calculateAscensionMult(props.member.str_asc_points),\n def: props.member.calculateAscensionMult(props.member.def_asc_points),\n dex: props.member.calculateAscensionMult(props.member.dex_asc_points),\n agi: props.member.calculateAscensionMult(props.member.agi_asc_points),\n cha: props.member.calculateAscensionMult(props.member.cha_asc_points),\n };\n\n return (\n <>\n \n Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x\n {numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)}{\" \"}\n Asc)\n
\n St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}\n (x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)}{\" \"}\n Asc)\n
\n Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}\n (x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)}{\" \"}\n Asc)\n
\n Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}\n (x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)}{\" \"}\n Asc)\n
\n Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}\n (x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)}{\" \"}\n Asc)\n
\n Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}\n (x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)}{\" \"}\n Asc)\n \n }\n >\n \n Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)\n
\n Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)\n
\n Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)\n
\n Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)\n
\n Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)\n
\n Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)\n
\n
\n \n
\n {props.member.canAscend() && (\n <>\n \n setAscendOpen(false)}\n member={props.member}\n onAscend={props.onAscend}\n />\n setHelpOpen(true)}>\n \n \n setHelpOpen(false)}>\n \n Ascending a Gang Member resets the member's progress and stats in exchange for a permanent boost to their\n stat multipliers.\n
\n
\n The additional stat multiplier that the Gang Member gains upon ascension is based on the amount of exp\n they have.\n
\n
\n Upon ascension, the member will lose all of its non-Augmentation Equipment and your gang will lose respect\n equal to the total respect earned by the member.\n
\n
\n \n )}\n \n );\n}\n","/**\n * React Component for the content of the popup before the player confirms the\n * ascension of a gang member.\n */\nimport React, { useState, useEffect } from \"react\";\nimport { GangMember } from \"../GangMember\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useGang } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n member: GangMember;\n onAscend: () => void;\n}\n\nexport function AscensionModal(props: IProps): React.ReactElement {\n const gang = useGang();\n const setRerender = useState(false)[1];\n\n useEffect(() => {\n const id = setInterval(() => setRerender((old) => !old), 1000);\n return () => clearInterval(id);\n }, []);\n\n function confirm(): void {\n props.onAscend();\n const res = gang.ascendMember(props.member);\n dialogBoxCreate(\n \n You ascended {props.member.name}!
\n
\n Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.\n
\n
\n {props.member.name} gained the following stat multipliers for ascending:\n
\n Hacking: x{numeralWrapper.format(res.hack, \"0.000\")}\n
\n Strength: x{numeralWrapper.format(res.str, \"0.000\")}\n
\n Defense: x{numeralWrapper.format(res.def, \"0.000\")}\n
\n Dexterity: x{numeralWrapper.format(res.dex, \"0.000\")}\n
\n Agility: x{numeralWrapper.format(res.agi, \"0.000\")}\n
\n Charisma: x{numeralWrapper.format(res.cha, \"0.000\")}\n
\n
,\n );\n props.onClose();\n }\n\n // const ascendBenefits = props.member.getAscensionResults();\n const preAscend = props.member.getCurrentAscensionMults();\n const postAscend = props.member.getAscensionMultsAfterAscend();\n\n return (\n \n \n Are you sure you want to ascend this member? They will lose all of\n
\n their non-Augmentation upgrades and their stats will reset back to 1.\n
\n
\n Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect\n
\n
\n In return, they will gain the following permanent boost to stat multipliers:\n
\n Hacking: x{numeralWrapper.format(preAscend.hack, \"0.000\")} => x\n {numeralWrapper.format(postAscend.hack, \"0.000\")}\n
\n Strength: x{numeralWrapper.format(preAscend.str, \"0.000\")} => x\n {numeralWrapper.format(postAscend.str, \"0.000\")}\n
\n Defense: x{numeralWrapper.format(preAscend.def, \"0.000\")} => x\n {numeralWrapper.format(postAscend.def, \"0.000\")}\n
\n Dexterity: x{numeralWrapper.format(preAscend.dex, \"0.000\")} => x\n {numeralWrapper.format(postAscend.dex, \"0.000\")}\n
\n Agility: x{numeralWrapper.format(preAscend.agi, \"0.000\")} => x\n {numeralWrapper.format(postAscend.agi, \"0.000\")}\n
\n Charisma: x{numeralWrapper.format(preAscend.cha, \"0.000\")} => x\n {numeralWrapper.format(postAscend.cha, \"0.000\")}\n
\n
\n \n
\n );\n}\n","/**\n * React Component for the middle part of the gang member accordion. Contains\n * the task selector as well as some stats.\n */\nimport React, { useState } from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { StatsTable } from \"../../ui/React/StatsTable\";\nimport { MoneyRate } from \"../../ui/React/MoneyRate\";\nimport { useGang } from \"./Context\";\nimport { GangMember } from \"../GangMember\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n member: GangMember;\n onTaskChange: () => void;\n}\n\nexport function TaskSelector(props: IProps): React.ReactElement {\n const gang = useGang();\n const [currentTask, setCurrentTask] = useState(props.member.task);\n\n function onChange(event: SelectChangeEvent): void {\n const task = event.target.value;\n props.member.assignToTask(task);\n setCurrentTask(task);\n props.onTaskChange();\n }\n\n const tasks = gang.getAllTaskNames();\n\n const data = [\n [`Money:`, ],\n [`Respect:`, `${numeralWrapper.formatRespect(5 * props.member.calculateRespectGain(gang))} / sec`],\n [`Wanted Level:`, `${numeralWrapper.formatWanted(5 * props.member.calculateWantedLevelGain(gang))} / sec`],\n [`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],\n ];\n\n return (\n <>\n \n\n \n \n );\n}\n","/**\n * React Component for left side of the gang member accordion, contains the\n * description of the task that member is currently doing.\n */\nimport React from \"react\";\nimport { GangMemberTasks } from \"../GangMemberTasks\";\nimport { GangMember } from \"../GangMember\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n member: GangMember;\n}\n\nexport function TaskDescription(props: IProps): React.ReactElement {\n const task = GangMemberTasks[props.member.task];\n const desc = task ? task.desc : GangMemberTasks[\"Unassigned\"].desc;\n\n return ;\n}\n","/**\n * React Component for the recruitment button and text on the gang main page.\n */\nimport React, { useState } from \"react\";\nimport { RecruitModal } from \"./RecruitModal\";\nimport { GangConstants } from \"../data/Constants\";\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { useGang } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n onRecruit: () => void;\n}\n\nexport function RecruitButton(props: IProps): React.ReactElement {\n const gang = useGang();\n const [open, setOpen] = useState(false);\n if (gang.members.length >= GangConstants.MaximumGangMembers) {\n return <>;\n }\n\n if (!gang.canRecruitMember()) {\n const respect = gang.getRespectNeededToRecruitMember();\n return (\n \n \n {formatNumber(respect, 2)} respect needed to recruit next member\n \n );\n }\n\n return (\n <>\n \n setOpen(false)} onRecruit={props.onRecruit} />\n \n );\n}\n","/**\n * React Component for the popup used to recruit new gang members.\n */\nimport React, { useState } from \"react\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { useGang } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\ninterface IRecruitPopupProps {\n open: boolean;\n onClose: () => void;\n onRecruit: () => void;\n}\n\nexport function RecruitModal(props: IRecruitPopupProps): React.ReactElement {\n const gang = useGang();\n const [name, setName] = useState(\"\");\n\n const disabled = name === \"\" || !gang.canRecruitMember();\n function recruit(): void {\n if (disabled) return;\n // At this point, the only way this can fail is if you already\n // have a gang member with the same name\n if (!gang.recruitMember(name)) {\n dialogBoxCreate(\"You already have a gang member with this name!\");\n return;\n }\n\n props.onRecruit();\n props.onClose();\n }\n\n function onKeyUp(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) recruit();\n }\n\n function onChange(event: React.ChangeEvent): void {\n setName(event.target.value);\n }\n\n return (\n \n Enter a name for your new Gang member:\n
\n \n Recruit\n \n ),\n }}\n />\n
\n );\n}\n","/**\n * React Component for the territory subpage.\n */\nimport React from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { AllGangs } from \"../AllGangs\";\nimport { useGang } from \"./Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Switch from \"@mui/material/Switch\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\nimport Paper from \"@mui/material/Paper\";\n\nexport function TerritorySubpage(): React.ReactElement {\n const gang = useGang();\n const gangNames = Object.keys(AllGangs).filter((g) => g != gang.facName);\n\n return (\n <>\n \n This page shows how much territory your Gang controls. This statistic is listed as a percentage, which\n represents how much of the total territory you control.\n
\n
\n Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance to win a clash depends on\n your gang's power, which is listed in the display below. Your gang's power slowly accumulates over time. The\n accumulation rate is determined by the stats of all Gang members you have assigned to the 'Territory Warfare'\n task. Gang members that are not assigned to this task do not contribute to your gang's power. Your gang also\n loses a small amount of power whenever you lose a clash.\n
\n
\n NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of\n whether you win or lose the clash. A gang member being killed results in both respect and power loss for your\n gang.\n
\n
\n The amount of territory you have affects all aspects of your Gang members' production, including money, respect,\n and wanted level. It is very beneficial to have high territory control.\n
\n
\n
\n (gang.territoryWarfareEngaged = event.target.checked)}\n />\n }\n label={\n \n Engaging in Territory Warfare sets your clash chance to 100%. Disengaging will cause your clash chance\n to gradually decrease until it reaches 0%.\n
\n }\n >\n Engage in Territory Warfare\n \n }\n />\n
\n \n \n This percentage represents the chance you have of 'clashing' with with another gang. If you do not wish to\n gain/lose territory, then keep this percentage at 0% by not engaging in territory warfare.\n \n }\n >\n \n Territory Clash Chance: {numeralWrapper.formatPercentage(gang.territoryClashChance, 3)}\n \n \n \n
\n (gang.notifyMemberDeath = event.target.checked)}\n />\n }\n label={\n \n If this is enabled, then you will receive a pop-up notifying you whenever one of your Gang Members dies\n in a territory clash.\n \n }\n >\n Notify about Gang Member Deaths\n \n }\n />\n
\n \n \n \n {gang.facName}\n \n
\n Power: {formatNumber(AllGangs[gang.facName].power, 6)}\n
\n Territory: {formatTerritory(AllGangs[gang.facName].territory)}%\n
\n
\n
\n {gangNames.map((name) => (\n \n ))}\n
\n \n );\n}\nfunction formatTerritory(n: number): string {\n const v = n * 100;\n if (v <= 0) {\n return formatNumber(0, 2);\n } else if (v >= 100) {\n return formatNumber(100, 2);\n } else {\n return formatNumber(v, 2);\n }\n}\n\ninterface ITerritoryProps {\n name: string;\n}\n\nfunction OtherGangTerritory(props: ITerritoryProps): React.ReactElement {\n const gang = useGang();\n const playerPower = AllGangs[gang.facName].power;\n const power = AllGangs[props.name].power;\n const clashVictoryChance = playerPower / (power + playerPower);\n return (\n \n {props.name}\n
\n Power: {formatNumber(power, 6)}\n
\n Territory: {formatTerritory(AllGangs[props.name].territory)}%
\n Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}\n
\n
\n
\n );\n}\n","/**\n * React Component for the popup that manages gang members upgrades\n */\nimport React, { useState } from \"react\";\nimport { formatNumber } from \"../../utils/StringHelperFunctions\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { GangMemberUpgrades } from \"../GangMemberUpgrades\";\nimport { GangMemberUpgrade } from \"../GangMemberUpgrade\";\nimport { Money } from \"../../ui/React/Money\";\nimport { useGang } from \"./Context\";\nimport { GangMember } from \"../GangMember\";\nimport { UpgradeType } from \"../data/upgrades\";\nimport { use } from \"../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\nimport Paper from \"@mui/material/Paper\";\n\ninterface INextRevealProps {\n upgrades: string[];\n type: UpgradeType;\n}\n\nfunction NextReveal(props: INextRevealProps): React.ReactElement {\n const gang = useGang();\n const player = use.Player();\n const upgrades = Object.keys(GangMemberUpgrades)\n .filter((upgName: string) => {\n const upg = GangMemberUpgrades[upgName];\n if (player.money.gt(gang.getUpgradeCost(upg))) return false;\n if (upg.type !== props.type) return false;\n if (props.upgrades.includes(upgName)) return false;\n return true;\n })\n .map((upgName: string) => GangMemberUpgrades[upgName]);\n\n if (upgrades.length === 0) return <>;\n return (\n \n Next at \n \n );\n}\n\nfunction PurchasedUpgrade({ upgName }: { upgName: string }): React.ReactElement {\n const upg = GangMemberUpgrades[upgName];\n return (\n \n \n }>\n {upg.name}\n \n \n \n );\n}\n\ninterface IUpgradeButtonProps {\n upg: GangMemberUpgrade;\n rerender: () => void;\n member: GangMember;\n}\n\nfunction UpgradeButton(props: IUpgradeButtonProps): React.ReactElement {\n const gang = useGang();\n const player = use.Player();\n function onClick(): void {\n props.member.buyUpgrade(props.upg, player, gang);\n props.rerender();\n }\n return (\n }>\n \n {props.upg.name}\n \n \n \n );\n}\n\ninterface IPanelProps {\n member: GangMember;\n}\n\nfunction GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {\n const gang = useGang();\n const player = use.Player();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {\n return Object.keys(GangMemberUpgrades)\n .filter((upgName: string) => {\n const upg = GangMemberUpgrades[upgName];\n if (player.money.lt(gang.getUpgradeCost(upg))) return false;\n if (upg.type !== type) return false;\n if (list.includes(upgName)) return false;\n return true;\n })\n .map((upgName: string) => GangMemberUpgrades[upgName]);\n }\n const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);\n const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);\n const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);\n const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);\n const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);\n\n const asc = {\n hack: props.member.calculateAscensionMult(props.member.hack_asc_points),\n str: props.member.calculateAscensionMult(props.member.str_asc_points),\n def: props.member.calculateAscensionMult(props.member.def_asc_points),\n dex: props.member.calculateAscensionMult(props.member.dex_asc_points),\n agi: props.member.calculateAscensionMult(props.member.agi_asc_points),\n cha: props.member.calculateAscensionMult(props.member.cha_asc_points),\n };\n return (\n \n \n {props.member.name} ({props.member.task})\n \n \n Hack: {props.member.hack} (x\n {formatNumber(props.member.hack_mult * asc.hack, 2)})
\n Str: {props.member.str} (x\n {formatNumber(props.member.str_mult * asc.str, 2)})
\n Def: {props.member.def} (x\n {formatNumber(props.member.def_mult * asc.def, 2)})
\n Dex: {props.member.dex} (x\n {formatNumber(props.member.dex_mult * asc.dex, 2)})
\n Agi: {props.member.agi} (x\n {formatNumber(props.member.agi_mult * asc.agi, 2)})
\n Cha: {props.member.cha} (x\n {formatNumber(props.member.cha_mult * asc.cha, 2)})\n
\n \n Purchased Upgrades: \n
\n {props.member.upgrades.map((upg: string) => (\n \n ))}\n {props.member.augmentations.map((upg: string) => (\n \n ))}\n
\n \n \n \n Weapons\n \n {weaponUpgrades.map((upg) => (\n \n ))}\n \n \n \n \n Armor\n \n {armorUpgrades.map((upg) => (\n \n ))}\n \n \n \n \n Vehicles\n \n {vehicleUpgrades.map((upg) => (\n \n ))}\n \n \n \n \n Rootkits\n \n {rootkitUpgrades.map((upg) => (\n \n ))}\n \n \n \n \n Augmentations\n \n {augUpgrades.map((upg) => (\n \n ))}\n \n \n \n
\n );\n}\n\nexport function EquipmentsSubpage(): React.ReactElement {\n const gang = useGang();\n return (\n <>\n \n You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power\n leads to more discounts.\n \n }\n >\n Discount: -{numeralWrapper.formatPercentage(1 - 1 / gang.getDiscount())}\n \n {gang.members.map((member: GangMember) => (\n \n ))}\n \n );\n}\n","// React Components for the Corporation UI's navigation tabs\n// These are the tabs at the top of the UI that let you switch to different\n// divisions, see an overview of your corporation, or create a new industry\nimport React, { useState, useEffect } from \"react\";\nimport { IIndustry } from \"../IIndustry\";\nimport { MainPanel } from \"./MainPanel\";\nimport { Industries } from \"../IndustryData\";\nimport { ExpandIndustryTab } from \"./ExpandIndustryTab\";\nimport { use } from \"../../ui/Context\";\nimport { Context } from \"./Context\";\nimport { Overview } from \"./Overview\";\n\nimport Tabs from \"@mui/material/Tabs\";\nimport Tab from \"@mui/material/Tab\";\n\nexport function CorporationRoot(): React.ReactElement {\n const player = use.Player();\n const corporation = player.corporation;\n if (corporation === null) return <>;\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const [divisionName, setDivisionName] = useState(\"Overview\");\n function handleChange(event: React.SyntheticEvent, tab: string | number): void {\n setDivisionName(tab);\n }\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n const canExpand =\n Object.keys(Industries).filter(\n (industryType: string) =>\n corporation.divisions.find((division: IIndustry) => division.type === industryType) === undefined,\n ).length > 0;\n\n return (\n \n \n \n {corporation.divisions.map((div) => (\n \n ))}\n {canExpand && }\n \n {divisionName === \"Overview\" && }\n {divisionName === -1 && }\n {typeof divisionName === \"string\" && divisionName !== \"Overview\" && (\n \n )}\n \n );\n}\n","// React Component for the element that contains the actual info/data\n// for the Corporation UI. This panel lies below the header tabs and will\n// be filled with whatever is needed based on the routing/navigation\nimport React from \"react\";\n\nimport { CityTabs } from \"./CityTabs\";\nimport { IIndustry } from \"../IIndustry\";\nimport { Context, useCorporation } from \"./Context\";\n\nimport { CityName } from \"../../Locations/data/CityNames\";\n\ninterface IProps {\n divisionName: string;\n rerender: () => void;\n}\n\nexport function MainPanel(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division =\n props.divisionName !== \"Overview\"\n ? corp.divisions.find((division: IIndustry) => division.name === props.divisionName)\n : undefined; // use undefined because find returns undefined\n\n if (division === undefined) throw new Error(\"Cannot find division\");\n return (\n \n \n \n );\n}\n","// React Components for the Corporation UI's City navigation tabs\n// These allow player to navigate between different cities for each industry\nimport React, { useState } from \"react\";\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { Industry } from \"./Industry\";\nimport { ExpandNewCity } from \"./ExpandNewCity\";\nimport { useDivision } from \"./Context\";\nimport Tabs from \"@mui/material/Tabs\";\nimport Tab from \"@mui/material/Tab\";\n\ninterface IProps {\n city: string;\n rerender: () => void;\n}\n\nexport function CityTabs(props: IProps): React.ReactElement {\n const division = useDivision();\n const [city, setCity] = useState(props.city);\n\n const office = division.offices[city];\n if (office === 0) {\n setCity(\"Sector-12\");\n return <>;\n }\n\n const canExpand =\n Object.keys(division.offices).filter((cityName: string) => division.offices[cityName] === 0).length > 0;\n function handleChange(event: React.SyntheticEvent, tab: string): void {\n setCity(tab);\n }\n return (\n <>\n \n {Object.values(division.offices).map(\n (office: OfficeSpace | 0) => office !== 0 && ,\n )}\n {canExpand && }\n \n\n {city !== \"Expand\" ? (\n \n ) : (\n \n )}\n \n );\n}\n","// React Component for managing the Corporation's Industry UI\n// This Industry component does NOT include the city tabs at the top\nimport React from \"react\";\n\nimport { IndustryOffice } from \"./IndustryOffice\";\nimport { IndustryOverview } from \"./IndustryOverview\";\nimport { IndustryWarehouse } from \"./IndustryWarehouse\";\nimport { Warehouse } from \"../Warehouse\";\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { use } from \"../../ui/Context\";\nimport { useCorporation, useDivision } from \"./Context\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n city: string;\n warehouse: Warehouse | 0;\n office: OfficeSpace;\n rerender: () => void;\n}\n\nexport function Industry(props: IProps): React.ReactElement {\n const player = use.Player();\n const corp = useCorporation();\n const division = useDivision();\n return (\n \n \n \n \n \n \n \n \n \n );\n}\n","// React Component for displaying an Industry's OfficeSpace information\n// (bottom-left panel in the Industry UI)\nimport React, { useState } from \"react\";\n\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { Employee } from \"../Employee\";\nimport { EmployeePositions } from \"../EmployeePositions\";\n\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\n\nimport { UpgradeOfficeSizeModal } from \"./UpgradeOfficeSizeModal\";\nimport { ThrowPartyModal } from \"./ThrowPartyModal\";\nimport { Money } from \"../../ui/React/Money\";\nimport { useCorporation, useDivision } from \"./Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport IconButton from \"@mui/material/IconButton\";\nimport Paper from \"@mui/material/Paper\";\nimport ArrowDropUpIcon from \"@mui/icons-material/ArrowDropUp\";\nimport ArrowDropDownIcon from \"@mui/icons-material/ArrowDropDown\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport Table from \"@mui/material/Table\";\nimport TableBody from \"@mui/material/TableBody\";\nimport TableRow from \"@mui/material/TableRow\";\nimport { TableCell } from \"../../ui/React/Table\";\n\ninterface IProps {\n office: OfficeSpace;\n rerender: () => void;\n}\n\nfunction countEmployee(employees: Employee[], job: string): number {\n let n = 0;\n for (let i = 0; i < employees.length; ++i) {\n if (employees[i].pos === job) n++;\n }\n return n;\n}\n\ninterface ISwitchProps {\n manualMode: boolean;\n switchMode: (f: (b: boolean) => boolean) => void;\n}\n\nfunction SwitchButton(props: ISwitchProps): React.ReactElement {\n if (props.manualMode) {\n return (\n \n Switch to Automatic Assignment Mode, which will automatically assign employees to your selected jobs. You\n simply have to select the number of assignments for each job\n \n }\n >\n \n \n );\n } else {\n return (\n \n Switch to Manual Assignment Mode, which allows you to specify which employees should get which jobs\n \n }\n >\n \n \n );\n }\n}\n\nfunction ManualManagement(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [employee, setEmployee] = useState(\n props.office.employees.length > 0 ? props.office.employees[0] : null,\n );\n\n // Employee Selector\n const employees = [];\n for (let i = 0; i < props.office.employees.length; ++i) {\n employees.push(\n \n {props.office.employees[i].name}\n ,\n );\n }\n\n function employeeSelectorOnChange(e: SelectChangeEvent): void {\n const name = e.target.value;\n for (let i = 0; i < props.office.employees.length; ++i) {\n if (name === props.office.employees[i].name) {\n setEmployee(props.office.employees[i]);\n break;\n }\n }\n\n props.rerender();\n }\n\n // Employee Positions Selector\n const emp = employee;\n let employeePositionSelectorInitialValue = \"\";\n const employeePositions = [];\n const positionNames = Object.values(EmployeePositions);\n for (let i = 0; i < positionNames.length; ++i) {\n employeePositions.push(\n \n {positionNames[i]}\n ,\n );\n if (emp != null && emp.pos === positionNames[i]) {\n employeePositionSelectorInitialValue = positionNames[i];\n }\n }\n\n function employeePositionSelectorOnChange(e: SelectChangeEvent): void {\n if (employee === null) return;\n employee.pos = e.target.value;\n props.rerender();\n }\n\n // Numeraljs formatter\n const nf = \"0.000\";\n\n // Employee stats (after applying multipliers)\n const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0;\n const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0;\n const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0;\n const effEff = emp ? emp.eff * corp.getEmployeeEffMultiplier() * division.getEmployeeEffMultiplier() : 0;\n\n return (\n <>\n
\n \n {employee != null && (\n \n Morale: {numeralWrapper.format(employee.mor, nf)}\n
\n Happiness: {numeralWrapper.format(employee.hap, nf)}\n
\n Energy: {numeralWrapper.format(employee.ene, nf)}\n
\n Intelligence: {numeralWrapper.format(effInt, nf)}\n
\n Charisma: {numeralWrapper.format(effCha, nf)}\n
\n Experience: {numeralWrapper.format(employee.exp, nf)}\n
\n Creativity: {numeralWrapper.format(effCre, nf)}\n
\n Efficiency: {numeralWrapper.format(effEff, nf)}\n
\n Salary: \n
\n )}\n {employee != null && (\n \n )}\n \n );\n}\n\ninterface IAutoAssignProps {\n office: OfficeSpace;\n job: string;\n desc: string;\n rerender: () => void;\n}\n\nfunction AutoAssignJob(props: IAutoAssignProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const numJob = countEmployee(props.office.employees, props.job);\n const numUnassigned = countEmployee(props.office.employees, EmployeePositions.Unassigned);\n function assignEmployee(): void {\n if (numUnassigned <= 0) {\n console.warn(\"Cannot assign employee. No unassigned employees available\");\n return;\n }\n\n props.office.assignEmployeeToJob(props.job);\n props.office.calculateEmployeeProductivity(corp, division);\n props.rerender();\n }\n\n function unassignEmployee(): void {\n props.office.unassignEmployeeFromJob(props.job);\n props.office.calculateEmployeeProductivity(corp, division);\n props.rerender();\n }\n return (\n \n \n \n \n {props.job} ({numJob})\n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n\nfunction AutoManagement(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const numUnassigned = countEmployee(props.office.employees, EmployeePositions.Unassigned);\n const vechain = corp.unlockUpgrades[4] === 1; // Has Vechain upgrade\n\n // Calculate average morale, happiness, energy, and salary.\n let totalMorale = 0,\n totalHappiness = 0,\n totalEnergy = 0,\n totalSalary = 0;\n for (let i = 0; i < props.office.employees.length; ++i) {\n totalMorale += props.office.employees[i].mor;\n totalHappiness += props.office.employees[i].hap;\n totalEnergy += props.office.employees[i].ene;\n totalSalary += props.office.employees[i].sal;\n }\n\n let avgMorale = 0,\n avgHappiness = 0,\n avgEnergy = 0;\n if (props.office.employees.length > 0) {\n avgMorale = totalMorale / props.office.employees.length;\n avgHappiness = totalHappiness / props.office.employees.length;\n avgEnergy = totalEnergy / props.office.employees.length;\n }\n\n return (\n <>\n \n \n \n \n Unassigned Employees:\n \n \n {numUnassigned}\n \n \n \n \n Avg Employee Morale:\n \n \n {numeralWrapper.format(avgMorale, \"0.000\")}\n \n \n \n \n Avg Employee Happiness:\n \n \n {numeralWrapper.format(avgHappiness, \"0.000\")}\n \n \n \n \n Avg Employee Energy:\n \n \n {numeralWrapper.format(avgEnergy, \"0.000\")}\n \n \n \n \n Total Employee Salary:\n \n \n \n \n \n \n \n {vechain && (\n <>\n \n \n \n The base amount of material this office can produce. Does not include production multipliers\n from upgrades and materials. This value is based off the productivity of your Operations,\n Engineering, and Management employees\n \n }\n >\n Material Production:\n \n \n \n \n {numeralWrapper.format(division.getOfficeProductivity(props.office), \"0.000\")}\n \n \n \n \n \n \n The base amount of any given Product this office can produce. Does not include production\n multipliers from upgrades and materials. This value is based off the productivity of your\n Operations, Engineering, and Management employees\n \n }\n >\n Product Production:\n \n \n \n \n {numeralWrapper.format(\n division.getOfficeProductivity(props.office, {\n forProduct: true,\n }),\n \"0.000\",\n )}\n \n \n \n \n \n The effect this office's 'Business' employees has on boosting sales}\n >\n Business Multiplier:\n \n \n \n x{numeralWrapper.format(division.getBusinessFactor(props.office), \"0.000\")}\n \n \n \n )}\n \n
\n\n \n \n \n\n \n\n \n\n \n\n \n\n \n \n
\n \n );\n}\n\nexport function IndustryOffice(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [upgradeOfficeSizeOpen, setUpgradeOfficeSizeOpen] = useState(false);\n const [throwPartyOpen, setThrowPartyOpen] = useState(false);\n const [employeeManualAssignMode, setEmployeeManualAssignMode] = useState(false);\n\n function autohireEmployeeButtonOnClick(): void {\n if (props.office.atCapacity()) return;\n props.office.hireRandomEmployee();\n props.rerender();\n }\n\n return (\n \n Office Space\n \n Size: {props.office.employees.length} / {props.office.size} employees\n \n Automatically hires an employee and gives him/her a random name}>\n \n \n \n \n
\n Upgrade the office's size so that it can hold more employees!}>\n \n \n \n \n setUpgradeOfficeSizeOpen(false)}\n />\n\n {!division.hasResearch(\"AutoPartyManager\") && (\n <>\n Throw an office party to increase your employee's morale and happiness}\n >\n \n \n \n \n setThrowPartyOpen(false)}\n />\n \n )}\n\n
\n\n \n {employeeManualAssignMode ? (\n \n ) : (\n \n )}\n
\n );\n}\n","import React from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { ICorporation } from \"../ICorporation\";\nimport { UpgradeOfficeSize } from \"../Actions\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\n\ninterface IUpgradeButton {\n cost: number;\n size: number;\n corp: ICorporation;\n office: OfficeSpace;\n onClose: () => void;\n rerender: () => void;\n}\n\nfunction UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {\n const corp = useCorporation();\n function upgradeSize(cost: number, size: number): void {\n if (corp.funds.lt(cost)) {\n return;\n }\n\n UpgradeOfficeSize(corp, props.office, size);\n props.rerender();\n props.onClose();\n }\n return (\n \n \n \n \n \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n office: OfficeSpace;\n rerender: () => void;\n}\n\nexport function UpgradeOfficeSizeModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const initialPriceMult = Math.round(props.office.size / CorporationConstants.OfficeInitialSize);\n const costMultiplier = 1.09;\n const upgradeCost = CorporationConstants.OfficeInitialCost * Math.pow(costMultiplier, initialPriceMult);\n\n // Calculate cost to upgrade size by 15 employees\n let mult = 0;\n for (let i = 0; i < 5; ++i) {\n mult += Math.pow(costMultiplier, initialPriceMult + i);\n }\n const upgradeCost15 = CorporationConstants.OfficeInitialCost * mult;\n\n //Calculate max upgrade size and cost\n const maxMult = corp.funds.dividedBy(CorporationConstants.OfficeInitialCost).toNumber();\n let maxNum = 1;\n mult = Math.pow(costMultiplier, initialPriceMult);\n while (maxNum < 50) {\n //Hard cap of 50x (extra 150 employees)\n if (mult >= maxMult) break;\n const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);\n if (mult + multIncrease > maxMult) {\n break;\n } else {\n mult += multIncrease;\n }\n ++maxNum;\n }\n const upgradeCostMax = CorporationConstants.OfficeInitialCost * mult;\n\n return (\n \n Increase the size of your office space to fit additional employees!\n \n Upgrade size: \n \n \n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { ThrowParty } from \"../Actions\";\nimport { Money } from \"../../ui/React/Money\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n office: OfficeSpace;\n rerender: () => void;\n}\n\nexport function ThrowPartyModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const [cost, setCost] = useState(0);\n\n const totalCost = cost * props.office.employees.length;\n const canParty = corp.funds.gte(totalCost);\n function changeCost(event: React.ChangeEvent): void {\n let x = parseFloat(event.target.value);\n if (isNaN(x)) x = 0;\n setCost(x);\n }\n\n function throwParty(): void {\n if (cost === null || isNaN(cost) || cost < 0) {\n dialogBoxCreate(\"Invalid value entered\");\n } else {\n if (!canParty) {\n dialogBoxCreate(\"You don't have enough company funds to throw a party!\");\n } else {\n const mult = ThrowParty(corp, props.office, cost);\n dialogBoxCreate(\n \"You threw a party for the office! The morale and happiness \" +\n \"of each employee increased by \" +\n numeralWrapper.formatPercentage(mult - 1),\n );\n props.rerender();\n props.onClose();\n }\n }\n }\n\n function EffectText(): React.ReactElement {\n if (isNaN(cost) || cost < 0) return Invalid value entered!;\n return (\n \n Throwing this party will cost a total of \n \n );\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) throwParty();\n }\n\n return (\n \n Enter the amount of money you would like to spend PER EMPLOYEE on this office party\n \n \n \n \n \n \n );\n}\n","// React Component for displaying an Industry's overview information\n// (top-left panel in the Industry UI)\nimport React, { useState } from \"react\";\n\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { Industries } from \"../IndustryData\";\nimport { IndustryUpgrades } from \"../IndustryUpgrades\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { createProgressBarText } from \"../../utils/helpers/createProgressBarText\";\nimport { MakeProductModal } from \"./MakeProductModal\";\nimport { ResearchPopup } from \"./ResearchPopup\";\nimport { createPopup } from \"../../ui/React/createPopup\";\nimport { Money } from \"../../ui/React/Money\";\nimport { MoneyRate } from \"../../ui/React/MoneyRate\";\nimport { StatsTable } from \"../../ui/React/StatsTable\";\nimport { StaticModal } from \"../../ui/React/StaticModal\";\nimport { MoneyCost } from \"./MoneyCost\";\nimport { useCorporation, useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Paper from \"@mui/material/Paper\";\nimport IconButton from \"@mui/material/IconButton\";\nimport HelpIcon from \"@mui/icons-material/Help\";\nimport Box from \"@mui/material/Box\";\n\nfunction MakeProductButton(): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [makeOpen, setMakeOpen] = useState(false);\n\n const hasMaxProducts = division.hasMaximumNumberProducts();\n\n function shouldFlash(): boolean {\n return Object.keys(division.products).length === 0;\n }\n\n let createProductButtonText = \"\";\n switch (division.type) {\n case Industries.Food:\n createProductButtonText = \"Build Restaurant\";\n break;\n case Industries.Tobacco:\n createProductButtonText = \"Create Product\";\n break;\n case Industries.Pharmaceutical:\n createProductButtonText = \"Create Drug\";\n break;\n case Industries.Computer:\n case \"Computer\":\n createProductButtonText = \"Create Product\";\n break;\n case Industries.Robotics:\n createProductButtonText = \"Design Robot\";\n break;\n case Industries.Software:\n createProductButtonText = \"Develop Software\";\n break;\n case Industries.Healthcare:\n createProductButtonText = \"Build Hospital\";\n break;\n case Industries.RealEstate:\n createProductButtonText = \"Develop Property\";\n break;\n default:\n createProductButtonText = \"Create Product\";\n return <>;\n }\n\n return (\n <>\n \n You have reached the maximum number of products: {division.getMaximumNumberProducts()}\n \n ) : (\n \"\"\n )\n }\n >\n setMakeOpen(true)}\n disabled={corp.funds.lt(0)}\n >\n {createProductButtonText}\n \n \n setMakeOpen(false)} />\n \n );\n}\nfunction Text(): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [helpOpen, setHelpOpen] = useState(false);\n const vechain = corp.unlockUpgrades[4] === 1;\n const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();\n\n let advertisingInfo = false;\n const advertisingFactors = division.getAdvertisingFactors();\n const awarenessFac = advertisingFactors[1];\n const popularityFac = advertisingFactors[2];\n const ratioFac = advertisingFactors[3];\n const totalAdvertisingFac = advertisingFactors[0];\n if (vechain) {\n advertisingInfo = true;\n }\n\n function convertEffectFacToGraphic(fac: number): string {\n return createProgressBarText({\n progress: fac,\n totalTicks: 20,\n });\n }\n\n function openResearchPopup(): void {\n const popupId = \"corporation-research-popup-box\";\n createPopup(popupId, ResearchPopup, {\n industry: division,\n popupId: popupId,\n });\n }\n\n return (\n <>\n \n Industry: {division.type} (Corp Funds: )\n \n
\n \n {advertisingInfo !== false && (\n \n Total multiplier for this industrys sales due to its awareness and popularity\n \n \n }\n >\n Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, \"0.000\")}\n \n )}\n
\n ],\n [\"Expenses:\", ],\n [\"Profit:\", ],\n ]}\n />\n
\n \n \n Production gain from owning production-boosting materials such as hardware, Robots, AI Cores, and Real\n Estate.\n \n }\n >\n Production Multiplier: {numeralWrapper.format(division.prodMult, \"0.00\")}\n \n setHelpOpen(true)}>\n \n \n setHelpOpen(false)}>\n \n Owning Hardware, Robots, AI Cores, and Real Estate can boost your Industry's production. The effect these\n materials have on your production varies between Industries. For example, Real Estate may be very effective\n for some Industries, but ineffective for others.\n
\n
\n This division's production multiplier is calculated by summing the individual production multiplier of each\n of its office locations. This production multiplier is applied to each office. Therefore, it is beneficial\n to expand into new cities as this can greatly increase the production multiplier of your entire Division.\n
\n
\n Below are approximations for how effective each material is at boosting this industry's production\n multiplier (Bigger bars = more effective):\n
\n
\n Hardware:    {convertEffectFacToGraphic(division.hwFac)}\n
\n Robots:      {convertEffectFacToGraphic(division.robFac)}\n
\n AI Cores:    {convertEffectFacToGraphic(division.aiFac)}\n
\n Real Estate: {convertEffectFacToGraphic(division.reFac)}\n
\n
\n
\n\n \n \n Scientific Research increases the quality of the materials and products that you produce.\n \n }\n >\n Scientific Research: {numeralWrapper.format(division.sciResearch.qty, \"0.000a\")}\n \n \n \n \n );\n}\n\nfunction Upgrades(props: { office: OfficeSpace; rerender: () => void }): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const upgrades = [];\n for (const index in IndustryUpgrades) {\n const upgrade = IndustryUpgrades[index];\n\n // AutoBrew research disables the Coffee upgrade\n if (division.hasResearch(\"AutoBrew\") && upgrade[4] === \"Coffee\") {\n continue;\n }\n\n const i = upgrade[0];\n const baseCost = upgrade[1];\n const priceMult = upgrade[2];\n let cost = 0;\n switch (i) {\n case 0: //Coffee, cost is static per employee\n cost = props.office.employees.length * baseCost;\n break;\n default:\n cost = baseCost * Math.pow(priceMult, division.upgrades[i]);\n break;\n }\n\n function onClick(): void {\n if (corp.funds.lt(cost)) return;\n corp.funds = corp.funds.minus(cost);\n division.upgrade(upgrade, {\n corporation: corp,\n office: props.office,\n });\n props.rerender();\n }\n\n upgrades.push(\n \n \n \n \n ,\n );\n }\n\n return <>{upgrades};\n}\n\ninterface IProps {\n currentCity: string;\n office: OfficeSpace;\n rerender: () => void;\n}\n\nexport function IndustryOverview(props: IProps): React.ReactElement {\n const division = useDivision();\n\n return (\n \n \n
\n Purchases & Upgrades\n
\n {division.makesProducts && }\n
\n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { Industries } from \"../IndustryData\";\nimport { MakeProduct } from \"../Actions\";\nimport { useCorporation, useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\nfunction productPlaceholder(tpe: string): string {\n if (tpe === Industries.Food) {\n return \"Restaurant Name\";\n } else if (tpe === Industries.Healthcare) {\n return \"Hospital Name\";\n } else if (tpe === Industries.RealEstate) {\n return \"Property Name\";\n }\n return \"Product Name\";\n}\n\n// Create a popup that lets the player create a product for their current industry\nexport function MakeProductModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const allCities = Object.keys(division.offices).filter((cityName: string) => division.offices[cityName] !== 0);\n const [city, setCity] = useState(allCities.length > 0 ? allCities[0] : \"\");\n const [name, setName] = useState(\"\");\n const [design, setDesign] = useState(null);\n const [marketing, setMarketing] = useState(null);\n if (division.hasMaximumNumberProducts()) return <>;\n\n let createProductPopupText = <>;\n switch (division.type) {\n case Industries.Food:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Build and manage a new restaurant!\n \n );\n break;\n case Industries.Tobacco:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Create a new tobacco product!\n \n );\n break;\n case Industries.Pharmaceutical:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Design and develop a new pharmaceutical drug!\n \n );\n break;\n case Industries.Computer:\n case \"Computer\":\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Design and manufacture a new computer hardware product!\n \n );\n break;\n case Industries.Robotics:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Design and create a new robot or robotic system!\n \n );\n break;\n case Industries.Software:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Develop a new piece of software!\n \n );\n break;\n case Industries.Healthcare:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Build and manage a new hospital!\n \n );\n break;\n case Industries.RealEstate:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Develop a new piece of real estate property!\n \n );\n break;\n default:\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n Create a new product!\n \n );\n return <>;\n }\n createProductPopupText = (\n <>\n {createProductPopupText}\n
\n
\n To begin developing a product, first choose the city in which it will be designed. The stats of your employees in\n the selected city affect the properties of the finished product, such as its quality, performance, and durability.\n
\n
\n You can also choose to invest money in the design and marketing of the product. Investing money in its design will\n result in a superior product. Investing money in marketing the product will help the product's sales.\n \n );\n\n function makeProduct(): void {\n if (design === null || marketing === null) return;\n try {\n MakeProduct(corp, division, city, name, design, marketing);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n props.onClose();\n }\n\n function onCityChange(event: SelectChangeEvent): void {\n setCity(event.target.value);\n }\n\n function onProductNameChange(event: React.ChangeEvent): void {\n setName(event.target.value);\n }\n\n function onDesignChange(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setDesign(null);\n else setDesign(parseFloat(event.target.value));\n }\n\n function onMarketingChange(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setMarketing(null);\n else setMarketing(parseFloat(event.target.value));\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) makeProduct();\n }\n\n return (\n \n {createProductPopupText}\n \n \n
\n \n \n \n
\n );\n}\n","import React, { useEffect } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { removePopup } from \"../../ui/React/createPopup\";\nimport { IndustryResearchTrees } from \"../IndustryData\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { Treant } from \"treant-js\";\nimport { IIndustry } from \"../IIndustry\";\nimport { Research } from \"../Actions\";\n\ninterface IProps {\n industry: IIndustry;\n popupId: string;\n}\n\n// Create the Research Tree UI for this Industry\nexport function ResearchPopup(props: IProps): React.ReactElement {\n const researchTree = IndustryResearchTrees[props.industry.type];\n if (researchTree === undefined) return <>;\n useEffect(() => {\n {\n const boxContent = document.getElementById(`${props.popupId}-content`);\n if (boxContent != null) {\n boxContent.style.minHeight = \"80vh\";\n }\n }\n\n // Get the tree's markup (i.e. config) for Treant\n const markup = researchTree.createTreantMarkup();\n markup.chart.container = \"#\" + props.popupId + \"-content\";\n markup.chart.nodeAlign = \"BOTTOM\";\n markup.chart.rootOrientation = \"WEST\";\n markup.chart.siblingSeparation = 40;\n markup.chart.connectors = {\n type: \"step\",\n style: {\n \"arrow-end\": \"block-wide-long\",\n stroke: \"white\",\n \"stroke-width\": 2,\n },\n };\n\n Treant(markup);\n\n // Add Event Listeners for all Nodes\n const allResearch = researchTree.getAllNodes();\n for (let i = 0; i < allResearch.length; ++i) {\n // If this is already Researched, skip it\n if (props.industry.researched[allResearch[i]] === true) {\n continue;\n }\n\n // Get the DOM Element to add a click listener to it\n const sanitizedName = allResearch[i].replace(/\\s/g, \"\");\n const div = document.getElementById(sanitizedName + \"-corp-research-click-listener\");\n if (div == null) {\n console.warn(`Could not find Research Tree div for ${sanitizedName}`);\n continue;\n }\n\n div.addEventListener(\"click\", () => {\n try {\n Research(props.industry, allResearch[i]);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n return;\n }\n\n dialogBoxCreate(\n `Researched ${allResearch[i]}. It may take a market cycle ` +\n `(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +\n `the Research apply.`,\n );\n removePopup(props.popupId);\n });\n }\n });\n\n return (\n
\n
\n Research points: {props.industry.sciResearch.qty}\n
\n Multipliers from research:\n
* Advertising Multiplier: x{researchTree.getAdvertisingMultiplier()}\n
* Employee Charisma Multiplier: x{researchTree.getEmployeeChaMultiplier()}\n
* Employee Creativity Multiplier: x{researchTree.getEmployeeCreMultiplier()}\n
* Employee Efficiency Multiplier: x{researchTree.getEmployeeEffMultiplier()}\n
* Employee Intelligence Multiplier: x{researchTree.getEmployeeIntMultiplier()}\n
* Production Multiplier: x{researchTree.getProductionMultiplier()}\n
* Sales Multiplier: x{researchTree.getSalesMultiplier()}\n
* Scientific Research Multiplier: x{researchTree.getScientificResearchMultiplier()}\n
* Storage Multiplier: x{researchTree.getStorageMultiplier()}\n
\n
\n );\n}\n","/**\n * React component for a popup content container\n *\n * Takes in a prop for rendering the content inside the popup\n */\nimport React, { useEffect } from \"react\";\n\ninterface IProps {\n content: (props: T) => React.ReactElement;\n id: string;\n props: T;\n removePopup: () => void;\n}\n\nexport function Popup(props: IProps): React.ReactElement {\n function keyDown(event: KeyboardEvent): void {\n if (event.key === \"Escape\") props.removePopup();\n }\n\n useEffect(() => {\n document.addEventListener(\"keydown\", keyDown);\n return () => {\n document.removeEventListener(\"keydown\", keyDown);\n };\n });\n\n return (\n
\n {React.createElement(props.content, props.props)}\n
\n );\n}\n","/**\n * For a given element, this function removes it AND its children\n * @param elem The element to remove.\n */\nexport function removeElement(elem: Element | null): void {\n if (elem === null) {\n // tslint:disable-next-line:no-console\n console.debug(\"The element passed into 'removeElement' was null.\");\n\n return;\n }\n if (!(elem instanceof Element)) {\n // tslint:disable-next-line:no-console\n console.debug(\"The element passed into 'removeElement' was not an instance of an Element.\");\n\n return;\n }\n\n while (elem.firstChild !== null) {\n elem.removeChild(elem.firstChild);\n }\n\n if (elem.parentNode !== null) {\n elem.parentNode.removeChild(elem);\n }\n}\n","// React Component for displaying an Industry's warehouse information\n// (right-side panel in the Industry UI)\nimport React, { useState } from \"react\";\n\nimport { CorporationConstants } from \"../data/Constants\";\nimport { Material } from \"../Material\";\nimport { Product } from \"../Product\";\nimport { Warehouse } from \"../Warehouse\";\nimport { SmartSupplyModal } from \"./SmartSupplyModal\";\nimport { ProductElem } from \"./ProductElem\";\nimport { MaterialElem } from \"./MaterialElem\";\nimport { MaterialSizes } from \"../MaterialSizes\";\n\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\n\nimport { ICorporation } from \"../ICorporation\";\nimport { IIndustry } from \"../IIndustry\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { MoneyCost } from \"./MoneyCost\";\nimport { isRelevantMaterial } from \"./Helpers\";\nimport { IndustryProductEquation } from \"./IndustryProductEquation\";\nimport { PurchaseWarehouse } from \"../Actions\";\nimport { useCorporation, useDivision } from \"./Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n corp: ICorporation;\n division: IIndustry;\n warehouse: Warehouse | 0;\n currentCity: string;\n player: IPlayer;\n rerender: () => void;\n}\n\nfunction WarehouseRoot(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [smartSupplyOpen, setSmartSupplyOpen] = useState(false);\n if (props.warehouse === 0) return <>;\n\n // Upgrade Warehouse size button\n const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, props.warehouse.level + 1);\n const canAffordUpgrade = corp.funds.gt(sizeUpgradeCost);\n function upgradeWarehouseOnClick(): void {\n if (division === null) return;\n if (props.warehouse === 0) return;\n if (!canAffordUpgrade) return;\n ++props.warehouse.level;\n props.warehouse.updateSize(corp, division);\n corp.funds = corp.funds.minus(sizeUpgradeCost);\n props.rerender();\n }\n\n // Current State:\n let stateText;\n switch (division.state) {\n case \"START\":\n stateText = \"Current state: Preparing...\";\n break;\n case \"PURCHASE\":\n stateText = \"Current state: Purchasing materials...\";\n break;\n case \"PRODUCTION\":\n stateText = \"Current state: Producing materials and/or products...\";\n break;\n case \"SALE\":\n stateText = \"Current state: Selling materials and/or products...\";\n break;\n case \"EXPORT\":\n stateText = \"Current state: Exporting materials and/or products...\";\n break;\n default:\n console.error(`Invalid state: ${division.state}`);\n break;\n }\n\n // Create React components for materials\n const mats = [];\n for (const matName in props.warehouse.materials) {\n if (!(props.warehouse.materials[matName] instanceof Material)) continue;\n // Only create UI for materials that are relevant for the industry\n if (!isRelevantMaterial(matName, division)) continue;\n mats.push(\n ,\n );\n }\n\n // Create React components for products\n const products = [];\n if (division.makesProducts && Object.keys(division.products).length > 0) {\n for (const productName in division.products) {\n const product = division.products[productName];\n if (!(product instanceof Product)) continue;\n products.push(\n ,\n );\n }\n }\n\n let breakdown = <>;\n for (const matName in props.warehouse.materials) {\n if (matName === \"RealEstate\") continue;\n const mat = props.warehouse.materials[matName];\n if (!MaterialSizes.hasOwnProperty(matName)) continue;\n if (mat.qty === 0) continue;\n breakdown = (\n <>\n {breakdown}\n {matName}: {numeralWrapper.format(mat.qty * MaterialSizes[matName], \"0,0.0\")}\n
\n \n );\n }\n\n return (\n \n \n \n = props.warehouse.size ? \"error\" : \"primary\"}>\n Storage: {numeralWrapper.formatBigNumber(props.warehouse.sizeUsed)} /{\" \"}\n {numeralWrapper.formatBigNumber(props.warehouse.size)}\n \n \n\n \n \n\n This industry uses the following equation for it's production: \n
\n \n \n \n
\n \n To get started with production, purchase your required materials or import them from another of your company's\n divisions.\n \n
\n\n {stateText}\n\n {corp.unlockUpgrades[1] && (\n <>\n \n setSmartSupplyOpen(false)}\n warehouse={props.warehouse}\n />\n \n )}\n\n {mats}\n\n {products}\n
\n );\n}\n\nexport function IndustryWarehouse(props: IProps): React.ReactElement {\n if (props.warehouse instanceof Warehouse) {\n return ;\n } else {\n return ;\n }\n}\n\ninterface IEmptyProps {\n city: string;\n rerender: () => void;\n}\n\nfunction EmptyWarehouse(props: IEmptyProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const disabled = corp.funds.lt(CorporationConstants.WarehouseInitialCost);\n function purchaseWarehouse(): void {\n if (disabled) return;\n PurchaseWarehouse(corp, division, props.city);\n props.rerender();\n }\n return (\n \n \n \n );\n}\n","import React, { useState } from \"react\";\n\nimport { Warehouse } from \"../Warehouse\";\nimport { SetSmartSupply, SetSmartSupplyUseLeftovers } from \"../Actions\";\nimport { Material } from \"../Material\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Switch from \"@mui/material/Switch\";\n\ninterface ILeftoverProps {\n matName: string;\n warehouse: Warehouse;\n}\n\nfunction Leftover(props: ILeftoverProps): React.ReactElement {\n const [checked, setChecked] = useState(!!props.warehouse.smartSupplyUseLeftovers[props.matName]);\n\n function onChange(event: React.ChangeEvent): void {\n try {\n const material = props.warehouse.materials[props.matName];\n SetSmartSupplyUseLeftovers(props.warehouse, material, event.target.checked);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n setChecked(event.target.checked);\n }\n\n return (\n <>\n }\n label={{props.warehouse.materials[props.matName].name}}\n />\n
\n \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n warehouse: Warehouse;\n}\n\nexport function SmartSupplyModal(props: IProps): React.ReactElement {\n const division = useDivision();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n // Smart Supply Checkbox\n function smartSupplyOnChange(e: React.ChangeEvent): void {\n SetSmartSupply(props.warehouse, e.target.checked);\n rerender();\n }\n\n // Create React components for materials\n const mats = [];\n for (const matName in props.warehouse.materials) {\n if (!(props.warehouse.materials[matName] instanceof Material)) continue;\n if (!Object.keys(division.reqMats).includes(matName)) continue;\n mats.push();\n }\n\n return (\n \n <>\n }\n label={Enable Smart Supply}\n />\n
\n Use materials already in the warehouse instead of buying new ones, if available:\n {mats}\n \n
\n );\n}\n","import React, { useState } from \"react\";\n\nimport { CorporationConstants } from \"../data/Constants\";\nimport { Product } from \"../Product\";\nimport { DiscontinueProductModal } from \"./DiscontinueProductModal\";\nimport { LimitProductProductionModal } from \"./LimitProductProductionModal\";\nimport { SellProductModal } from \"./SellProductModal\";\nimport { ProductMarketTaModal } from \"./ProductMarketTaModal\";\n\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\n\nimport { isString } from \"../../utils/helpers/isString\";\nimport { Money } from \"../../ui/React/Money\";\nimport { useCorporation, useDivision } from \"./Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProductProps {\n city: string;\n product: Product;\n rerender: () => void;\n}\n\n// Creates the UI for a single Product type\nexport function ProductElem(props: IProductProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [sellOpen, setSellOpen] = useState(false);\n const [limitOpen, setLimitOpen] = useState(false);\n const [discontinueOpen, setDiscontinueOpen] = useState(false);\n const [marketTaOpen, setMarketTaOpen] = useState(false);\n const city = props.city;\n const product = props.product;\n\n // Numeraljs formatters\n const nf = \"0.000\";\n const nfB = \"0.000a\"; // For numbers that might be big\n\n const hasUpgradeDashboard = division.hasResearch(\"uPgrade: Dashboard\");\n\n // Total product gain = production - sale\n const totalGain = product.data[city][1] - product.data[city][2];\n\n // Sell button\n let sellButtonText: JSX.Element;\n if (product.sllman[city][0]) {\n if (isString(product.sllman[city][1])) {\n sellButtonText = (\n <>\n Sell ({numeralWrapper.format(product.data[city][2], nfB)}/{product.sllman[city][1]})\n \n );\n } else {\n sellButtonText = (\n <>\n Sell ({numeralWrapper.format(product.data[city][2], nfB)}/\n {numeralWrapper.format(product.sllman[city][1], nfB)})\n \n );\n }\n } else {\n sellButtonText = <>Sell (0.000/0.000);\n }\n\n if (product.marketTa2) {\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n } else if (product.marketTa1) {\n const markupLimit = product.rat / product.mku;\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n } else if (product.sCost) {\n if (isString(product.sCost)) {\n const sCost = (product.sCost as string).replace(/MP/g, product.pCost + \"\");\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n } else {\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n }\n }\n\n // Limit Production button\n let limitProductionButtonText = \"Limit Production\";\n if (product.prdman[city][0]) {\n limitProductionButtonText += \" (\" + numeralWrapper.format(product.prdman[city][1], nf) + \")\";\n }\n\n return (\n \n {!product.fin ? (\n <>\n \n Designing {product.name} (req. Operations/Engineers in {product.createCity})...\n \n
\n {numeralWrapper.format(product.prog, \"0.00\")}% complete\n \n ) : (\n <>\n \n \n Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s\n
\n Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s\n \n }\n >\n \n {product.name}: {numeralWrapper.format(product.data[city][0], nfB)} (\n {numeralWrapper.format(totalGain, nfB)}\n /s)\n \n \n
\n \n \n Quality: {numeralWrapper.format(product.qlt, nf)}
\n Performance: {numeralWrapper.format(product.per, nf)}
\n Durability: {numeralWrapper.format(product.dur, nf)}
\n Reliability: {numeralWrapper.format(product.rel, nf)}
\n Aesthetics: {numeralWrapper.format(product.aes, nf)}
\n Features: {numeralWrapper.format(product.fea, nf)}\n {corp.unlockUpgrades[2] === 1 &&
}\n {corp.unlockUpgrades[2] === 1 && \"Demand: \" + numeralWrapper.format(product.dmd, nf)}\n {corp.unlockUpgrades[3] === 1 &&
}\n {corp.unlockUpgrades[3] === 1 && \"Competition: \" + numeralWrapper.format(product.cmp, nf)}\n \n }\n >\n Rating: {numeralWrapper.format(product.rat, nf)}\n \n
\n \n An estimate of the material cost it takes to create this Product.}>\n \n Est. Production Cost:{\" \"}\n {numeralWrapper.formatMoney(product.pCost / CorporationConstants.ProductProductionCostRatio)}\n \n \n \n \n \n An estimate of how much consumers are willing to pay for this product. Setting the sale price above\n this may result in less sales. Setting the sale price below this may result in more sales.\n \n }\n >\n Est. Market Price: {numeralWrapper.formatMoney(product.pCost)}\n \n \n \n )}\n\n {(hasUpgradeDashboard || product.fin) && (\n <>\n \n setSellOpen(false)} />\n
\n \n setLimitOpen(false)}\n />\n \n\n setDiscontinueOpen(false)}\n />\n {division.hasResearch(\"Market-TA.I\") && (\n <>\n \n setMarketTaOpen(false)} />\n \n )}\n \n )}\n
\n );\n}\n","import React from \"react\";\n\nimport { Product } from \"../Product\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n product: Product;\n rerender: () => void;\n}\n\n// Create a popup that lets the player discontinue a product\nexport function DiscontinueProductModal(props: IProps): React.ReactElement {\n const division = useDivision();\n function discontinue(): void {\n division.discontinueProduct(props.product);\n props.onClose();\n props.rerender();\n }\n\n return (\n \n \n Are you sure you want to do this? Discontinuing a product removes it completely and permanently. You will no\n longer produce this product and all of its existing stock will be removed and left unsold\n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { Product } from \"../Product\";\nimport { LimitProductProduction } from \"../Actions\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n product: Product;\n city: string;\n}\n\n// Create a popup that lets the player limit the production of a product\nexport function LimitProductProductionModal(props: IProps): React.ReactElement {\n const [limit, setLimit] = useState(null);\n\n function limitProductProduction(): void {\n let qty = limit;\n if (qty === null) qty = -1;\n LimitProductProduction(props.product, props.city, qty);\n props.onClose();\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) limitProductProduction();\n }\n\n function onChange(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setLimit(null);\n else setLimit(parseFloat(event.target.value));\n }\n\n return (\n \n \n Enter a limit to the amount of this product you would like to product per second. Leave the box empty to set no\n limit.\n \n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Product } from \"../Product\";\nimport { SellProduct } from \"../Actions\";\nimport { Modal } from \"../../ui/React/Modal\";\n\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Switch from \"@mui/material/Switch\";\n\nfunction initialPrice(product: Product): string {\n let val = product.sCost ? product.sCost + \"\" : \"\";\n if (product.marketTa2) {\n val += \" (Market-TA.II)\";\n } else if (product.marketTa1) {\n val += \" (Market-TA.I)\";\n }\n return val;\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n product: Product;\n city: string;\n}\n\n// Create a popup that let the player manage sales of a material\nexport function SellProductModal(props: IProps): React.ReactElement {\n const [checked, setChecked] = useState(true);\n const [iQty, setQty] = useState(\n props.product.sllman[props.city][1] ? props.product.sllman[props.city][1] : \"\",\n );\n const [px, setPx] = useState(initialPrice(props.product));\n\n function onCheckedChange(event: React.ChangeEvent): void {\n setChecked(event.target.checked);\n }\n\n function sellProduct(): void {\n try {\n SellProduct(props.product, props.city, iQty, px, checked);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n\n props.onClose();\n }\n\n function onAmtChange(event: React.ChangeEvent): void {\n setQty(event.target.value);\n }\n\n function onPriceChange(event: React.ChangeEvent): void {\n setPx(event.target.value);\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) sellProduct();\n }\n\n return (\n \n \n Enter the maximum amount of {props.product.name} you would like to sell per second, as well as the price at\n which you would like to sell it at.\n
\n
\n If the sell amount is set to 0, then the product will not be sold. If the sell price is set to 0, then the\n product will be discarded.\n
\n
\n Setting the sell amount to 'MAX' will result in you always selling the maximum possible amount of the material.\n
\n
\n When setting the sell amount, you can use the 'PROD' variable to designate a dynamically changing amount that\n depends on your production. For example, if you set the sell amount to 'PROD-1' then you will always sell 1 less\n of the material than you produce.\n
\n
\n When setting the sell price, you can use the 'MP' variable to set a dynamically changing price that depends on\n the Product's estimated market price. For example, if you set it to 'MP*5' then it will always be sold at five\n times the estimated market price.\n
\n
\n \n \n \n }\n label={Use same 'Sell Amount' for all cities}\n />\n
\n );\n}\n","import React, { useState } from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Product } from \"../Product\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Switch from \"@mui/material/Switch\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\ninterface ITa2Props {\n product: Product;\n}\n\nfunction MarketTA2(props: ITa2Props): React.ReactElement {\n const division = useDivision();\n if (!division.hasResearch(\"Market-TA.II\")) return <>;\n const markupLimit = props.product.rat / props.product.mku;\n const [value, setValue] = useState(props.product.pCost);\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n function onChange(event: React.ChangeEvent): void {\n setValue(parseFloat(event.target.value));\n }\n\n function onCheckedChange(event: React.ChangeEvent): void {\n props.product.marketTa2 = event.target.checked;\n rerender();\n }\n\n const sCost = value;\n let markup = 1;\n if (sCost > props.product.pCost) {\n if (sCost - props.product.pCost > markupLimit) {\n markup = markupLimit / (sCost - props.product.pCost);\n }\n }\n\n return (\n <>\n Market-TA.II\n
\n \n If you sell at {numeralWrapper.formatMoney(sCost)}, then you will sell{\" \"}\n {numeralWrapper.format(markup, \"0.00000\")}x as much compared to if you sold at market price.\n \n \n
\n }\n label={\n \n If this is enabled, then this Material will automatically be sold at the optimal price such that the\n amount sold matches the amount produced. (i.e. the highest possible price, while still ensuring that all\n produced materials will be sold)\n \n }\n >\n Use Market-TA.II for Auto-Sale Price\n \n }\n />\n\n \n Note that Market-TA.II overrides Market-TA.I. This means that if both are enabled, then Market-TA.II will take\n effect, not Market-TA.I\n \n \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n product: Product;\n}\n\n// Create a popup that lets the player use the Market TA research for Products\nexport function ProductMarketTaModal(props: IProps): React.ReactElement {\n const markupLimit = props.product.rat / props.product.mku;\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n function onChange(event: React.ChangeEvent): void {\n props.product.marketTa1 = event.target.checked;\n rerender();\n }\n\n return (\n \n Market-TA.I\n \n The maximum sale price you can mark this up to is{\" \"}\n {numeralWrapper.formatMoney(props.product.pCost + markupLimit)}. This means that if you set the sale price\n higher than this, you will begin to experience a loss in number of sales\n \n\n }\n label={\n \n If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I\n (i.e. the price shown above)\n \n }\n >\n Use Market-TA.I for Auto-Sale Price\n \n }\n />\n\n \n \n );\n}\n","// React Component for displaying an Industry's warehouse information\n// (right-side panel in the Industry UI)\nimport React, { useState } from \"react\";\n\nimport { OfficeSpace } from \"../OfficeSpace\";\nimport { Material } from \"../Material\";\nimport { Warehouse } from \"../Warehouse\";\nimport { ExportModal } from \"./ExportModal\";\nimport { MaterialMarketTaModal } from \"./MaterialMarketTaModal\";\nimport { SellMaterialModal } from \"./SellMaterialModal\";\nimport { PurchaseMaterialModal } from \"./PurchaseMaterialModal\";\n\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\n\nimport { isString } from \"../../utils/helpers/isString\";\nimport { Money } from \"../../ui/React/Money\";\nimport { useCorporation, useDivision } from \"./Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\n\ninterface IMaterialProps {\n warehouse: Warehouse;\n city: string;\n mat: Material;\n rerender: () => void;\n}\n\n// Creates the UI for a single Material type\nexport function MaterialElem(props: IMaterialProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const [purchaseMaterialOpen, setPurchaseMaterialOpen] = useState(false);\n const [exportOpen, setExportOpen] = useState(false);\n const [sellMaterialOpen, setSellMaterialOpen] = useState(false);\n const [materialMarketTaOpen, setMaterialMarketTaOpen] = useState(false);\n const warehouse = props.warehouse;\n const city = props.city;\n const mat = props.mat;\n const markupLimit = mat.getMarkupLimit();\n const office = division.offices[city];\n if (!(office instanceof OfficeSpace)) {\n throw new Error(`Could not get OfficeSpace object for this city (${city})`);\n }\n\n // Numeraljs formatter\n const nf = \"0.000\";\n const nfB = \"0.000a\"; // For numbers that might be biger\n\n // Total gain or loss of this material (per second)\n const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp;\n\n // Flag that determines whether this industry is \"new\" and the current material should be\n // marked with flashing-red lights\n const tutorial =\n division.newInd && Object.keys(division.reqMats).includes(mat.name) && mat.buy === 0 && mat.imp === 0;\n\n // Purchase material button\n const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;\n\n // Sell material button\n let sellButtonText: JSX.Element;\n if (mat.sllman[0]) {\n if (isString(mat.sllman[1])) {\n sellButtonText = (\n <>\n Sell ({numeralWrapper.format(mat.sll, nfB)}/{mat.sllman[1]})\n \n );\n } else {\n sellButtonText = (\n <>\n Sell ({numeralWrapper.format(mat.sll, nfB)}/{numeralWrapper.format(mat.sllman[1] as number, nfB)})\n \n );\n }\n\n if (mat.marketTa2) {\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n } else if (mat.marketTa1) {\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n } else if (mat.sCost) {\n if (isString(mat.sCost)) {\n const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + \"\");\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n } else {\n sellButtonText = (\n <>\n {sellButtonText} @ \n \n );\n }\n }\n } else {\n sellButtonText = <>Sell (0.000/0.000);\n }\n\n return (\n \n \n \n \n Buy: {numeralWrapper.format(mat.buy, nfB)}
\n Prod: {numeralWrapper.format(mat.prd, nfB)}
\n Sell: {numeralWrapper.format(mat.sll, nfB)}
\n Export: {numeralWrapper.format(mat.totalExp, nfB)}
\n Import: {numeralWrapper.format(mat.imp, nfB)}\n {corp.unlockUpgrades[2] === 1 &&
}\n {corp.unlockUpgrades[2] === 1 && \"Demand: \" + numeralWrapper.format(mat.dmd, nf)}\n {corp.unlockUpgrades[3] === 1 &&
}\n {corp.unlockUpgrades[3] === 1 && \"Competition: \" + numeralWrapper.format(mat.cmp, nf)}\n \n }\n >\n \n {mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s)\n \n \n \n Market Price: The price you would pay if you were to buy this material on the market\n \n }\n >\n MP: {numeralWrapper.formatMoney(mat.bCost)}\n \n The quality of your material. Higher quality will lead to more sales}\n >\n Quality: {numeralWrapper.format(mat.qlt, \"0.00a\")}\n \n
\n\n \n Purchase your required materials to get production started! : \"\"}\n >\n \n setPurchaseMaterialOpen(true)}\n disabled={props.warehouse.smartSupplyEnabled && Object.keys(division.reqMats).includes(props.mat.name)}\n >\n {purchaseButtonText}\n \n \n \n setPurchaseMaterialOpen(false)}\n />\n\n {corp.unlockUpgrades[0] === 1 && (\n <>\n \n\n setExportOpen(false)} />\n \n )}\n
\n\n setSellMaterialOpen(true)}\n >\n {sellButtonText}\n \n setSellMaterialOpen(false)} />\n {division.hasResearch(\"Market-TA.I\") && (\n <>\n \n\n setMaterialMarketTaOpen(false)}\n />\n \n )}\n
\n
\n
\n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Material } from \"../Material\";\nimport { Export } from \"../Export\";\nimport { IIndustry } from \"../IIndustry\";\nimport { ExportMaterial } from \"../Actions\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n mat: Material;\n}\n\n// Create a popup that lets the player manage exports\nexport function ExportModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n if (corp.divisions.length === 0) throw new Error(\"Export popup created with no divisions.\");\n if (Object.keys(corp.divisions[0].warehouses).length === 0)\n throw new Error(\"Export popup created in a division with no warehouses.\");\n const [industry, setIndustry] = useState(corp.divisions[0].name);\n const [city, setCity] = useState(Object.keys(corp.divisions[0].warehouses)[0]);\n const [amt, setAmt] = useState(\"\");\n const setRerender = useState(false)[1];\n\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n function onCityChange(event: SelectChangeEvent): void {\n setCity(event.target.value);\n }\n\n function onIndustryChange(event: SelectChangeEvent): void {\n setIndustry(event.target.value);\n }\n\n function onAmtChange(event: React.ChangeEvent): void {\n setAmt(event.target.value);\n }\n\n function exportMaterial(): void {\n try {\n ExportMaterial(industry, city, props.mat, amt);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n props.onClose();\n }\n\n function removeExport(exp: Export): void {\n for (let i = 0; i < props.mat.exp.length; ++i) {\n if (props.mat.exp[i].ind !== exp.ind || props.mat.exp[i].city !== exp.city || props.mat.exp[i].amt !== exp.amt)\n continue;\n props.mat.exp.splice(i, 1);\n break;\n }\n rerender();\n }\n\n const currentDivision = corp.divisions.find((division: IIndustry) => division.name === industry);\n if (currentDivision === undefined)\n throw new Error(`Export popup somehow ended up with undefined division '${currentDivision}'`);\n const possibleCities = Object.keys(currentDivision.warehouses).filter(\n (city) => currentDivision.warehouses[city] !== 0,\n );\n if (possibleCities.length > 0 && !possibleCities.includes(city)) {\n setCity(possibleCities[0]);\n }\n\n return (\n \n \n Select the industry and city to export this material to, as well as how much of this material to export per\n second. You can set the export amount to 'MAX' to export all of the materials in this warehouse.\n \n \n \n \n \n \n Below is a list of all current exports of this material from this warehouse. Clicking on one of the exports\n below will REMOVE that export.\n \n {props.mat.exp.map((exp: Export, index: number) => (\n \n \n \n Industry: {exp.ind}\n
\n City: {exp.city}\n
\n Amount/s: {exp.amt}\n
\n
\n ))}\n
\n );\n}\n","import React, { useState } from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Material } from \"../Material\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Switch from \"@mui/material/Switch\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\ninterface IMarketTA2Props {\n mat: Material;\n}\n\nfunction MarketTA2(props: IMarketTA2Props): React.ReactElement {\n const division = useDivision();\n if (!division.hasResearch(\"Market-TA.II\")) return <>;\n\n const [newCost, setNewCost] = useState(props.mat.bCost);\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const markupLimit = props.mat.getMarkupLimit();\n\n function onChange(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setNewCost(0);\n else setNewCost(parseFloat(event.target.value));\n }\n\n const sCost = newCost;\n let markup = 1;\n if (sCost > props.mat.bCost) {\n //Penalty if difference between sCost and bCost is greater than markup limit\n if (sCost - props.mat.bCost > markupLimit) {\n markup = Math.pow(markupLimit / (sCost - props.mat.bCost), 2);\n }\n } else if (sCost < props.mat.bCost) {\n if (sCost <= 0) {\n markup = 1e12; //Sell everything, essentially discard\n } else {\n //Lower prices than market increases sales\n markup = props.mat.bCost / sCost;\n }\n }\n\n function onMarketTA2(event: React.ChangeEvent): void {\n props.mat.marketTa2 = event.target.checked;\n rerender();\n }\n\n return (\n <>\n Market-TA.II\n
\n \n If you sell at {numeralWrapper.formatMoney(sCost)}, then you will sell{\" \"}\n {numeralWrapper.format(markup, \"0.00000\")}x as much compared to if you sold at market price.\n \n \n
\n }\n label={\n \n If this is enabled, then this Material will automatically be sold at the optimal price such that the\n amount sold matches the amount produced. (i.e. the highest possible price, while still ensuring that all\n produced materials will be sold)\n \n }\n >\n Use Market-TA.II for Auto-Sale Price\n \n }\n />\n\n \n Note that Market-TA.II overrides Market-TA.I. This means that if both are enabled, then Market-TA.II will take\n effect, not Market-TA.I\n \n \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n mat: Material;\n}\n\n// Create a popup that lets the player use the Market TA research for Materials\nexport function MaterialMarketTaModal(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const markupLimit = props.mat.getMarkupLimit();\n\n function onMarketTA1(event: React.ChangeEvent): void {\n props.mat.marketTa1 = event.target.checked;\n rerender();\n }\n\n return (\n \n Market-TA.I\n \n The maximum sale price you can mark this up to is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}.\n This means that if you set the sale price higher than this, you will begin to experience a loss in number of\n sales\n \n\n }\n label={\n \n If this is enabled, then this Material will automatically be sold at the price identified by Market-TA.I\n (i.e. the price shown above)\n \n }\n >\n Use Market-TA.I for Auto-Sale Price\n \n }\n />\n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Material } from \"../Material\";\nimport { SellMaterial } from \"../Actions\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\nfunction initialPrice(mat: Material): string {\n let val = mat.sCost ? mat.sCost + \"\" : \"\";\n if (mat.marketTa2) {\n val += \" (Market-TA.II)\";\n } else if (mat.marketTa1) {\n val += \" (Market-TA.I)\";\n }\n return val;\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n mat: Material;\n}\n\n// Create a popup that let the player manage sales of a material\nexport function SellMaterialModal(props: IProps): React.ReactElement {\n const [amt, setAmt] = useState(props.mat.sllman[1] ? props.mat.sllman[1] + \"\" : \"\");\n const [price, setPrice] = useState(initialPrice(props.mat));\n\n function sellMaterial(): void {\n try {\n SellMaterial(props.mat, amt, price);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n props.onClose();\n }\n\n function onAmtChange(event: React.ChangeEvent): void {\n setAmt(event.target.value);\n }\n\n function onPriceChange(event: React.ChangeEvent): void {\n setPrice(event.target.value);\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) sellMaterial();\n }\n\n return (\n \n \n Enter the maximum amount of {props.mat.name} you would like to sell per second, as well as the price at which\n you would like to sell at.\n
\n
\n If the sell amount is set to 0, then the material will not be sold. If the sell price if set to 0, then the\n material will be discarded\n
\n
\n Setting the sell amount to 'MAX' will result in you always selling the maximum possible amount of the material.\n
\n
\n When setting the sell amount, you can use the 'PROD' variable to designate a dynamically changing amount that\n depends on your production. For example, if you set the sell amount to 'PROD-5' then you will always sell 5 less\n of the material than you produce.\n
\n
\n When setting the sell price, you can use the 'MP' variable to designate a dynamically changing price that\n depends on the market price. For example, if you set the sell price to 'MP+10' then it will always be sold at\n $10 above the market price.\n
\n
\n \n \n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { MaterialSizes } from \"../MaterialSizes\";\nimport { Warehouse } from \"../Warehouse\";\nimport { Material } from \"../Material\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { BuyMaterial } from \"../Actions\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useCorporation, useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\ninterface IBulkPurchaseTextProps {\n warehouse: Warehouse;\n mat: Material;\n amount: string;\n}\n\nfunction BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement {\n const parsedAmt = parseFloat(props.amount);\n const cost = parsedAmt * props.mat.bCost;\n\n const matSize = MaterialSizes[props.mat.name];\n const maxAmount = (props.warehouse.size - props.warehouse.sizeUsed) / matSize;\n\n if (parsedAmt * matSize > maxAmount) {\n return <>Not enough warehouse space to purchase this amount;\n } else if (isNaN(cost)) {\n return <>Invalid put for Bulk Purchase amount;\n } else {\n return (\n <>\n Purchasing {numeralWrapper.format(parsedAmt, \"0,0.00\")} of {props.mat.name} will cost{\" \"}\n {numeralWrapper.formatMoney(cost)}\n \n );\n }\n}\n\ninterface IBPProps {\n onClose: () => void;\n mat: Material;\n warehouse: Warehouse;\n}\n\nfunction BulkPurchase(props: IBPProps): React.ReactElement {\n const corp = useCorporation();\n const [buyAmt, setBuyAmt] = useState(\"\");\n\n function bulkPurchase(): void {\n const amount = parseFloat(buyAmt);\n\n const matSize = MaterialSizes[props.mat.name];\n const maxAmount = (props.warehouse.size - props.warehouse.sizeUsed) / matSize;\n if (amount * matSize > maxAmount) {\n dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);\n return;\n }\n\n if (isNaN(amount)) {\n dialogBoxCreate(\"Invalid input amount\");\n } else {\n const cost = amount * props.mat.bCost;\n if (corp.funds.gt(cost)) {\n corp.funds = corp.funds.minus(cost);\n props.mat.qty += amount;\n } else {\n dialogBoxCreate(`You cannot afford this purchase.`);\n return;\n }\n props.onClose();\n }\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) bulkPurchase();\n }\n\n function onChange(event: React.ChangeEvent): void {\n setBuyAmt(event.target.value);\n }\n\n return (\n <>\n \n Enter the amount of {props.mat.name} you would like to bulk purchase. This purchases the specified amount\n instantly (all at once).\n \n \n \n \n \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n mat: Material;\n warehouse: Warehouse;\n}\n\n// Create a popup that lets the player purchase a Material\nexport function PurchaseMaterialModal(props: IProps): React.ReactElement {\n const division = useDivision();\n const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : 0);\n\n function purchaseMaterial(): void {\n if (buyAmt === null) return;\n try {\n BuyMaterial(props.mat, buyAmt);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n\n props.onClose();\n }\n\n function clearPurchase(): void {\n props.mat.buy = 0;\n props.onClose();\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) purchaseMaterial();\n }\n\n function onChange(event: React.ChangeEvent): void {\n setBuyAmt(parseFloat(event.target.value));\n }\n\n return (\n \n <>\n \n Enter the amount of {props.mat.name} you would like to purchase per second. This material's cost changes\n constantly.\n \n \n \n \n {division.hasResearch(\"Bulk Purchasing\") && (\n \n )}\n \n \n );\n}\n","import { IIndustry } from \"../IIndustry\";\n\n// Returns a boolean indicating whether the given material is relevant for the\n// current industry.\nexport function isRelevantMaterial(matName: string, division: IIndustry): boolean {\n // Materials that affect Production multiplier\n const prodMultiplierMats = [\"Hardware\", \"Robots\", \"AICores\", \"RealEstate\"];\n\n if (Object.keys(division.reqMats).includes(matName)) {\n return true;\n }\n if (division.prodMats.includes(matName)) {\n return true;\n }\n if (prodMultiplierMats.includes(matName)) {\n return true;\n }\n\n return false;\n}\n","import React from \"react\";\nimport { IIndustry } from \"../IIndustry\";\nimport { MathComponent } from \"mathjax-react\";\n\ninterface IProps {\n division: IIndustry;\n}\n\nexport function IndustryProductEquation(props: IProps): React.ReactElement {\n const reqs = [];\n for (const reqMat of Object.keys(props.division.reqMats)) {\n const reqAmt = props.division.reqMats[reqMat];\n if (reqAmt === undefined) continue;\n reqs.push(String.raw`${reqAmt}\\text{ }${reqMat}`);\n }\n const prod = props.division.prodMats.slice();\n if (props.division.makesProducts) {\n prod.push(props.division.type);\n }\n\n return (\n String.raw`1\\text{ }${p}`).join(\"+\")}\n />\n );\n}\n","import React, { useState } from \"react\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { NewCity } from \"../Actions\";\nimport { MoneyCost } from \"./MoneyCost\";\nimport { useCorporation, useDivision } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n cityStateSetter: (city: string) => void;\n}\n\nexport function ExpandNewCity(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const division = useDivision();\n const possibleCities = Object.keys(division.offices).filter((cityName: string) => division.offices[cityName] === 0);\n const [city, setCity] = useState(possibleCities[0]);\n\n const disabled = corp.funds.lt(CorporationConstants.OfficeInitialCost);\n\n function onCityChange(event: SelectChangeEvent): void {\n setCity(event.target.value);\n }\n\n function expand(): void {\n try {\n NewCity(corp, division, city);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n return;\n }\n\n dialogBoxCreate(`Opened a new office in ${city}!`);\n\n props.cityStateSetter(city);\n }\n return (\n <>\n \n Would you like to expand into a new city by opening an office? This would cost{\" \"}\n \n \n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { IndustryStartingCosts, Industries, IndustryDescriptions } from \"../IndustryData\";\nimport { useCorporation } from \"./Context\";\nimport { IIndustry } from \"../IIndustry\";\nimport { NewIndustry } from \"../Actions\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Box from \"@mui/material/Box\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n setDivisionName: (name: string) => void;\n}\n\nexport function ExpandIndustryTab(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const allIndustries = Object.keys(Industries).sort();\n const possibleIndustries = allIndustries\n .filter(\n (industryType: string) =>\n corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,\n )\n .sort();\n const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : \"\");\n const [name, setName] = useState(\"\");\n\n const cost = IndustryStartingCosts[industry];\n if (cost === undefined) {\n throw new Error(`Invalid industry: '${industry}'`);\n }\n const disabled = corp.funds.lt(cost) || name === \"\";\n\n function newIndustry(): void {\n if (disabled) return;\n try {\n NewIndustry(corp, industry, name);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n return;\n }\n\n // Set routing to the new division so that the UI automatically switches to it\n props.setDivisionName(name);\n }\n\n function onNameChange(event: React.ChangeEvent): void {\n // [a-zA-Z0-9-_]\n setName(event.target.value);\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) newIndustry();\n }\n\n function onIndustryChange(event: SelectChangeEvent): void {\n setIndustry(event.target.value);\n }\n\n const desc = IndustryDescriptions[industry];\n if (desc === undefined) throw new Error(`Trying to create an industry that doesn't exists: '${industry}'`);\n\n return (\n <>\n Create a new division to expand into a new industry:\n \n {desc(corp)}\n
\n
\n\n Division name:\n\n \n \n \n \n \n );\n}\n","// React Component for displaying Corporation Overview info\nimport React, { useState } from \"react\";\nimport { LevelableUpgrade } from \"./LevelableUpgrade\";\nimport { UnlockUpgrade } from \"./UnlockUpgrade\";\nimport { BribeFactionModal } from \"./BribeFactionModal\";\nimport { SellSharesModal } from \"./SellSharesModal\";\nimport { BuybackSharesModal } from \"./BuybackSharesModal\";\nimport { IssueDividendsModal } from \"./IssueDividendsModal\";\nimport { IssueNewSharesModal } from \"./IssueNewSharesModal\";\nimport { FindInvestorsModal } from \"./FindInvestorsModal\";\nimport { GoPublicModal } from \"./GoPublicModal\";\nimport { Factions } from \"../../Faction/Factions\";\n\nimport { CorporationConstants } from \"../data/Constants\";\nimport { CorporationUnlockUpgrade, CorporationUnlockUpgrades } from \"../data/CorporationUnlockUpgrades\";\nimport { CorporationUpgrade, CorporationUpgrades } from \"../data/CorporationUpgrades\";\n\nimport { CONSTANTS } from \"../../Constants\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { Money } from \"../../ui/React/Money\";\nimport { MoneyRate } from \"../../ui/React/MoneyRate\";\nimport { StatsTable } from \"../../ui/React/StatsTable\";\nimport { use } from \"../../ui/Context\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\nimport Paper from \"@mui/material/Paper\";\nimport Grid from \"@mui/material/Grid\";\n\ninterface IProps {\n rerender: () => void;\n}\nexport function Overview({ rerender }: IProps): React.ReactElement {\n const player = use.Player();\n const corp = useCorporation();\n const profit: number = corp.revenue.minus(corp.expenses).toNumber();\n\n const multRows: any[][] = [];\n function appendMult(name: string, value: number): void {\n if (value === 1) return;\n multRows.push([name, numeralWrapper.format(value, \"0.000\")]);\n }\n appendMult(\"Production Multiplier: \", corp.getProductionMultiplier());\n appendMult(\"Storage Multiplier: \", corp.getStorageMultiplier());\n appendMult(\"Advertising Multiplier: \", corp.getAdvertisingMultiplier());\n appendMult(\"Empl. Creativity Multiplier: \", corp.getEmployeeCreMultiplier());\n appendMult(\"Empl. Charisma Multiplier: \", corp.getEmployeeChaMultiplier());\n appendMult(\"Empl. Intelligence Multiplier: \", corp.getEmployeeIntMultiplier());\n appendMult(\"Empl. Efficiency Multiplier: \", corp.getEmployeeEffMultiplier());\n appendMult(\"Sales Multiplier: \", corp.getSalesMultiplier());\n appendMult(\"Scientific Research Multiplier: \", corp.getScientificResearchMultiplier());\n\n return (\n <>\n ],\n [\"Total Revenue:\", ],\n [\"Total Expenses:\", ],\n [\"Publicly Traded:\", corp.public ? \"Yes\" : \"No\"],\n [\"Owned Stock Shares:\", numeralWrapper.format(corp.numShares, \"0.000a\")],\n [\"Stock Price:\", corp.public ? : \"N/A\"],\n ]}\n />\n
\n \n \n }\n >\n Total Stock Shares: {numeralWrapper.format(corp.totalShares, \"0.000a\")}\n \n \n
\n \n
\n \n
\n \n \n Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' This is a .lit file\n that guides you through the beginning of setting up a Corporation and provides some tips/pointers for\n helping you get started with managing it.\n \n }\n >\n \n \n {corp.public ? : }\n \n
\n \n \n );\n}\n\ninterface IPrivateButtonsProps {\n rerender: () => void;\n}\n// Render the buttons for when your Corporation is still private\nfunction PrivateButtons({ rerender }: IPrivateButtonsProps): React.ReactElement {\n const corp = useCorporation();\n const [findInvestorsopen, setFindInvestorsopen] = useState(false);\n const [goPublicopen, setGoPublicopen] = useState(false);\n\n const fundingAvailable = corp.fundingRound < 4;\n const findInvestorsTooltip = fundingAvailable\n ? \"Search for private investors who will give you startup funding in exchange for equity (stock shares) in your company\"\n : \"\";\n\n return (\n <>\n {findInvestorsTooltip}}>\n \n \n \n \n \n Become a publicly traded and owned entity. Going public involves issuing shares for an IPO. Once you are a\n public company, your shares will be traded on the stock market.\n \n }\n >\n \n \n setFindInvestorsopen(false)} rerender={rerender} />\n setGoPublicopen(false)} rerender={rerender} />\n
\n \n );\n}\n\ninterface IUpgradeProps {\n rerender: () => void;\n}\n// Render the UI for Corporation upgrades\nfunction Upgrades({ rerender }: IUpgradeProps): React.ReactElement {\n const corp = useCorporation();\n // Don't show upgrades\n if (corp.divisions.length <= 0) {\n return Upgrades are unlocked once you create an industry.;\n }\n\n return (\n <>\n \n Unlocks\n \n {Object.values(CorporationUnlockUpgrades)\n .filter((upgrade: CorporationUnlockUpgrade) => corp.unlockUpgrades[upgrade[0]] === 0)\n .map((upgrade: CorporationUnlockUpgrade) => (\n \n ))}\n \n \n \n Upgrades\n \n {corp.upgrades\n .map((level: number, i: number) => CorporationUpgrades[i])\n .map((upgrade: CorporationUpgrade) => (\n \n ))}\n \n \n \n );\n}\n\ninterface IPublicButtonsProps {\n rerender: () => void;\n}\n\n// Render the buttons for when your Corporation has gone public\nfunction PublicButtons({ rerender }: IPublicButtonsProps): React.ReactElement {\n const corp = useCorporation();\n const [sellSharesOpen, setSellSharesOpen] = useState(false);\n const [buybackSharesOpen, setBuybackSharesOpen] = useState(false);\n const [issueNewSharesOpen, setIssueNewSharesOpen] = useState(false);\n const [issueDividendsOpen, setIssueDividendsOpen] = useState(false);\n\n const sellSharesOnCd = corp.shareSaleCooldown > 0;\n const sellSharesTooltip = sellSharesOnCd\n ? \"Cannot sell shares for \" + corp.convertCooldownToString(corp.shareSaleCooldown)\n : \"Sell your shares in the company. The money earned from selling your \" +\n \"shares goes into your personal account, not the Corporation's. \" +\n \"This is one of the only ways to profit from your business venture.\";\n\n const issueNewSharesOnCd = corp.issueNewSharesCooldown > 0;\n const issueNewSharesTooltip = issueNewSharesOnCd\n ? \"Cannot issue new shares for \" + corp.convertCooldownToString(corp.issueNewSharesCooldown)\n : \"Issue new equity shares to raise capital.\";\n\n return (\n <>\n {sellSharesTooltip}}>\n \n \n \n \n setSellSharesOpen(false)} rerender={rerender} />\n Buy back shares you that previously issued or sold at market price.}>\n \n \n \n \n setBuybackSharesOpen(false)} rerender={rerender} />\n
\n {issueNewSharesTooltip}}>\n \n \n \n \n setIssueNewSharesOpen(false)} />\n Manage the dividends that are paid out to shareholders (including yourself)}\n >\n \n \n setIssueDividendsOpen(false)} />\n
\n \n );\n}\n\nfunction BribeButton(): React.ReactElement {\n const player = use.Player();\n const corp = useCorporation();\n const [open, setOpen] = useState(false);\n const canBribe =\n corp.determineValuation() >= CorporationConstants.BribeThreshold &&\n player.factions.filter((f) => Factions[f].getInfo().offersWork()).length > 0;\n\n function openBribe(): void {\n if (!canBribe) return;\n setOpen(true);\n }\n\n return (\n <>\n \n \n \n \n \n setOpen(false)} />\n \n );\n}\n\ninterface IDividendsStatsProps {\n profit: number;\n}\nfunction DividendsStats({ profit }: IDividendsStatsProps): React.ReactElement {\n const corp = useCorporation();\n if (corp.dividendPercentage <= 0 || profit <= 0) return <>;\n const totalDividends = (corp.dividendPercentage / 100) * profit;\n const retainedEarnings = profit - totalDividends;\n const dividendsPerShare = totalDividends / corp.totalShares;\n const playerEarnings = corp.numShares * dividendsPerShare;\n return (\n ],\n [\"Dividend Percentage:\", numeralWrapper.format(corp.dividendPercentage / 100, \"0%\")],\n [\"Dividends per share:\", ],\n [\"Your earnings as a shareholder (Pre-Tax):\", ],\n [\"Dividend Tax Rate:\", <>{corp.dividendTaxPercentage}%],\n [\n \"Your earnings as a shareholder (Post-Tax):\",\n ,\n ],\n ]}\n />\n );\n}\n\n// Returns a string with general information about Corporation\nfunction BonusTime(): React.ReactElement {\n const corp = useCorporation();\n const storedTime = corp.storedCycles * CONSTANTS.MilliPerCycle;\n if (storedTime <= 15000) return <>;\n return (\n \n Bonus time: {convertTimeMsToTimeElapsedString(storedTime)}\n
\n
\n
\n );\n}\n","// React components for the levelable upgrade buttons on the overview panel\nimport React from \"react\";\n\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { CorporationUpgrade } from \"../data/CorporationUpgrades\";\nimport { LevelUpgrade } from \"../Actions\";\nimport { MoneyCost } from \"./MoneyCost\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\nimport Grid from \"@mui/material/Grid\";\n\ninterface IProps {\n upgrade: CorporationUpgrade;\n rerender: () => void;\n}\n\nexport function LevelableUpgrade(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const data = props.upgrade;\n const level = corp.upgrades[data[0]];\n\n const baseCost = data[1];\n const priceMult = data[2];\n const cost = baseCost * Math.pow(priceMult, level);\n\n const tooltip = data[5];\n function onClick(): void {\n if (corp.funds.lt(cost)) return;\n try {\n LevelUpgrade(corp, props.upgrade);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n props.rerender();\n }\n\n return (\n \n \n \n \n {data[4]} \n \n \n \n );\n}\n","// React Components for the Unlock upgrade buttons on the overview page\nimport React from \"react\";\n\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { CorporationUnlockUpgrade } from \"../data/CorporationUnlockUpgrades\";\nimport { useCorporation } from \"./Context\";\nimport { UnlockUpgrade as UU } from \"../Actions\";\nimport { MoneyCost } from \"./MoneyCost\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\nimport Grid from \"@mui/material/Grid\";\n\ninterface IProps {\n upgradeData: CorporationUnlockUpgrade;\n rerender: () => void;\n}\n\nexport function UnlockUpgrade(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const data = props.upgradeData;\n const tooltip = data[3];\n function onClick(): void {\n if (corp.funds.lt(data[1])) return;\n try {\n UU(corp, props.upgradeData);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n props.rerender();\n }\n\n return (\n \n \n \n \n {data[2]}\n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { Factions } from \"../../Faction/Factions\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { use } from \"../../ui/Context\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport TextField from \"@mui/material/TextField\";\nimport Box from \"@mui/material/Box\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\nexport function BribeFactionModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const factions = player.factions.filter((name: string) => {\n const info = Factions[name].getInfo();\n if (!info.offersWork()) return false;\n if (player.hasGangWith(name)) return false;\n return true;\n });\n const corp = useCorporation();\n const [money, setMoney] = useState(0);\n const [stock, setStock] = useState(0);\n const [selectedFaction, setSelectedFaction] = useState(factions.length > 0 ? factions[0] : \"\");\n const disabled =\n money === null ||\n stock === null ||\n (money === 0 && stock === 0) ||\n isNaN(money) ||\n isNaN(stock) ||\n money < 0 ||\n stock < 0 ||\n corp.funds.lt(money) ||\n stock > corp.numShares;\n\n function onMoneyChange(event: React.ChangeEvent): void {\n setMoney(parseFloat(event.target.value));\n }\n\n function onStockChange(event: React.ChangeEvent): void {\n setStock(parseFloat(event.target.value));\n }\n\n function changeFaction(event: SelectChangeEvent): void {\n setSelectedFaction(event.target.value);\n }\n\n function repGain(money: number, stock: number): number {\n return (money + stock * corp.sharePrice) / CorporationConstants.BribeToRepRatio;\n }\n\n function getRepText(money: number, stock: number): string {\n if (money === 0 && stock === 0) return \"\";\n if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {\n return \"ERROR: Invalid value(s) entered\";\n } else if (corp.funds.lt(money)) {\n return \"ERROR: You do not have this much money to bribe with\";\n } else if (stock > corp.numShares) {\n return \"ERROR: You do not have this many shares to bribe with\";\n } else {\n return (\n \"You will gain \" +\n numeralWrapper.formatReputation(repGain(money, stock)) +\n \" reputation with \" +\n selectedFaction +\n \" with this bribe\"\n );\n }\n }\n\n function bribe(money: number, stock: number): void {\n const fac = Factions[selectedFaction];\n if (disabled) return;\n const rep = repGain(money, stock);\n dialogBoxCreate(\n \"You gained \" + numeralWrapper.formatReputation(rep) + \" reputation with \" + fac.name + \" by bribing them.\",\n );\n fac.playerReputation += rep;\n corp.funds = corp.funds.minus(money);\n corp.numShares -= stock;\n props.onClose();\n }\n\n return (\n \n \n You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.\n \n \n Faction:\n \n \n {getRepText(money ? money : 0, stock ? stock : 0)}\n \n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { use } from \"../../ui/Context\";\nimport { useCorporation } from \"./Context\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { ICorporation } from \"../ICorporation\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n rerender: () => void;\n}\n\n// Create a popup that lets the player sell Corporation shares\n// This is created when the player clicks the \"Sell Shares\" button in the overview panel\nexport function SellSharesModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const corp = useCorporation();\n const [shares, setShares] = useState(null);\n\n const disabled = shares === null || isNaN(shares) || shares <= 0 || shares > corp.numShares;\n\n function changeShares(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setShares(null);\n else setShares(Math.round(parseFloat(event.target.value)));\n }\n\n function ProfitIndicator(props: { shares: number | null; corp: ICorporation }): React.ReactElement {\n if (props.shares === null) return <>;\n if (isNaN(props.shares) || props.shares <= 0) {\n return <>ERROR: Invalid value entered for number of shares to sell;\n } else if (props.shares > corp.numShares) {\n return <>You don't have this many shares to sell!;\n } else {\n const stockSaleResults = corp.calculateShareSale(props.shares);\n const profit = stockSaleResults[0];\n return (\n <>\n Sell {props.shares} shares for a total of {numeralWrapper.formatMoney(profit)}\n \n );\n }\n }\n\n function sell(): void {\n if (shares === null) return;\n if (disabled) return;\n const stockSaleResults = corp.calculateShareSale(shares);\n const profit = stockSaleResults[0];\n const newSharePrice = stockSaleResults[1];\n const newSharesUntilUpdate = stockSaleResults[2];\n\n corp.numShares -= shares;\n if (isNaN(corp.issuedShares)) {\n console.error(`Corporation issuedShares is NaN: ${corp.issuedShares}`);\n const res = corp.issuedShares;\n if (isNaN(res)) {\n corp.issuedShares = 0;\n } else {\n corp.issuedShares = res;\n }\n }\n corp.issuedShares += shares;\n corp.sharePrice = newSharePrice;\n corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate;\n corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown;\n player.gainMoney(profit);\n player.recordMoneySource(profit, \"corporation\");\n props.onClose();\n dialogBoxCreate(\n `Sold {numeralWrapper.formatMoney(shares)} shares for ` +\n `${numeralWrapper.formatMoney(profit)}. ` +\n `The corporation's stock price fell to ${numeralWrapper.formatMoney(corp.sharePrice)} ` +\n `as a result of dilution.`,\n );\n\n props.rerender();\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) sell();\n }\n\n return (\n \n \n Enter the number of shares you would like to sell. The money from selling your shares will go directly to you\n (NOT your Corporation).\n
\n
\n Selling your shares will cause your corporation's stock price to fall due to dilution. Furthermore, selling a\n large number of shares all at once will have an immediate effect in reducing your stock price.\n
\n
\n The current price of your company's stock is {numeralWrapper.formatMoney(corp.sharePrice)}\n
\n \n
\n \n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { use } from \"../../ui/Context\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n rerender: () => void;\n}\n\n// Create a popup that lets the player buyback shares\n// This is created when the player clicks the \"Buyback Shares\" button in the overview panel\nexport function BuybackSharesModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const corp = useCorporation();\n const [shares, setShares] = useState(null);\n\n function changeShares(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setShares(null);\n else setShares(Math.round(parseFloat(event.target.value)));\n }\n\n const currentStockPrice = corp.sharePrice;\n const buybackPrice = currentStockPrice * 1.1;\n const disabled =\n shares === null ||\n isNaN(shares) ||\n shares <= 0 ||\n shares > corp.issuedShares ||\n shares * buybackPrice > player.money;\n\n function buy(): void {\n if (shares === null) return;\n corp.numShares += shares;\n if (isNaN(corp.issuedShares)) {\n console.warn(\"Corporation issuedShares is NaN: \" + corp.issuedShares);\n console.warn(\"Converting to number now\");\n const res = corp.issuedShares;\n if (isNaN(res)) {\n corp.issuedShares = 0;\n } else {\n corp.issuedShares = res;\n }\n }\n corp.issuedShares -= shares;\n player.loseMoney(shares * buybackPrice);\n props.onClose();\n props.rerender();\n }\n\n function CostIndicator(): React.ReactElement {\n if (shares === null) return <>;\n if (isNaN(shares) || shares <= 0) {\n return <>ERROR: Invalid value entered for number of shares to buyback;\n } else if (shares > corp.issuedShares) {\n return (\n <>\n There are not this many shares available to buy back. There are only{\" \"}\n {numeralWrapper.formatBigNumber(corp.issuedShares)} outstanding shares.\n \n );\n } else {\n return (\n <>\n Purchase {shares} shares for a total of {numeralWrapper.formatMoney(shares * buybackPrice)}\n \n );\n }\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) buy();\n }\n\n return (\n \n \n Enter the number of outstanding shares you would like to buy back. These shares must be bought at a 10% premium.\n However, repurchasing shares from the market tends to lead to an increase in stock price.\n
\n
\n To purchase these shares, you must use your own money (NOT your Corporation's funds).\n
\n
\n The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}. Your company\n currently has {numeralWrapper.formatBigNumber(corp.issuedShares)} outstanding stock shares.\n
\n \n
\n \n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { IssueDividends } from \"../Actions\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\n// Create a popup that lets the player issue & manage dividends\n// This is created when the player clicks the \"Issue Dividends\" button in the overview panel\nexport function IssueDividendsModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const [percent, setPercent] = useState(0);\n\n const canIssue = !isNaN(percent) && percent >= 0 && percent <= CorporationConstants.DividendMaxPercentage * 100;\n function issueDividends(): void {\n if (!canIssue) return;\n if (percent === null) return;\n try {\n IssueDividends(corp, percent / 100);\n } catch (err) {\n dialogBoxCreate(err + \"\");\n }\n\n props.onClose();\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) issueDividends();\n }\n\n function onChange(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setPercent(0);\n else {\n let p = parseFloat(event.target.value);\n if (p > 50) p = 50;\n if (p < 0) p = 0;\n setPercent(p);\n }\n }\n\n return (\n \n \n Dividends are a distribution of a portion of the corporation's profits to the shareholders. This includes\n yourself, as well.\n
\n
\n In order to issue dividends, simply allocate some percentage of your corporation's profits to dividends. This\n percentage must be an integer between 0 and {CorporationConstants.DividendMaxPercentage}. (A percentage of 0\n means no dividends will be issued\n
\n
\n Two important things to note:\n
\n * Issuing dividends will negatively affect your corporation's stock price\n
\n * Dividends are taxed. Taxes start at 50%, but can be decreased\n
\n
\n Example: Assume your corporation makes $100m / sec in profit and you allocate 40% of that towards dividends.\n That means your corporation will gain $60m / sec in funds and the remaining $40m / sec will be paid as\n dividends. Since your corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share\n per second before taxes.\n
\n \n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { getRandomInt } from \"../../utils/helpers/getRandomInt\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\ninterface IEffectTextProps {\n shares: number | null;\n}\n\nfunction EffectText(props: IEffectTextProps): React.ReactElement {\n const corp = useCorporation();\n if (props.shares === null) return <>;\n const newSharePrice = Math.round(corp.sharePrice * 0.9);\n const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2);\n const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);\n let newShares = props.shares;\n if (isNaN(newShares)) {\n return Invalid input;\n }\n\n // Round to nearest ten-millionth\n newShares /= 10e6;\n newShares = Math.round(newShares) * 10e6;\n\n if (newShares < 10e6) {\n return Must issue at least 10 million new shares;\n }\n\n if (newShares > maxNewShares) {\n return You cannot issue that many shares;\n }\n\n return (\n \n Issue ${numeralWrapper.format(newShares, \"0.000a\")} new shares for{\" \"}\n {numeralWrapper.formatMoney(newShares * newSharePrice)}?\n \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\n// Create a popup that lets the player issue new shares\n// This is created when the player clicks the \"Issue New Shares\" buttons in the overview panel\nexport function IssueNewSharesModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const [shares, setShares] = useState(null);\n const maxNewSharesUnrounded = Math.round(corp.totalShares * 0.2);\n const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);\n\n const newShares = Math.round((shares || 0) / 10e6) * 10e6;\n const disabled = shares === null || isNaN(newShares) || newShares < 10e6 || newShares > maxNewShares;\n\n function issueNewShares(): void {\n if (shares === null) return;\n if (disabled) return;\n\n const newSharePrice = Math.round(corp.sharePrice * 0.9);\n let newShares = shares;\n\n // Round to nearest ten-millionth\n newShares = Math.round(newShares / 10e6) * 10e6;\n\n const profit = newShares * newSharePrice;\n corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown;\n corp.totalShares += newShares;\n\n // Determine how many are bought by private investors\n // Private investors get up to 50% at most\n // Round # of private shares to the nearest millionth\n let privateShares = getRandomInt(0, Math.round(newShares / 2));\n privateShares = Math.round(privateShares / 1e6) * 1e6;\n\n corp.issuedShares += newShares - privateShares;\n corp.funds = corp.funds.plus(profit);\n corp.immediatelyUpdateSharePrice();\n props.onClose();\n dialogBoxCreate(\n `Issued ${numeralWrapper.format(newShares, \"0.000a\")} and raised ` +\n `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, \"0.000a\")} ` +\n `of these shares were bought by private investors.

` +\n `Stock price decreased to ${numeralWrapper.formatMoney(corp.sharePrice)}`,\n );\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) issueNewShares();\n }\n\n function onChange(event: React.ChangeEvent): void {\n if (event.target.value === \"\") setShares(null);\n else setShares(parseFloat(event.target.value));\n }\n\n return (\n \n \n You can issue new equity shares (i.e. stocks) in order to raise capital for your corporation.\n
\n
\n  * You can issue at most {numeralWrapper.formatMoney(maxNewShares)} new shares\n
\n  * New shares are sold at a 10% discount\n
\n  * You can only issue new shares once every 12 hours\n
\n  * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share\n
\n  * Number of new shares issued must be a multiple of 10 million\n
\n
\n When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares.\n If they choose to exercise this option, these newly issued shares become private, restricted shares, which means\n you cannot buy them back.\n
\n \n \n \n
\n );\n}\n","import React from \"react\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { CorporationConstants } from \"../data/Constants\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { useCorporation } from \"./Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n rerender: () => void;\n}\n\n// Create a popup that lets the player manage exports\nexport function FindInvestorsModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const val = corp.determineValuation();\n let percShares = 0;\n let roundMultiplier = 4;\n switch (corp.fundingRound) {\n case 0: //Seed\n percShares = 0.1;\n roundMultiplier = 4;\n break;\n case 1: //Series A\n percShares = 0.35;\n roundMultiplier = 3;\n break;\n case 2: //Series B\n percShares = 0.25;\n roundMultiplier = 3;\n break;\n case 3: //Series C\n percShares = 0.2;\n roundMultiplier = 2.5;\n break;\n default:\n return <>;\n }\n const funding = val * percShares * roundMultiplier;\n const investShares = Math.floor(CorporationConstants.INITIALSHARES * percShares);\n\n function findInvestors(): void {\n corp.fundingRound++;\n corp.addFunds(funding);\n corp.numShares -= investShares;\n props.rerender();\n props.onClose();\n }\n return (\n \n \n An investment firm has offered you {numeralWrapper.formatMoney(funding)} in funding in exchange for a{\" \"}\n {numeralWrapper.format(percShares * 100, \"0.000a\")}% stake in the company (\n {numeralWrapper.format(investShares, \"0.000a\")} shares).\n
\n
\n Do you accept or reject this offer?\n
\n
\n Hint: Investment firms will offer more money if your corporation is turning a profit\n
\n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { useCorporation } from \"./Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n rerender: () => void;\n}\n\n// Create a popup that lets the player manage exports\nexport function GoPublicModal(props: IProps): React.ReactElement {\n const corp = useCorporation();\n const [shares, setShares] = useState(\"\");\n const initialSharePrice = corp.determineValuation() / corp.totalShares;\n\n function goPublic(): void {\n const numShares = parseFloat(shares);\n const initialSharePrice = corp.determineValuation() / corp.totalShares;\n if (isNaN(numShares)) {\n dialogBoxCreate(\"Invalid value for number of issued shares\");\n return;\n }\n if (numShares > corp.numShares) {\n dialogBoxCreate(\"Error: You don't have that many shares to issue!\");\n return;\n }\n corp.public = true;\n corp.sharePrice = initialSharePrice;\n corp.issuedShares = numShares;\n corp.numShares -= numShares;\n corp.addFunds(numShares * initialSharePrice);\n props.rerender();\n dialogBoxCreate(\n `You took your ${corp.name} public and earned ` +\n `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`,\n );\n props.onClose();\n }\n\n function onKeyDown(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) goPublic();\n }\n\n function onChange(event: React.ChangeEvent): void {\n setShares(event.target.value);\n }\n\n return (\n \n \n Enter the number of shares you would like to issue for your IPO. These shares will be publicly sold and you will\n no longer own them. Your Corporation will receive {numeralWrapper.formatMoney(initialSharePrice)} per share (the\n IPO money will be deposited directly into your Corporation's funds).\n
\n
\n You have a total of {numeralWrapper.format(corp.numShares, \"0.000a\")} of shares that you can issue.\n
\n \n \n \n \n
\n );\n}\n","import { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport React, { useState } from \"react\";\nimport { Intro } from \"./Intro\";\nimport { Game } from \"./Game\";\nimport { Location } from \"../../Locations/Location\";\nimport { use } from \"../../ui/Context\";\n\ninterface IProps {\n location: Location;\n}\nfunction calcDifficulty(player: IPlayer, startingDifficulty: number): number {\n const totalStats = player.strength + player.defense + player.dexterity + player.agility + player.charisma;\n const difficulty = startingDifficulty - Math.pow(totalStats, 0.9) / 250 - player.intelligence / 1600;\n if (difficulty < 0) return 0;\n if (difficulty > 3) return 3;\n return difficulty;\n}\n\nexport function InfiltrationRoot(props: IProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const [start, setStart] = useState(false);\n\n if (props.location.infiltrationData === undefined) throw new Error(\"Trying to do infiltration on invalid location.\");\n const startingDifficulty = props.location.infiltrationData.startingSecurityLevel;\n const difficulty = calcDifficulty(player, startingDifficulty);\n\n function cancel(): void {\n router.toCity();\n }\n\n if (!start) {\n return (\n setStart(true)}\n cancel={cancel}\n />\n );\n }\n\n return (\n \n );\n}\n","import React from \"react\";\nimport { Location } from \"../../Locations/Location\";\nimport Grid from \"@mui/material/Grid\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n Location: Location;\n Difficulty: number;\n MaxLevel: number;\n start: () => void;\n cancel: () => void;\n}\n\nfunction arrowPart(color: string, length: number): JSX.Element {\n let arrow = \"\";\n if (length <= 0) length = 0;\n else if (length > 13) length = 13;\n else {\n length--;\n arrow = \">\";\n }\n return (\n \n {\"=\".repeat(length)}\n {arrow}\n {\" \".repeat(13 - arrow.length - length)}\n \n );\n}\n\nfunction coloredArrow(difficulty: number): JSX.Element {\n if (difficulty === 0) {\n return (\n \n {\">\"}\n {\" \".repeat(38)}\n \n );\n } else {\n return (\n <>\n {arrowPart(\"white\", difficulty * 13)}\n {arrowPart(\"orange\", (difficulty - 1) * 13)}\n {arrowPart(\"red\", (difficulty - 2) * 13)}\n \n );\n }\n}\n\nexport function Intro(props: IProps): React.ReactElement {\n return (\n <>\n \n \n Infiltrating {props.Location.name}\n \n \n \n Maximum level: {props.MaxLevel}\n \n \n \n [{coloredArrow(props.Difficulty)}]\n {` ^ ^ ^ ^`}\n {` Trivial Normal Hard Impossible`}\n \n \n \n Infiltration is a series of short minigames that get progressively harder. You take damage for failing them.\n Reaching the maximum level rewards you with intel you can trade for money or reputation.\n \n
\n \n The minigames you play are randomly selected. It might take you few tries to get used to them.\n \n
\n No game require use of the mouse.\n
\n Spacebar is the default action/confirm button.\n
\n Everything that uses arrow can also use WASD\n
\n Sometimes the rest of the keyboard is used.\n
\n \n \n \n \n \n \n
\n \n );\n}\n","import { use } from \"../../ui/Context\";\nimport React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { Countdown } from \"./Countdown\";\nimport { BracketGame } from \"./BracketGame\";\nimport { SlashGame } from \"./SlashGame\";\nimport { BackwardGame } from \"./BackwardGame\";\nimport { BribeGame } from \"./BribeGame\";\nimport { CheatCodeGame } from \"./CheatCodeGame\";\nimport { Cyberpunk2077Game } from \"./Cyberpunk2077Game\";\nimport { MinesweeperGame } from \"./MinesweeperGame\";\nimport { WireCuttingGame } from \"./WireCuttingGame\";\nimport { Victory } from \"./Victory\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n StartingDifficulty: number;\n Difficulty: number;\n MaxLevel: number;\n}\n\nenum Stage {\n Countdown = 0,\n Minigame,\n Result,\n Sell,\n}\n\nconst minigames = [\n SlashGame,\n BracketGame,\n BackwardGame,\n BribeGame,\n CheatCodeGame,\n Cyberpunk2077Game,\n MinesweeperGame,\n WireCuttingGame,\n];\n\nexport function Game(props: IProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const [level, setLevel] = useState(1);\n const [stage, setStage] = useState(Stage.Countdown);\n const [results, setResults] = useState(\"\");\n const [gameIds, setGameIds] = useState({\n lastGames: [-1, -1],\n id: Math.floor(Math.random() * minigames.length),\n });\n\n function nextGameId(): number {\n let id = gameIds.lastGames[0];\n const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id];\n while (ids.includes(id)) {\n id = Math.floor(Math.random() * minigames.length);\n }\n return id;\n }\n\n function setupNextGame(): void {\n setGameIds({\n lastGames: [gameIds.lastGames[1], gameIds.id],\n id: nextGameId(),\n });\n }\n\n function success(): void {\n pushResult(true);\n if (level === props.MaxLevel) {\n setStage(Stage.Sell);\n } else {\n setStage(Stage.Countdown);\n setLevel(level + 1);\n }\n setupNextGame();\n }\n\n function pushResult(win: boolean): void {\n setResults((old) => {\n let next = old;\n next += win ? \"✓\" : \"✗\";\n if (next.length > 15) next = next.slice(1);\n return next;\n });\n }\n\n function failure(options?: { automated: boolean }): void {\n setStage(Stage.Countdown);\n pushResult(false);\n // Kill the player immediately if they use automation, so\n // it's clear they're not meant to\n const damage = options?.automated ? player.hp : props.StartingDifficulty * 3;\n if (player.takeDamage(damage)) {\n router.toCity();\n return;\n }\n setupNextGame();\n }\n\n let stageComponent: React.ReactNode;\n switch (stage) {\n case Stage.Countdown:\n stageComponent = setStage(Stage.Minigame)} />;\n break;\n case Stage.Minigame: {\n const MiniGame = minigames[gameIds.id];\n stageComponent = ;\n break;\n }\n case Stage.Sell:\n stageComponent = (\n \n );\n break;\n }\n\n function Progress(): React.ReactElement {\n return (\n

\n {results.slice(0, results.length - 1)}\n {results[results.length - 1]}\n

\n );\n }\n\n return (\n <>\n \n \n \n Level: {level} / {props.MaxLevel}\n \n \n \n\n \n {stageComponent}\n \n \n \n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport Grid from \"@mui/material/Grid\";\n\nimport Typography from \"@mui/material/Typography\";\ninterface IProps {\n onFinish: () => void;\n}\n\nexport function Countdown(props: IProps): React.ReactElement {\n const [x, setX] = useState(3);\n useEffect(() => {\n if (x === 0) {\n props.onFinish();\n return;\n }\n setTimeout(() => setX(x - 1), 200);\n });\n\n return (\n <>\n \n \n Get Ready!\n {x}\n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { random } from \"../utils\";\nimport { interpolate } from \"./Difficulty\";\nimport { BlinkingCursor } from \"./BlinkingCursor\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n min: number;\n max: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 8000, min: 2, max: 3 },\n Normal: { timer: 6000, min: 4, max: 5 },\n Hard: { timer: 4000, min: 4, max: 6 },\n Impossible: { timer: 2500, min: 7, max: 7 },\n};\n\nfunction generateLeftSide(difficulty: Difficulty): string {\n let str = \"\";\n const length = random(difficulty.min, difficulty.max);\n for (let i = 0; i < length; i++) {\n str += [\"[\", \"<\", \"(\", \"{\"][Math.floor(Math.random() * 4)];\n }\n\n return str;\n}\n\nfunction getChar(event: KeyboardEvent): string {\n if (event.keyCode == 48 && event.shiftKey) return \")\";\n if (event.keyCode == 221 && !event.shiftKey) return \"]\";\n if (event.keyCode == 221 && event.shiftKey) return \"}\";\n if (event.keyCode == 190 && event.shiftKey) return \">\";\n return \"\";\n}\n\nfunction match(left: string, right: string): boolean {\n return (\n (left === \"[\" && right === \"]\") ||\n (left === \"<\" && right === \">\") ||\n (left === \"(\" && right === \")\") ||\n (left === \"{\" && right === \"}\")\n );\n}\n\nexport function BracketGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { timer: 0, min: 0, max: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [right, setRight] = useState(\"\");\n const [left] = useState(generateLeftSide(difficulty));\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n const char = getChar(event);\n if (!char) return;\n if (!match(left[left.length - right.length - 1], char)) {\n props.onFailure();\n return;\n }\n if (left.length === right.length + 1) {\n props.onSuccess();\n return;\n }\n setRight(right + char);\n }\n\n return (\n \n \n \n Close the brackets\n \n {`${left}${right}`}\n \n \n \n \n \n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { interpolate } from \"./Difficulty\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n window: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { window: 600 },\n Normal: { window: 325 },\n Hard: { window: 250 },\n Impossible: { window: 150 },\n};\n\nexport function SlashGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { window: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const [guarding, setGuarding] = useState(true);\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n if (event.keyCode !== 32) return;\n if (guarding) {\n props.onFailure();\n } else {\n props.onSuccess();\n }\n }\n\n useEffect(() => {\n let id2 = -1;\n const id = window.setTimeout(() => {\n setGuarding(false);\n id2 = window.setTimeout(() => setGuarding(true), difficulty.window);\n }, Math.random() * 3250 + 1500);\n return () => {\n clearInterval(id);\n if (id2 !== -1) clearInterval(id2);\n };\n }, []);\n\n return (\n \n \n \n Slash when his guard is down!\n {guarding ? \"!Guarding!\" : \"!ATTACKING!\"}\n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { random } from \"../utils\";\nimport { interpolate } from \"./Difficulty\";\nimport { BlinkingCursor } from \"./BlinkingCursor\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n min: number;\n max: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 16000, min: 3, max: 4 },\n Normal: { timer: 12500, min: 2, max: 3 },\n Hard: { timer: 15000, min: 3, max: 4 },\n Impossible: { timer: 8000, min: 4, max: 4 },\n};\n\nexport function BackwardGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { timer: 0, min: 0, max: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [answer] = useState(makeAnswer(difficulty));\n const [guess, setGuess] = useState(\"\");\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n if (event.keyCode === 16) return;\n const nextGuess = guess + event.key.toUpperCase();\n if (!answer.startsWith(nextGuess)) props.onFailure();\n else if (answer === nextGuess) props.onSuccess();\n else setGuess(nextGuess);\n }\n\n return (\n \n \n \n Type it backward\n \n \n \n {answer}\n \n \n \n {guess}\n \n \n \n \n );\n}\n\nfunction makeAnswer(difficulty: Difficulty): string {\n const length = random(difficulty.min, difficulty.max);\n let answer = \"\";\n for (let i = 0; i < length; i++) {\n if (i > 0) answer += \" \";\n answer += words[Math.floor(Math.random() * words.length)];\n }\n\n return answer;\n}\n\nconst words = [\n \"ALGORITHM\",\n \"ANALOG\",\n \"APP\",\n \"APPLICATION\",\n \"ARRAY\",\n \"BACKUP\",\n \"BANDWIDTH\",\n \"BINARY\",\n \"BIT\",\n \"BITE\",\n \"BITMAP\",\n \"BLOG\",\n \"BLOGGER\",\n \"BOOKMARK\",\n \"BOOT\",\n \"BROADBAND\",\n \"BROWSER\",\n \"BUFFER\",\n \"BUG\",\n \"BUS\",\n \"BYTE\",\n \"CACHE\",\n \"CAPS LOCK\",\n \"CAPTCHA\",\n \"CD\",\n \"CD-ROM\",\n \"CLIENT\",\n \"CLIPBOARD\",\n \"CLOUD\",\n \"COMPUTING\",\n \"COMMAND\",\n \"COMPILE\",\n \"COMPRESS\",\n \"COMPUTER\",\n \"CONFIGURE\",\n \"COOKIE\",\n \"COPY\",\n \"CPU\",\n \"CYBERCRIME\",\n \"CYBERSPACE\",\n \"DASHBOARD\",\n \"DATA\",\n \"MINING\",\n \"DATABASE\",\n \"DEBUG\",\n \"DECOMPRESS\",\n \"DELETE\",\n \"DESKTOP\",\n \"DEVELOPMENT\",\n \"DIGITAL\",\n \"DISK\",\n \"DNS\",\n \"DOCUMENT\",\n \"DOMAIN\",\n \"DOMAIN NAME\",\n \"DOT\",\n \"DOT MATRIX\",\n \"DOWNLOAD\",\n \"DRAG\",\n \"DVD\",\n \"DYNAMIC\",\n \"EMAIL\",\n \"EMOTICON\",\n \"ENCRYPT\",\n \"ENCRYPTION\",\n \"ENTER\",\n \"EXABYTE\",\n \"FAQ\",\n \"FILE\",\n \"FINDER\",\n \"FIREWALL\",\n \"FIRMWARE\",\n \"FLAMING\",\n \"FLASH\",\n \"FLASH DRIVE\",\n \"FLOPPY DISK\",\n \"FLOWCHART\",\n \"FOLDER\",\n \"FONT\",\n \"FORMAT\",\n \"FRAME\",\n \"FREEWARE\",\n \"GIGABYTE\",\n \"GRAPHICS\",\n \"HACK\",\n \"HACKER\",\n \"HARDWARE\",\n \"HOME PAGE\",\n \"HOST\",\n \"HTML\",\n \"HYPERLINK\",\n \"HYPERTEXT\",\n \"ICON\",\n \"INBOX\",\n \"INTEGER\",\n \"INTERFACE\",\n \"INTERNET\",\n \"IP ADDRESS\",\n \"ITERATION\",\n \"JAVA\",\n \"JOYSTICK\",\n \"JUNKMAIL\",\n \"KERNEL\",\n \"KEY\",\n \"KEYBOARD\",\n \"KEYWORD\",\n \"LAPTOP\",\n \"LASER PRINTER\",\n \"LINK\",\n \"LINUX\",\n \"LOG OUT\",\n \"LOGIC\",\n \"LOGIN\",\n \"LURKING\",\n \"MACINTOSH\",\n \"MACRO\",\n \"MAINFRAME\",\n \"MALWARE\",\n \"MEDIA\",\n \"MEMORY\",\n \"MIRROR\",\n \"MODEM\",\n \"MONITOR\",\n \"MOTHERBOARD\",\n \"MOUSE\",\n \"MULTIMEDIA\",\n \"NET\",\n \"NETWORK\",\n \"NODE\",\n \"NOTEBOOK\",\n \"COMPUTER\",\n \"OFFLINE\",\n \"ONLINE\",\n \"OPENSOURCE\",\n \"OPERATING\",\n \"SYSTEM\",\n \"OPTION\",\n \"OUTPUT\",\n \"PAGE\",\n \"PASSWORD\",\n \"PASTE\",\n \"PATH\",\n \"PHISHING\",\n \"PIRACY\",\n \"PIRATE\",\n \"PLATFORM\",\n \"PLUGIN\",\n \"PODCAST\",\n \"POPUP\",\n \"PORTAL\",\n \"PRINT\",\n \"PRINTER\",\n \"PRIVACY\",\n \"PROCESS\",\n \"PROGRAM\",\n \"PROGRAMMER\",\n \"PROTOCOL\",\n \"QUEUE\",\n \"QWERTY\",\n \"RAM\",\n \"REALTIME\",\n \"REBOOT\",\n \"RESOLUTION\",\n \"RESTORE\",\n \"ROM\",\n \"ROOT\",\n \"ROUTER\",\n \"RUNTIME\",\n \"SAVE\",\n \"SCAN\",\n \"SCANNER\",\n \"SCREEN\",\n \"SCREENSHOT\",\n \"SCRIPT\",\n \"SCROLL\",\n \"SCROLL\",\n \"SEARCH\",\n \"ENGINE\",\n \"SECURITY\",\n \"SERVER\",\n \"SHAREWARE\",\n \"SHELL\",\n \"SHIFT\",\n \"SHIFT KEY\",\n \"SNAPSHOT\",\n \"SOCIAL NETWORKING\",\n \"SOFTWARE\",\n \"SPAM\",\n \"SPAMMER\",\n \"SPREADSHEET\",\n \"SPYWARE\",\n \"STATUS\",\n \"STORAGE\",\n \"SUPERCOMPUTER\",\n \"SURF\",\n \"SYNTAX\",\n \"TABLE\",\n \"TAG\",\n \"TERMINAL\",\n \"TEMPLATE\",\n \"TERABYTE\",\n \"TEXT EDITOR\",\n \"THREAD\",\n \"TOOLBAR\",\n \"TRASH\",\n \"TROJAN HORSE\",\n \"TYPEFACE\",\n \"UNDO\",\n \"UNIX\",\n \"UPLOAD\",\n \"URL\",\n \"USER\",\n \"USER INTERFACE\",\n \"USERNAME\",\n \"UTILITY\",\n \"VERSION\",\n \"VIRTUAL\",\n \"VIRTUAL MEMORY\",\n \"VIRUS\",\n \"WEB\",\n \"WEBMASTER\",\n \"WEBSITE\",\n \"WIDGET\",\n \"WIKI\",\n \"WINDOW\",\n \"WINDOWS\",\n \"WIRELESS\",\n \"PROCESSOR\",\n \"WORKSTATION\",\n \"WEB\",\n \"WORM\",\n \"WWW\",\n \"XML\",\n \"ZIP\",\n];\n","import React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { interpolate } from \"./Difficulty\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n size: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 12000, size: 6 },\n Normal: { timer: 9000, size: 8 },\n Hard: { timer: 5000, size: 9 },\n Impossible: { timer: 2500, size: 12 },\n};\n\nexport function BribeGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { timer: 0, size: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [choices] = useState(makeChoices(difficulty));\n const [index, setIndex] = useState(0);\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n const k = event.keyCode;\n if (k === 32) {\n if (positive.includes(choices[index])) props.onSuccess();\n else props.onFailure();\n return;\n }\n\n let newIndex = index;\n if ([38, 87, 68, 39].includes(k)) newIndex++;\n if ([65, 37, 83, 40].includes(k)) newIndex--;\n while (newIndex < 0) newIndex += choices.length;\n while (newIndex > choices.length - 1) newIndex -= choices.length;\n setIndex(newIndex);\n }\n\n return (\n \n \n \n Say something nice about the guard.\n \n \n \n \n ↑\n \n \n {choices[index]}\n \n \n ↓\n \n \n \n );\n}\n\nfunction shuffleArray(array: string[]): void {\n for (let i = array.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n const temp = array[i];\n array[i] = array[j];\n array[j] = temp;\n }\n}\n\nfunction makeChoices(difficulty: Difficulty): string[] {\n const choices = [];\n choices.push(positive[Math.floor(Math.random() * positive.length)]);\n for (let i = 0; i < difficulty.size; i++) {\n const option = negative[Math.floor(Math.random() * negative.length)];\n if (choices.includes(option)) {\n i--;\n continue;\n }\n choices.push(option);\n }\n shuffleArray(choices);\n return choices;\n}\n\nconst positive = [\n \"affectionate\",\n \"agreeable\",\n \"bright\",\n \"charming\",\n \"creative\",\n \"determined\",\n \"energetic\",\n \"friendly\",\n \"funny\",\n \"generous\",\n \"polite\",\n \"likable\",\n \"diplomatic\",\n \"helpful\",\n \"giving\",\n \"kind\",\n \"hardworking\",\n \"patient\",\n \"dynamic\",\n \"loyal\",\n];\n\nconst negative = [\n \"aggressive\",\n \"aloof\",\n \"arrogant\",\n \"big-headed\",\n \"boastful\",\n \"boring\",\n \"bossy\",\n \"careless\",\n \"clingy\",\n \"couch potato\",\n \"cruel\",\n \"cynical\",\n \"grumpy\",\n \"hot air\",\n \"know it all\",\n \"obnoxious\",\n \"pain in the neck\",\n \"picky\",\n \"tactless\",\n \"thoughtless\",\n];\n","import React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { random, getArrow } from \"../utils\";\nimport { interpolate } from \"./Difficulty\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n min: number;\n max: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 13000, min: 6, max: 8 },\n Normal: { timer: 7000, min: 7, max: 8 },\n Hard: { timer: 5000, min: 8, max: 9 },\n Impossible: { timer: 3000, min: 9, max: 10 },\n};\n\nexport function CheatCodeGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { timer: 0, min: 0, max: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [code] = useState(generateCode(difficulty));\n const [index, setIndex] = useState(0);\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n if (code[index] !== getArrow(event)) {\n props.onFailure();\n return;\n }\n setIndex(index + 1);\n if (index + 1 >= code.length) props.onSuccess();\n }\n\n return (\n \n \n \n Enter the Code!\n {code[index]}\n \n \n \n );\n}\n\nfunction generateCode(difficulty: Difficulty): string {\n const arrows = [\"←\", \"→\", \"↑\", \"↓\"];\n let code = \"\";\n for (let i = 0; i < random(difficulty.min, difficulty.max); i++) {\n let arrow = arrows[Math.floor(4 * Math.random())];\n while (arrow === code[code.length - 1]) arrow = arrows[Math.floor(4 * Math.random())];\n code += arrow;\n }\n\n return code;\n}\n","import React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { interpolate } from \"./Difficulty\";\nimport { getArrow } from \"../utils\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n width: number;\n height: number;\n symbols: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 12500, width: 3, height: 3, symbols: 6 },\n Normal: { timer: 15000, width: 4, height: 4, symbols: 7 },\n Hard: { timer: 12500, width: 5, height: 5, symbols: 8 },\n Impossible: { timer: 10000, width: 6, height: 6, symbols: 9 },\n};\n\nexport function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { timer: 0, width: 0, height: 0, symbols: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [grid] = useState(generatePuzzle(difficulty));\n const [answer] = useState(generateAnswer(grid, difficulty));\n const [index, setIndex] = useState(0);\n const [pos, setPos] = useState([0, 0]);\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n const move = [0, 0];\n const arrow = getArrow(event);\n switch (arrow) {\n case \"↑\":\n move[1]--;\n break;\n case \"←\":\n move[0]--;\n break;\n case \"↓\":\n move[1]++;\n break;\n case \"→\":\n move[0]++;\n break;\n }\n const next = [pos[0] + move[0], pos[1] + move[1]];\n next[0] = (next[0] + grid[0].length) % grid[0].length;\n next[1] = (next[1] + grid.length) % grid.length;\n setPos(next);\n\n if (event.keyCode == 32) {\n const selected = grid[pos[1]][pos[0]];\n const expected = answer[index];\n if (selected !== expected) {\n props.onFailure();\n return;\n }\n setIndex(index + 1);\n if (answer.length === index + 1) props.onSuccess();\n }\n }\n\n const fontSize = \"2em\";\n return (\n \n \n \n Match the symbols!\n \n Targets:{\" \"}\n {answer.map((a, i) => {\n if (i == index)\n return (\n \n {a} \n \n );\n return (\n \n {a} \n \n );\n })}\n \n
\n {grid.map((line, y) => (\n
\n
\n              {line.map((cell, x) => {\n                if (x == pos[0] && y == pos[1])\n                  return (\n                    \n                      {cell} \n                    \n                  );\n                return (\n                  \n                    {cell} \n                  \n                );\n              })}\n            
\n
\n
\n ))}\n \n
\n
\n );\n}\n\nfunction generateAnswer(grid: string[][], difficulty: Difficulty): string[] {\n const answer = [];\n for (let i = 0; i < Math.round(difficulty.symbols); i++) {\n answer.push(grid[Math.floor(Math.random() * grid.length)][Math.floor(Math.random() * grid[0].length)]);\n }\n return answer;\n}\n\nfunction randChar(): string {\n return \"ABCDEF0123456789\"[Math.floor(Math.random() * 16)];\n}\n\nfunction generatePuzzle(difficulty: Difficulty): string[][] {\n const puzzle = [];\n for (let i = 0; i < Math.round(difficulty.height); i++) {\n const line = [];\n for (let j = 0; j < Math.round(difficulty.width); j++) {\n line.push(randChar() + randChar());\n }\n puzzle.push(line);\n }\n return puzzle;\n}\n","import React, { useState, useEffect } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { interpolate } from \"./Difficulty\";\nimport { getArrow } from \"../utils\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n width: number;\n height: number;\n mines: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 15000, width: 3, height: 3, mines: 4 },\n Normal: { timer: 15000, width: 4, height: 4, mines: 7 },\n Hard: { timer: 15000, width: 5, height: 5, mines: 11 },\n Impossible: { timer: 15000, width: 6, height: 6, mines: 15 },\n};\n\nexport function MinesweeperGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = { timer: 0, width: 0, height: 0, mines: 0 };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [minefield] = useState(generateMinefield(difficulty));\n const [answer, setAnswer] = useState(generateEmptyField(difficulty));\n const [pos, setPos] = useState([0, 0]);\n const [memoryPhase, setMemoryPhase] = useState(true);\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n if (memoryPhase) return;\n const move = [0, 0];\n const arrow = getArrow(event);\n switch (arrow) {\n case \"↑\":\n move[1]--;\n break;\n case \"←\":\n move[0]--;\n break;\n case \"↓\":\n move[1]++;\n break;\n case \"→\":\n move[0]++;\n break;\n }\n const next = [pos[0] + move[0], pos[1] + move[1]];\n next[0] = (next[0] + minefield[0].length) % minefield[0].length;\n next[1] = (next[1] + minefield.length) % minefield.length;\n setPos(next);\n\n if (event.keyCode == 32) {\n if (!minefield[pos[1]][pos[0]]) {\n props.onFailure();\n return;\n }\n setAnswer((old) => {\n old[pos[1]][pos[0]] = true;\n if (fieldEquals(minefield, old)) props.onSuccess();\n return old;\n });\n }\n }\n\n useEffect(() => {\n const id = setTimeout(() => setMemoryPhase(false), 2000);\n return () => clearInterval(id);\n }, []);\n\n return (\n \n \n \n {memoryPhase ? \"Remember all the mines!\" : \"Mark all the mines!\"}\n {minefield.map((line, y) => (\n
\n
\n              {line.map((cell, x) => {\n                if (memoryPhase) {\n                  if (minefield[y][x]) return [?] ;\n                  return [ ] ;\n                } else {\n                  if (x == pos[0] && y == pos[1]) return [X] ;\n                  if (answer[y][x]) return [.] ;\n                  return [ ] ;\n                }\n              })}\n            
\n
\n
\n ))}\n \n
\n
\n );\n}\n\nfunction fieldEquals(a: boolean[][], b: boolean[][]): boolean {\n function count(field: boolean[][]): number {\n return field.flat().reduce((a, b) => a + (b ? 1 : 0), 0);\n }\n return count(a) === count(b);\n}\n\nfunction generateEmptyField(difficulty: Difficulty): boolean[][] {\n const field = [];\n for (let i = 0; i < difficulty.height; i++) {\n field.push(new Array(Math.round(difficulty.width)).fill(false));\n }\n return field;\n}\n\nfunction generateMinefield(difficulty: Difficulty): boolean[][] {\n const field = generateEmptyField(difficulty);\n for (let i = 0; i < difficulty.mines; i++) {\n const x = Math.floor(Math.random() * field.length);\n const y = Math.floor(Math.random() * field[0].length);\n if (field[x][y]) {\n i--;\n continue;\n }\n field[x][y] = true;\n }\n return field;\n}\n","import React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport Typography from \"@mui/material/Typography\";\nimport { IMinigameProps } from \"./IMinigameProps\";\nimport { KeyHandler } from \"./KeyHandler\";\nimport { GameTimer } from \"./GameTimer\";\nimport { random } from \"../utils\";\nimport { interpolate } from \"./Difficulty\";\n\ninterface Difficulty {\n [key: string]: number;\n timer: number;\n wiresmin: number;\n wiresmax: number;\n rules: number;\n}\n\nconst difficulties: {\n Trivial: Difficulty;\n Normal: Difficulty;\n Hard: Difficulty;\n Impossible: Difficulty;\n} = {\n Trivial: { timer: 9000, wiresmin: 4, wiresmax: 4, rules: 2 },\n Normal: { timer: 7000, wiresmin: 6, wiresmax: 6, rules: 2 },\n Hard: { timer: 5000, wiresmin: 8, wiresmax: 8, rules: 3 },\n Impossible: { timer: 4000, wiresmin: 9, wiresmax: 9, rules: 4 },\n};\n\nconst types = [\"|\", \".\", \"/\", \"-\", \"█\", \"#\"];\n\nconst colors = [\"red\", \"#FFC107\", \"blue\", \"white\"];\n\nconst colorNames: any = {\n red: \"red\",\n \"#FFC107\": \"yellow\",\n blue: \"blue\",\n white: \"white\",\n};\n\ninterface Wire {\n tpe: string;\n colors: string[];\n}\n\ninterface Question {\n toString: () => string;\n shouldCut: (wire: Wire, index: number) => boolean;\n}\n\nexport function WireCuttingGame(props: IMinigameProps): React.ReactElement {\n const difficulty: Difficulty = {\n timer: 0,\n wiresmin: 0,\n wiresmax: 0,\n rules: 0,\n };\n interpolate(difficulties, props.difficulty, difficulty);\n const timer = difficulty.timer;\n const [wires] = useState(generateWires(difficulty));\n const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false));\n const [questions] = useState(generateQuestion(wires, difficulty));\n\n function press(this: Document, event: KeyboardEvent): void {\n event.preventDefault();\n const wireNum = parseInt(event.key);\n\n if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;\n setCutWires((old) => {\n const next = [...old];\n next[wireNum - 1] = true;\n if (!questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1))) {\n props.onFailure();\n }\n\n // check if we won\n const wiresToBeCut = [];\n for (let j = 0; j < wires.length; j++) {\n let shouldBeCut = false;\n for (let i = 0; i < questions.length; i++) {\n shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j);\n }\n wiresToBeCut.push(shouldBeCut);\n }\n if (wiresToBeCut.every((b, i) => b === next[i])) {\n props.onSuccess();\n }\n\n return next;\n });\n }\n\n return (\n \n \n \n Cut the wires with the following properties! (keyboard 1 to 9)\n {questions.map((question, i) => (\n {question.toString()}\n ))}\n
\n          {new Array(wires.length).fill(0).map((_, i) => (\n             {i + 1}    \n          ))}\n        
\n {new Array(8).fill(0).map((_, i) => (\n
\n
\n              {wires.map((wire, j) => {\n                if ((i === 3 || i === 4) && cutWires[j])\n                  return       ;\n                return (\n                  \n                    |{wire.tpe}|   \n                  \n                );\n              })}\n            
\n
\n ))}\n \n
\n
\n );\n}\n\nfunction randomPositionQuestion(wires: Wire[]): Question {\n const index = Math.floor(Math.random() * wires.length);\n return {\n toString: (): string => {\n return `Cut wires number ${index + 1}.`;\n },\n shouldCut: (wire: Wire, i: number): boolean => {\n return index === i;\n },\n };\n}\n\nfunction randomColorQuestion(wires: Wire[]): Question {\n const index = Math.floor(Math.random() * wires.length);\n const cutColor = wires[index].colors[0];\n return {\n toString: (): string => {\n return `Cut all wires colored ${colorNames[cutColor]}.`;\n },\n shouldCut: (wire: Wire): boolean => {\n return wire.colors.includes(cutColor);\n },\n };\n}\n\nfunction generateQuestion(wires: Wire[], difficulty: Difficulty): Question[] {\n const numQuestions = difficulty.rules;\n const questionGenerators = [randomPositionQuestion, randomColorQuestion];\n const questions = [];\n for (let i = 0; i < numQuestions; i++) {\n questions.push(questionGenerators[i % 2](wires));\n }\n return questions;\n}\n\nfunction generateWires(difficulty: Difficulty): Wire[] {\n const wires = [];\n const numWires = random(difficulty.wiresmin, difficulty.wiresmax);\n for (let i = 0; i < numWires; i++) {\n const wireColors = [colors[Math.floor(Math.random() * colors.length)]];\n if (Math.random() < 0.15) {\n wireColors.push(colors[Math.floor(Math.random() * colors.length)]);\n }\n wires.push({\n tpe: types[Math.floor(Math.random() * types.length)],\n colors: wireColors,\n });\n }\n return wires;\n}\n","import { Factions } from \"../../Faction/Factions\";\nimport React, { useState } from \"react\";\nimport Grid from \"@mui/material/Grid\";\nimport { Money } from \"../../ui/React/Money\";\nimport { Reputation } from \"../../ui/React/Reputation\";\nimport { BitNodeMultipliers } from \"../../BitNode/BitNodeMultipliers\";\nimport { use } from \"../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n StartingDifficulty: number;\n Difficulty: number;\n MaxLevel: number;\n}\n\nexport function Victory(props: IProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const [faction, setFaction] = useState(\"none\");\n\n function quitInfiltration(): void {\n router.toCity();\n }\n\n const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel);\n\n const repGain =\n Math.pow(props.Difficulty + 1, 1.1) *\n Math.pow(props.StartingDifficulty, 1.2) *\n 30 *\n levelBonus *\n BitNodeMultipliers.InfiltrationRep;\n\n const moneyGain =\n Math.pow(props.Difficulty + 1, 2) *\n Math.pow(props.StartingDifficulty, 3) *\n 3e3 *\n levelBonus *\n BitNodeMultipliers.InfiltrationMoney;\n\n function sell(): void {\n player.gainMoney(moneyGain);\n player.recordMoneySource(moneyGain, \"infiltration\");\n quitInfiltration();\n }\n\n function trade(): void {\n if (faction === \"none\") return;\n Factions[faction].playerReputation += repGain;\n quitInfiltration();\n }\n\n function changeDropdown(event: SelectChangeEvent): void {\n setFaction(event.target.value);\n }\n\n return (\n <>\n \n \n Infiltration successful!\n \n \n \n You can trade the confidential information you found for money or reputation.\n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n","import React, { useState } from \"react\";\n\nimport { generateResleeves } from \"../Resleeving\";\nimport { Resleeve } from \"../Resleeve\";\nimport { ResleeveElem } from \"./ResleeveElem\";\nimport { use } from \"../../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Box from \"@mui/material/Box\";\n\nconst SortOption: {\n [key: string]: string | undefined;\n Cost: string;\n Hacking: string;\n Strength: string;\n Defense: string;\n Dexterity: string;\n Agility: string;\n Charisma: string;\n AverageCombatStats: string;\n AverageAllStats: string;\n TotalNumAugmentations: string;\n} = {\n Cost: \"Cost\",\n Hacking: \"Hacking Level\",\n Strength: \"Strength Level\",\n Defense: \"Defense Level\",\n Dexterity: \"Dexterity Level\",\n Agility: \"Agility Level\",\n Charisma: \"Charisma Level\",\n AverageCombatStats: \"Average Combat Stats\",\n AverageAllStats: \"Average Stats\",\n TotalNumAugmentations: \"Number of Augmentations\",\n};\n\n// Helper function for averaging\nfunction getAverage(...values: number[]): number {\n let sum = 0;\n for (let i = 0; i < values.length; ++i) {\n sum += values[i];\n }\n\n return sum / values.length;\n}\n\nconst SortFunctions: {\n [key: string]: ((a: Resleeve, b: Resleeve) => number) | undefined;\n Cost: (a: Resleeve, b: Resleeve) => number;\n Hacking: (a: Resleeve, b: Resleeve) => number;\n Strength: (a: Resleeve, b: Resleeve) => number;\n Defense: (a: Resleeve, b: Resleeve) => number;\n Dexterity: (a: Resleeve, b: Resleeve) => number;\n Agility: (a: Resleeve, b: Resleeve) => number;\n Charisma: (a: Resleeve, b: Resleeve) => number;\n AverageCombatStats: (a: Resleeve, b: Resleeve) => number;\n AverageAllStats: (a: Resleeve, b: Resleeve) => number;\n TotalNumAugmentations: (a: Resleeve, b: Resleeve) => number;\n} = {\n Cost: (a: Resleeve, b: Resleeve): number => a.getCost() - b.getCost(),\n Hacking: (a: Resleeve, b: Resleeve): number => a.hacking_skill - b.hacking_skill,\n Strength: (a: Resleeve, b: Resleeve): number => a.strength - b.strength,\n Defense: (a: Resleeve, b: Resleeve): number => a.defense - b.defense,\n Dexterity: (a: Resleeve, b: Resleeve): number => a.dexterity - b.dexterity,\n Agility: (a: Resleeve, b: Resleeve): number => a.agility - b.agility,\n Charisma: (a: Resleeve, b: Resleeve): number => a.charisma - b.charisma,\n AverageCombatStats: (a: Resleeve, b: Resleeve): number =>\n getAverage(a.strength, a.defense, a.dexterity, a.agility) -\n getAverage(b.strength, b.defense, b.dexterity, b.agility),\n AverageAllStats: (a: Resleeve, b: Resleeve): number =>\n getAverage(a.hacking_skill, a.strength, a.defense, a.dexterity, a.agility, a.charisma) -\n getAverage(b.hacking_skill, b.strength, b.defense, b.dexterity, b.agility, b.charisma),\n TotalNumAugmentations: (a: Resleeve, b: Resleeve): number => a.augmentations.length - b.augmentations.length,\n};\n\nexport function ResleeveRoot(): React.ReactElement {\n const player = use.Player();\n const [sort, setSort] = useState(SortOption.Cost);\n // Randomly create all Resleeves if they dont already exist\n if (player.resleeves.length === 0) {\n player.resleeves = generateResleeves();\n }\n\n function onSortChange(event: SelectChangeEvent): void {\n setSort(event.target.value);\n }\n\n const sortFunction = SortFunctions[sort];\n if (sortFunction === undefined) throw new Error(`sort function '${sort}' is undefined`);\n player.resleeves.sort(sortFunction);\n\n return (\n <>\n \n Re-sleeving is the process of digitizing and transferring your consciousness into a new human body, or 'sleeve'.\n Here at VitaLife, you can purchase new specially-engineered bodies for the re-sleeve process. Many of these\n bodies even come with genetic and cybernetic Augmentations!\n
\n
\n Re-sleeving will change your experience for every stat. It will also REMOVE all of your currently-installed\n Augmentations, and replace them with the ones provided by the purchased sleeve. However, Augmentations that you\n have purchased but not installed will NOT be removed. If you have purchased an Augmentation and then re-sleeve\n into a body which already has that Augmentation, it will be removed (since you cannot have duplicate\n Augmentations).\n
\n
\n NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File.\n
\n \n Sort By: \n \n \n {player.resleeves.map((resleeve, i) => (\n \n ))}\n \n );\n}\n","/**\n * Implements the Resleeve class, which defines a new body\n * that the player can \"re-sleeve\" into.\n */\nimport { Person } from \"../Person\";\n\nimport { Augmentation } from \"../../Augmentation/Augmentation\";\nimport { Augmentations } from \"../../Augmentation/Augmentations\";\n\nimport { Generic_fromJSON, Generic_toJSON, Reviver } from \"../../utils/JSONReviver\";\n\nexport class Resleeve extends Person {\n constructor() {\n super();\n }\n\n getCost(): number {\n // Each experience point adds this to the cost\n const CostPerExp = 25e3;\n\n // Final cost is multiplied by this constant ^ # Augs\n const NumAugsExponent = 1.2;\n\n // Get total exp in this re-sleeve\n const totalExp: number =\n this.hacking_exp +\n this.strength_exp +\n this.defense_exp +\n this.dexterity_exp +\n this.agility_exp +\n this.charisma_exp;\n\n // Get total base Augmentation cost for this re-sleeve\n let totalAugmentationCost = 0;\n for (let i = 0; i < this.augmentations.length; ++i) {\n const aug: Augmentation | null = Augmentations[this.augmentations[i].name];\n if (aug == null) {\n console.error(`Could not find Augmentation ${this.augmentations[i].name}`);\n continue;\n }\n totalAugmentationCost += aug.startingCost;\n }\n\n return totalExp * CostPerExp + totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length);\n }\n\n /**\n * Serialize the current object to a JSON save state.\n */\n toJSON(): any {\n return Generic_toJSON(\"Resleeve\", this);\n }\n\n /**\n * Initiatizes a Resleeve object from a JSON save state.\n */\n // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types\n static fromJSON(value: any): Resleeve {\n return Generic_fromJSON(Resleeve, value.data);\n }\n}\n\nReviver.constructors.Resleeve = Resleeve;\n","import React, { useState } from \"react\";\nimport { IPlayer } from \"../../IPlayer\";\nimport { Resleeve } from \"../Resleeve\";\nimport { Augmentations } from \"../../../Augmentation/Augmentations\";\nimport { purchaseResleeve } from \"../Resleeving\";\nimport { Money } from \"../../../ui/React/Money\";\n\nimport { numeralWrapper } from \"../../../ui/numeralFormat\";\nimport { dialogBoxCreate } from \"../../../ui/React/DialogBox\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Grid from \"@mui/material/Grid\";\n\ninterface IProps {\n resleeve: Resleeve;\n player: IPlayer;\n}\n\nexport function ResleeveElem(props: IProps): React.ReactElement {\n const [aug, setAug] = useState(props.resleeve.augmentations[0].name);\n\n function openStats(): void {\n dialogBoxCreate(\n <>\n \n Total Multipliers:\n \n \n Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)}\n
\n Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)}\n
\n Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)}\n
\n Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)}\n
\n Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)}\n
\n Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)}\n
\n Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)}\n
\n Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)}\n
\n Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)}\n
\n Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)}\n
\n Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)}\n
\n Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)}\n
\n Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)}\n
\n Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)}\n
\n Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)}\n
\n Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)}\n
\n Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)}\n
\n Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)}\n
\n Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)}\n
\n Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)}\n
\n Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)}\n
\n Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)}\n
\n Hacknet Purchase Cost multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)}\n
\n Hacknet Level Upgrade Cost multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)}\n
\n Hacknet Ram Upgrade Cost multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)}\n
\n Hacknet Core Upgrade Cost multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)}\n
\n Bladeburner Max Stamina multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)}\n
\n Bladeburner Stamina Gain multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)}\n
\n Bladeburner Field Analysis multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)}\n
\n Bladeburner Success Chance multiplier:\n {numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)}\n
\n ,\n );\n }\n\n function onAugChange(event: SelectChangeEvent): void {\n setAug(event.target.value);\n }\n\n const currentAug = Augmentations[aug];\n const cost = props.resleeve.getCost();\n\n function purchase(): void {\n if (!purchaseResleeve(props.resleeve, props.player)) return;\n dialogBoxCreate(\n <>\n You re-sleeved for !\n ,\n );\n }\n\n return (\n \n \n \n \n Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking_skill)} (\n {numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp)\n
\n Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} (\n {numeralWrapper.formatExp(props.resleeve.strength_exp)} exp)\n
\n Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} (\n {numeralWrapper.formatExp(props.resleeve.defense_exp)} exp)\n
\n Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} (\n {numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp)\n
\n Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} (\n {numeralWrapper.formatExp(props.resleeve.agility_exp)} exp)\n
\n Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} (\n {numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp)\n
# Augmentations: {props.resleeve.augmentations.length}\n
\n \n
\n \n \n {currentAug !== undefined && currentAug.info}\n \n \n \n It costs to purchase this Sleeve.\n \n \n \n
\n
\n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport { use } from \"./Context\";\nimport { CONSTANTS } from \"../Constants\";\nimport { numeralWrapper } from \"./numeralFormat\";\nimport { Reputation } from \"./React/Reputation\";\nimport { ReputationRate } from \"./React/ReputationRate\";\nimport { MoneyRate } from \"./React/MoneyRate\";\nimport { Money } from \"./React/Money\";\nimport { convertTimeMsToTimeElapsedString } from \"../utils/StringHelperFunctions\";\nimport { Factions } from \"../Faction/Factions\";\nimport { Company } from \"../Company/Company\";\nimport { Companies } from \"../Company/Companies\";\nimport { Locations } from \"../Locations/Locations\";\nimport { LocationName } from \"../Locations/data/LocationNames\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Grid from \"@mui/material/Grid\";\nimport Button from \"@mui/material/Button\";\n\nimport { createProgressBarText } from \"../utils/helpers/createProgressBarText\";\n\nconst CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;\n\nexport function WorkInProgressRoot(): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, CONSTANTS.MilliPerCycle);\n return () => clearInterval(id);\n }, []);\n const player = use.Player();\n const router = use.Router();\n const faction = Factions[player.currentWorkFactionName];\n if (player.workType == CONSTANTS.WorkTypeFaction) {\n function cancel(): void {\n router.toFaction(faction);\n player.finishFactionWork(true);\n }\n function unfocus(): void {\n router.toFaction(faction);\n player.stopFocusing();\n }\n return (\n \n \n \n You are currently {player.currentWorkFactionDescription} for your faction {faction.name}\n
\n (Current Faction Reputation: \n ).
\n You have been doing this for {convertTimeMsToTimeElapsedString(player.timeWorked)}\n
\n
\n You have earned:
\n
\n (){\" \"}\n
\n
\n (\n ) reputation for this faction
\n
\n {numeralWrapper.formatExp(player.workHackExpGained)} (\n {numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp
\n
\n {numeralWrapper.formatExp(player.workStrExpGained)} (\n {numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp
\n {numeralWrapper.formatExp(player.workDefExpGained)} (\n {numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp
\n {numeralWrapper.formatExp(player.workDexExpGained)} (\n {numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp
\n {numeralWrapper.formatExp(player.workAgiExpGained)} (\n {numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp
\n
\n {numeralWrapper.formatExp(player.workChaExpGained)} (\n {numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp
\n
\n You will automatically finish after working for 20 hours. You can cancel earlier if you wish.\n
\n There is no penalty for cancelling earlier.\n
\n
\n \n \n \n \n
\n );\n }\n\n const className = player.className;\n if (player.className !== \"\") {\n function cancel(): void {\n player.finishClass(true);\n router.toCity();\n }\n\n let stopText = \"\";\n if (\n className == CONSTANTS.ClassGymStrength ||\n className == CONSTANTS.ClassGymDefense ||\n className == CONSTANTS.ClassGymDexterity ||\n className == CONSTANTS.ClassGymAgility\n ) {\n stopText = \"Stop training at gym\";\n } else {\n stopText = \"Stop taking course\";\n }\n\n return (\n \n \n \n You have been {className} for {convertTimeMsToTimeElapsedString(player.timeWorked)}\n
\n
\n This has cost you:
\n (){\" \"}\n
\n
\n You have gained:
\n {numeralWrapper.formatExp(player.workHackExpGained)} (\n {numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp
\n {numeralWrapper.formatExp(player.workStrExpGained)} (\n {numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp
\n {numeralWrapper.formatExp(player.workDefExpGained)} (\n {numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp
\n {numeralWrapper.formatExp(player.workDexExpGained)} (\n {numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp
\n {numeralWrapper.formatExp(player.workAgiExpGained)} (\n {numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp
\n {numeralWrapper.formatExp(player.workChaExpGained)} (\n {numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp
\n You may cancel at any time\n
\n
\n \n \n \n
\n );\n }\n\n if (player.workType == CONSTANTS.WorkTypeCompany) {\n const comp = Companies[player.companyName];\n let companyRep = 0;\n if (comp == null || !(comp instanceof Company)) {\n throw new Error(`Could not find Company: ${player.companyName}`);\n }\n companyRep = comp.playerReputation;\n\n function cancel(): void {\n player.finishWork(true);\n router.toJob();\n }\n function unfocus(): void {\n player.stopFocusing();\n router.toJob();\n }\n\n const position = player.jobs[player.companyName];\n\n const penalty = player.cancelationPenalty();\n\n const penaltyString = penalty === 0.5 ? \"half\" : \"three-quarters\";\n return (\n \n \n \n You are currently working as a {position} at {player.companyName} (Current Company Reputation:{\" \"}\n )
\n
\n You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}\n
\n
\n You have earned:
\n
\n (){\" \"}\n
\n
\n (\n ) reputation for this company
\n
\n {numeralWrapper.formatExp(player.workHackExpGained)} (\n {`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) hacking exp
\n
\n {numeralWrapper.formatExp(player.workStrExpGained)} (\n {`${numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) strength exp
\n {numeralWrapper.formatExp(player.workDefExpGained)} (\n {`${numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) defense exp
\n {numeralWrapper.formatExp(player.workDexExpGained)} (\n {`${numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) dexterity exp
\n {numeralWrapper.formatExp(player.workAgiExpGained)} (\n {`${numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) agility exp
\n
\n {numeralWrapper.formatExp(player.workChaExpGained)} (\n {`${numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) charisma exp
\n
\n You will automatically finish after working for 8 hours. You can cancel earlier if you wish, but you will\n only gain {penaltyString} of the reputation you've earned so far.\n
\n
\n \n \n \n \n
\n );\n }\n\n if (player.workType == CONSTANTS.WorkTypeCompanyPartTime) {\n function cancel(): void {\n player.finishWork(true);\n router.toJob();\n }\n function unfocus(): void {\n player.stopFocusing();\n router.toJob();\n }\n const comp = Companies[player.companyName];\n let companyRep = 0;\n if (comp == null || !(comp instanceof Company)) {\n throw new Error(`Could not find Company: ${player.companyName}`);\n }\n companyRep = comp.playerReputation;\n\n const position = player.jobs[player.companyName];\n return (\n \n \n \n You are currently working as a {position} at {player.companyName} (Current Company Reputation:{\" \"}\n )
\n
\n You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}\n
\n
\n You have earned:
\n
\n (){\" \"}\n
\n
\n (\n \n ) reputation for this company
\n
\n {numeralWrapper.formatExp(player.workHackExpGained)} (\n {`${numeralWrapper.formatExp(player.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) hacking exp
\n
\n {numeralWrapper.formatExp(player.workStrExpGained)} (\n {`${numeralWrapper.formatExp(player.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) strength exp
\n {numeralWrapper.formatExp(player.workDefExpGained)} (\n {`${numeralWrapper.formatExp(player.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) defense exp
\n {numeralWrapper.formatExp(player.workDexExpGained)} (\n {`${numeralWrapper.formatExp(player.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) dexterity exp
\n {numeralWrapper.formatExp(player.workAgiExpGained)} (\n {`${numeralWrapper.formatExp(player.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) agility exp
\n
\n {numeralWrapper.formatExp(player.workChaExpGained)} (\n {`${numeralWrapper.formatExp(player.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}\n ) charisma exp
\n
\n You will automatically finish after working for 8 hours. You can cancel earlier if you wish, and there will\n be no penalty because this is a part-time job.\n
\n
\n \n \n \n \n
\n );\n }\n\n if (player.crimeType !== \"\") {\n const percent = Math.round((player.timeWorked / player.timeNeededToCompleteWork) * 100);\n let numBars = Math.round(percent / 5);\n if (numBars < 0) {\n numBars = 0;\n }\n if (numBars > 20) {\n numBars = 20;\n }\n // const progressBar = \"[\" + Array(numBars + 1).join(\"|\") + Array(20 - numBars + 1).join(\" \") + \"]\";\n const progressBar = createProgressBarText({ progress: (numBars + 1) / 20, totalTicks: 20 });\n\n return (\n \n \n \n You are attempting to {player.crimeType}.\n
\n\n \n Time remaining: {convertTimeMsToTimeElapsedString(player.timeNeededToCompleteWork - player.timeWorked)}\n \n\n
\n
{progressBar}
\n
\n
\n \n {\n router.toLocation(Locations[LocationName.Slums]);\n player.finishCrime(true);\n }}\n >\n Cancel crime\n \n \n
\n );\n }\n\n if (player.createProgramName !== \"\") {\n return (\n \n \n \n You are currently working on coding {player.createProgramName}.
\n
\n You have been working for {convertTimeMsToTimeElapsedString(player.timeWorked)}\n
\n
\n The program is {((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}\n % complete.
\n If you cancel, your work will be saved and you can come back to complete the program later.\n
\n
\n \n {\n player.finishCreateProgramWork(true);\n router.toTerminal();\n }}\n >\n Cancel work on creating program\n \n \n
\n );\n }\n\n return <>;\n}\n","import React, { useState, useRef } from \"react\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport Typography from \"@mui/material/Typography\";\nimport Slider from \"@mui/material/Slider\";\nimport Grid from \"@mui/material/Grid\";\nimport FormControlLabel from \"@mui/material/FormControlLabel\";\nimport Switch from \"@mui/material/Switch\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport MenuItem from \"@mui/material/MenuItem\";\nimport Button from \"@mui/material/Button\";\n\nimport Box from \"@mui/material/Box\";\nimport List from \"@mui/material/List\";\nimport ListItem from \"@mui/material/ListItem\";\nimport Link from \"@mui/material/Link\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\nimport DownloadIcon from \"@mui/icons-material/Download\";\nimport UploadIcon from \"@mui/icons-material/Upload\";\n\nimport { FileDiagnosticModal } from \"../../Diagnostic/FileDiagnosticModal\";\nimport { dialogBoxCreate } from \"./DialogBox\";\nimport { ConfirmationModal } from \"./ConfirmationModal\";\nimport { ThemeEditorModal } from \"./ThemeEditorModal\";\n\nimport { Settings } from \"../../Settings/Settings\";\nimport { save, deleteGame } from \"../../db\";\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n root: {\n width: 50,\n padding: theme.spacing(2),\n userSelect: \"none\",\n },\n }),\n);\n\ninterface IProps {\n player: IPlayer;\n save: () => void;\n export: () => void;\n forceKill: () => void;\n softReset: () => void;\n}\n\nexport function GameOptionsRoot(props: IProps): React.ReactElement {\n const classes = useStyles();\n const importInput = useRef(null);\n\n const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);\n const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);\n const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);\n const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);\n\n const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);\n\n const [suppressMessages, setSuppressMessages] = useState(Settings.SuppressMessages);\n const [suppressFactionInvites, setSuppressFactionInvites] = useState(Settings.SuppressFactionInvites);\n const [suppressTravelConfirmations, setSuppressTravelConfirmations] = useState(Settings.SuppressTravelConfirmation);\n const [suppressBuyAugmentationConfirmation, setSuppressBuyAugmentationConfirmation] = useState(\n Settings.SuppressBuyAugmentationConfirmation,\n );\n const [suppressHospitalizationPopup, setSuppressHospitalizationPopup] = useState(\n Settings.SuppressHospitalizationPopup,\n );\n\n const [suppressBladeburnerPopup, setSuppressBladeburnerPopup] = useState(Settings.SuppressBladeburnerPopup);\n\n const [disableHotkeys, setDisableHotkeys] = useState(Settings.DisableHotkeys);\n const [disableASCIIArt, setDisableASCIIArt] = useState(Settings.DisableASCIIArt);\n const [disableTextEffects, setDisableTextEffects] = useState(Settings.DisableTextEffects);\n const [enableBashHotkeys, setEnableBashHotkeys] = useState(Settings.EnableBashHotkeys);\n const [enableTimestamps, setEnableTimestamps] = useState(Settings.EnableTimestamps);\n\n const [locale, setLocale] = useState(Settings.Locale);\n const [diagnosticOpen, setDiagnosticOpen] = useState(false);\n const [deleteGameOpen, setDeleteOpen] = useState(false);\n const [themeEditorOpen, setThemeEditorOpen] = useState(false);\n\n function handleExecTimeChange(event: any, newValue: number | number[]): void {\n setExecTime(newValue as number);\n Settings.CodeInstructionRunTime = newValue as number;\n }\n\n function handleLogSizeChange(event: any, newValue: number | number[]): void {\n setLogSize(newValue as number);\n Settings.MaxLogCapacity = newValue as number;\n }\n\n function handlePortSizeChange(event: any, newValue: number | number[]): void {\n setPortSize(newValue as number);\n Settings.MaxPortCapacity = newValue as number;\n }\n\n function handleTerminalSizeChange(event: any, newValue: number | number[]): void {\n setTerminalSize(newValue as number);\n Settings.MaxTerminalCapacity = newValue as number;\n }\n\n function handleAutosaveIntervalChange(event: any, newValue: number | number[]): void {\n setAutosaveInterval(newValue as number);\n Settings.AutosaveInterval = newValue as number;\n }\n\n function handleSuppressMessagesChange(event: React.ChangeEvent): void {\n setSuppressMessages(event.target.checked);\n Settings.SuppressMessages = event.target.checked;\n }\n\n function handleSuppressFactionInvitesChange(event: React.ChangeEvent): void {\n setSuppressFactionInvites(event.target.checked);\n Settings.SuppressFactionInvites = event.target.checked;\n }\n\n function handleSuppressTravelConfirmationsChange(event: React.ChangeEvent): void {\n setSuppressTravelConfirmations(event.target.checked);\n Settings.SuppressTravelConfirmation = event.target.checked;\n }\n\n function handleSuppressBuyAugmentationConfirmationChange(event: React.ChangeEvent): void {\n setSuppressBuyAugmentationConfirmation(event.target.checked);\n Settings.SuppressBuyAugmentationConfirmation = event.target.checked;\n }\n\n function handleSuppressHospitalizationPopupChange(event: React.ChangeEvent): void {\n setSuppressHospitalizationPopup(event.target.checked);\n Settings.SuppressHospitalizationPopup = event.target.checked;\n }\n\n function handleSuppressBladeburnerPopupChange(event: React.ChangeEvent): void {\n setSuppressBladeburnerPopup(event.target.checked);\n Settings.SuppressBladeburnerPopup = event.target.checked;\n }\n\n function handleDisableHotkeysChange(event: React.ChangeEvent): void {\n setDisableHotkeys(event.target.checked);\n Settings.DisableHotkeys = event.target.checked;\n }\n\n function handleDisableASCIIArtChange(event: React.ChangeEvent): void {\n setDisableASCIIArt(event.target.checked);\n Settings.DisableASCIIArt = event.target.checked;\n }\n\n function handleDisableTextEffectsChange(event: React.ChangeEvent): void {\n setDisableTextEffects(event.target.checked);\n Settings.DisableTextEffects = event.target.checked;\n }\n function handleLocaleChange(event: SelectChangeEvent): void {\n setLocale(event.target.value as string);\n Settings.Locale = event.target.value as string;\n }\n\n function handleEnableBashHotkeysChange(event: React.ChangeEvent): void {\n setEnableBashHotkeys(event.target.checked);\n Settings.EnableBashHotkeys = event.target.checked;\n }\n function handleEnableTimestampsChange(event: React.ChangeEvent): void {\n setEnableTimestamps(event.target.checked);\n Settings.EnableTimestamps = event.target.checked;\n }\n\n function startImport(): void {\n if (!window.File || !window.FileReader || !window.FileList || !window.Blob) return;\n const ii = importInput.current;\n if (ii === null) throw new Error(\"import input should not be null\");\n ii.click();\n }\n\n function onImport(event: React.ChangeEvent): void {\n const files = event.target.files;\n if (files === null) return;\n const file = files[0];\n if (!file) {\n dialogBoxCreate(\"Invalid file selected\");\n return;\n }\n\n const reader = new FileReader();\n reader.onload = function (this: FileReader, e: ProgressEvent) {\n const target = e.target;\n if (target === null) {\n console.error(\"error importing file\");\n return;\n }\n const result = target.result;\n if (typeof result !== \"string\" || result === null) {\n console.error(\"FileReader event was not type string\");\n return;\n }\n const contents = result;\n save(contents).then(() => setTimeout(() => location.reload(), 1000));\n };\n reader.readAsText(file);\n }\n\n return (\n
\n \n Options\n \n\n \n \n \n \n \n The minimum number of milliseconds it takes to execute an operation in Netscript. Setting this too\n low can result in poor performance if you have many scripts running.\n \n }\n >\n Netscript exec time (ms)\n \n \n \n \n \n The maximum number of lines a script's logs can hold. Setting this too high can cause the game to\n use a lot of memory if you have many scripts running.\n \n }\n >\n Netscript log size\n \n \n \n \n \n The maximum number of entries that can be written to a port using Netscript's write() function.\n Setting this too high can cause the game to use a lot of memory.\n \n }\n >\n Netscript port size\n \n \n \n \n \n The maximum number of entries that can be written to a the terminal. Setting this too high can cause\n the game to use a lot of memory.\n \n }\n >\n Terminal capacity\n \n \n \n \n The time (in seconds) between each autosave. Set to 0 to disable autosave.\n }\n >\n Autosave interval (s)\n \n \n \n \n }\n label={\n \n If this is set, then any messages you receive will not appear as popups on the screen. They will\n still get sent to your home computer as '.msg' files and can be viewed with the 'cat' Terminal\n command.\n \n }\n >\n Suppress messages\n \n }\n />\n \n \n }\n label={\n \n If this is set, then any faction invites you receive will not appear as popups on the screen.\n Your outstanding faction invites can be viewed in the 'Factions' page.\n \n }\n >\n Suppress faction invites\n \n }\n />\n \n \n \n }\n label={\n \n If this is set, the confirmation message before traveling will not show up. You will\n automatically be deducted the travel cost as soon as you click.\n \n }\n >\n Suppress travel confirmations\n \n }\n />\n \n \n \n }\n label={\n \n If this is set, the confirmation message before buying augmentation will not show up.\n \n }\n >\n Suppress buy augmentation confirmation\n \n }\n />\n \n \n \n }\n label={\n \n If this is set, a popup message will no longer be shown when you are hospitalized after taking\n too much damage.\n \n }\n >\n Suppress hospitalization popup\n \n }\n />\n \n {!!props.player.bladeburner && (\n \n \n }\n label={\n \n If this is set, then having your Bladeburner actions interrupted by being busy with something\n else will not display a popup message.\n \n }\n >\n Suppress bladeburner popup\n \n }\n />\n \n )}\n \n }\n label={\n \n If this is set, then most hotkeys (keyboard shortcuts) in the game are disabled. This includes\n Terminal commands, hotkeys to navigate between different parts of the game, and the \"Save and\n Close (Ctrl + b)\" hotkey in the Text Editor.\n \n }\n >\n Disable hotkeys\n \n }\n />\n \n \n }\n label={\n If this is set all ASCII art will be disabled.}>\n Disable ascii art\n \n }\n />\n \n \n }\n label={\n \n If this is set, text effects will not be displayed. This can help if text is difficult to read\n in certain areas.\n \n }\n >\n Disable text effects\n \n }\n />\n \n\n \n }\n label={\n \n Improved Bash emulation mode. Setting this to 1 enables several new Terminal shortcuts and\n features that more closely resemble a real Bash-style shell. Note that when this mode is\n enabled, the default browser shortcuts are overriden by the new Bash shortcuts.\n \n }\n >\n Enable bash hotkeys\n \n }\n />\n \n \n }\n label={\n \n Terminal commands and log entries will be timestamped. The timestamp will have the format: M/D\n h:m\n \n }\n >\n Enable timestamps\n \n }\n />\n \n\n \n Sets the locale for displaying numbers.}>\n Locale \n \n \n \n \n
\n
\n
\n \n \n \n \n
\n \n \n \n \n \n \n export}>\n \n \n import}>\n \n \n \n \n \n Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the\n game. After using this, save the game and then reload the page. This is different then normal kill in\n that normal kill will tell the script to shut down while force kill just removes the references to it\n (and it should crash on it's own). This will not remove the files on your computer. Just forcefully\n kill all running instance of all scripts.\n \n }\n >\n \n \n \n \n \n Perform a soft reset. Resets everything as if you had just purchased an Augmentation.\n \n }\n >\n \n \n \n \n \n If your save file is extremely big you can use this button to view a map of all the files on every\n server. Be careful there might be spoilers.\n \n }\n >\n \n \n \n \n \n \n Report bug\n \n \n Changelog\n \n \n Documentation\n \n \n Discord\n \n \n Reddit\n \n \n \n
\n setDiagnosticOpen(false)} />\n {\n setDeleteOpen(false);\n deleteGame()\n .then(() => setTimeout(() => location.reload(), 1000))\n .catch((r) => console.error(`Could not delete game: ${r}`));\n }}\n open={deleteGameOpen}\n onClose={() => setDeleteOpen(false)}\n confirmationText={\"Really delete your game? (It's permanent!)\"}\n />\n setThemeEditorOpen(false)} />\n
\n );\n}\n","import React from \"react\";\nimport { AllServers } from \"../Server/AllServers\";\nimport { Modal } from \"../ui/React/Modal\";\nimport { numeralWrapper } from \"../ui/numeralFormat\";\n\nimport Table from \"@mui/material/Table\";\nimport TableBody from \"@mui/material/TableBody\";\nimport TableCell from \"@mui/material/TableCell\";\nimport TableContainer from \"@mui/material/TableContainer\";\nimport TableHead from \"@mui/material/TableHead\";\nimport TableRow from \"@mui/material/TableRow\";\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\nimport Accordion from \"@mui/material/Accordion\";\nimport AccordionSummary from \"@mui/material/AccordionSummary\";\nimport AccordionDetails from \"@mui/material/AccordionDetails\";\nimport ExpandMoreIcon from \"@mui/icons-material/ExpandMore\";\n\ninterface IServerProps {\n ip: string;\n}\n\nfunction ServerAccordion(props: IServerProps): React.ReactElement {\n const server = AllServers[props.ip];\n let totalSize = 0;\n for (const f of server.scripts) {\n totalSize += f.code.length;\n }\n\n for (const f of server.textFiles) {\n totalSize += f.text.length;\n }\n\n if (totalSize === 0) {\n return <>;\n }\n\n interface File {\n name: string;\n size: number;\n }\n\n const files: File[] = [];\n\n for (const f of server.scripts) {\n files.push({ name: f.filename, size: f.code.length });\n }\n\n for (const f of server.textFiles) {\n files.push({ name: f.fn, size: f.text.length });\n }\n\n files.sort((a: File, b: File): number => b.size - a.size);\n\n return (\n \n }>\n \n {server.hostname} ({numeralWrapper.formatBigNumber(totalSize)}b)\n \n \n \n \n \n \n \n \n Filename\n \n \n Size\n \n \n \n \n {files.map((file: File) => (\n \n \n {file.name}\n \n \n {numeralWrapper.formatBigNumber(file.size)}b\n \n \n ))}\n \n
\n
\n
    \n
    \n
    \n );\n}\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\nexport function FileDiagnosticModal(props: IProps): React.ReactElement {\n const ips: string[] = [];\n for (const ip of Object.keys(AllServers)) {\n ips.push(ip);\n }\n\n return (\n \n <>\n \n Welcome to the file diagnostic! If your save file is really big it's likely because you have too many\n text/scripts. This tool can help you narrow down where they are.\n \n {ips.map((ip: string) => (\n \n ))}\n \n \n );\n}\n","import React from \"react\";\nimport { Modal } from \"./Modal\";\n\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n onConfirm: () => void;\n confirmationText: string;\n}\n\nexport function ConfirmationModal(props: IProps): React.ReactElement {\n return (\n \n <>\n {props.confirmationText}\n {\n props.onConfirm();\n }}\n >\n Confirm\n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { Modal } from \"./Modal\";\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport IconButton from \"@mui/material/IconButton\";\nimport ReplyIcon from \"@mui/icons-material/Reply\";\nimport { Color, ColorPicker } from \"material-ui-color\";\nimport { ThemeEvents } from \"./Theme\";\nimport { Settings, defaultSettings } from \"../../Settings/Settings\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\ninterface IColorEditorProps {\n name: string;\n color: string | undefined;\n onColorChange: (name: string, value: string) => void;\n defaultColor: string;\n}\n\nfunction ColorEditor({ name, onColorChange, color, defaultColor }: IColorEditorProps): React.ReactElement {\n if (color === undefined) {\n console.error(`color ${name} was undefined, reverting to default`);\n color = defaultColor;\n }\n\n return (\n <>\n \n onColorChange(name, \"#\" + newColor.hex)}\n />\n \n ),\n endAdornment: (\n <>\n onColorChange(name, defaultColor)}>\n \n \n \n ),\n }}\n />\n \n );\n}\n\nexport function ThemeEditorModal(props: IProps): React.ReactElement {\n const [customTheme, setCustomTheme] = useState<{ [key: string]: string | undefined }>({\n ...Settings.theme,\n });\n\n function onThemeChange(event: React.ChangeEvent): void {\n try {\n const importedTheme = JSON.parse(event.target.value);\n if (typeof importedTheme !== \"object\") return;\n setCustomTheme(importedTheme);\n for (const key of Object.keys(importedTheme)) {\n Settings.theme[key] = importedTheme[key];\n }\n ThemeEvents.emit();\n } catch (err) {\n // ignore\n }\n }\n\n function onColorChange(name: string, value: string): void {\n setCustomTheme((old: any) => {\n old[name] = value;\n return old;\n });\n\n Settings.theme[name] = value;\n ThemeEvents.emit();\n }\n\n return (\n \n \n \n \n \n \n primary\n secondary\n warning\n info\n error\n
    \n \n \n \n\n
    \n \n \n \n\n
    \n \n \n \n\n
    \n \n \n \n\n
    \n \n \n \n\n
    \n \n \n \n \n\n
    \n \n \n \n \n \n \n \n \n
    \n
    \n \n
    \n );\n}\n","import React, { useState, useEffect } from \"react\";\n\nimport { SleeveElem } from \"./SleeveElem\";\nimport { FAQModal } from \"./FAQModal\";\nimport { use } from \"../../../ui/Context\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Link from \"@mui/material/Link\";\n\nexport function SleeveRoot(): React.ReactElement {\n const player = use.Player();\n const [FAQOpen, setFAQOpen] = useState(false);\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n return (\n <>\n Sleeves\n \n Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciousness has been copied. In\n other words, these Synthoids contain a perfect duplicate of your mind.\n
    \n
    \n Sleeves can be used to perform different tasks synchronously.\n
    \n
    \n
    \n\n \n \n Documentation\n \n {player.sleeves.map((sleeve, i) => (\n \n ))}\n setFAQOpen(false)} />\n \n );\n}\n","import React, { useState } from \"react\";\n\nimport { Sleeve } from \"../Sleeve\";\nimport { SleeveTaskType } from \"../SleeveTaskTypesEnum\";\n\nimport { CONSTANTS } from \"../../../Constants\";\n\nimport { Crimes } from \"../../../Crime/Crimes\";\n\nimport { numeralWrapper } from \"../../../ui/numeralFormat\";\n\nimport { createProgressBarText } from \"../../../utils/helpers/createProgressBarText\";\n\nimport { SleeveAugmentationsModal } from \"./SleeveAugmentationsModal\";\nimport { TravelModal } from \"./TravelModal\";\nimport { Money } from \"../../../ui/React/Money\";\nimport { MoneyRate } from \"../../../ui/React/MoneyRate\";\nimport { use } from \"../../../ui/Context\";\nimport { ReputationRate } from \"../../../ui/React/ReputationRate\";\nimport { StatsElement } from \"../ui/StatsElement\";\nimport { MoreStatsModal } from \"./MoreStatsModal\";\nimport { MoreEarningsModal } from \"../ui/MoreEarningsModal\";\nimport { TaskSelector } from \"../ui/TaskSelector\";\nimport { FactionWorkType } from \"../../../Faction/FactionWorkTypeEnum\";\nimport { StatsTable } from \"../../../ui/React/StatsTable\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\nimport Grid from \"@mui/material/Grid\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\ninterface IProps {\n sleeve: Sleeve;\n rerender: () => void;\n}\n\nexport function SleeveElem(props: IProps): React.ReactElement {\n const player = use.Player();\n const [statsOpen, setStatsOpen] = useState(false);\n const [earningsOpen, setEarningsOpen] = useState(false);\n const [travelOpen, setTravelOpen] = useState(false);\n const [augmentationsOpen, setAugmentationsOpen] = useState(false);\n\n const [abc, setABC] = useState([\"------\", \"------\", \"------\"]);\n\n function setTask(): void {\n props.sleeve.resetTaskStatus(); // sets to idle\n switch (abc[0]) {\n case \"------\":\n break;\n case \"Work for Company\":\n props.sleeve.workForCompany(player, abc[1]);\n break;\n case \"Work for Faction\":\n props.sleeve.workForFaction(player, abc[1], abc[2]);\n break;\n case \"Commit Crime\":\n props.sleeve.commitCrime(player, abc[1]);\n break;\n case \"Take University Course\":\n props.sleeve.takeUniversityCourse(player, abc[2], abc[1]);\n break;\n case \"Workout at Gym\":\n props.sleeve.workoutAtGym(player, abc[2], abc[1]);\n break;\n case \"Shock Recovery\":\n props.sleeve.shockRecovery(player);\n break;\n case \"Synchronize\":\n props.sleeve.synchronize(player);\n break;\n default:\n console.error(`Invalid/Unrecognized taskValue in setSleeveTask(): ${abc[0]}`);\n }\n props.rerender();\n }\n\n let desc = <>;\n switch (props.sleeve.currentTask) {\n case SleeveTaskType.Idle:\n desc = <>This sleeve is currently idle;\n break;\n case SleeveTaskType.Company:\n desc = <>This sleeve is currently working your job at {props.sleeve.currentTaskLocation}.;\n break;\n case SleeveTaskType.Faction: {\n let doing = \"nothing\";\n switch (props.sleeve.factionWorkType) {\n case FactionWorkType.Field:\n doing = \"Field work\";\n break;\n case FactionWorkType.Hacking:\n doing = \"Hacking contracts\";\n break;\n case FactionWorkType.Security:\n doing = \"Security work\";\n break;\n }\n desc = (\n <>\n This sleeve is currently doing {doing} for {props.sleeve.currentTaskLocation}.\n \n );\n break;\n }\n case SleeveTaskType.Crime:\n desc = (\n <>\n This sleeve is currently attempting to {Crimes[props.sleeve.crimeType].type} (Success Rate:{\" \"}\n {numeralWrapper.formatPercentage(Crimes[props.sleeve.crimeType].successRate(props.sleeve))}).\n \n );\n break;\n case SleeveTaskType.Class:\n desc = <>This sleeve is currently studying/taking a course at {props.sleeve.currentTaskLocation}.;\n break;\n case SleeveTaskType.Gym:\n desc = <>This sleeve is currently working out at {props.sleeve.currentTaskLocation}.;\n break;\n case SleeveTaskType.Recovery:\n desc = (\n <>\n This sleeve is currently set to focus on shock recovery. This causes the Sleeve's shock to decrease at a\n faster rate.\n \n );\n break;\n case SleeveTaskType.Synchro:\n desc = (\n <>\n This sleeve is currently set to synchronize with the original consciousness. This causes the Sleeve's\n synchronization to increase.\n \n );\n break;\n default:\n console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`);\n }\n\n let data: any[][] = [];\n if (props.sleeve.currentTask === SleeveTaskType.Crime) {\n data = [\n [`Money`, , `(on success)`],\n [`Hacking Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.hack), `(2x on success)`],\n [`Strength Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.str), `(2x on success)`],\n [`Defense Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.def), `(2x on success)`],\n [`Dexterity Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.dex), `(2x on success)`],\n [`Agility Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.agi), `(2x on success)`],\n [`Charisma Exp`, numeralWrapper.formatExp(props.sleeve.gainRatesForTask.cha), `(2x on success)`],\n ];\n } else {\n data = [\n [`Money:`, ],\n [`Hacking Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.hack)} / s`],\n [`Strength Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.str)} / s`],\n [`Defense Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.def)} / s`],\n [`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.dex)} / s`],\n [`Agility Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.agi)} / s`],\n [`Charisma Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.cha)} / s`],\n ];\n if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) {\n const repGain: number = props.sleeve.getRepGain(player);\n data.push([`Reputation:`, ]);\n }\n }\n\n return (\n <>\n \n \n \n \n Insufficient funds : \"\"}>\n \n \n \n \n Unlocked when sleeve has fully recovered : \"\"}\n >\n \n \n \n \n \n \n \n {desc}\n \n {props.sleeve.currentTask === SleeveTaskType.Crime &&\n createProgressBarText({\n progress: props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime,\n totalTicks: 25,\n })}\n \n \n \n \n \n \n \n \n setStatsOpen(false)} sleeve={props.sleeve} />\n setEarningsOpen(false)} sleeve={props.sleeve} />\n setTravelOpen(false)}\n sleeve={props.sleeve}\n rerender={props.rerender}\n />\n setAugmentationsOpen(false)}\n sleeve={props.sleeve}\n />\n \n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport { Sleeve } from \"../Sleeve\";\nimport { findSleevePurchasableAugs } from \"../SleeveHelpers\";\nimport { Augmentations } from \"../../../Augmentation/Augmentations\";\nimport { Augmentation } from \"../../../Augmentation/Augmentation\";\nimport { Money } from \"../../../ui/React/Money\";\nimport { Modal } from \"../../../ui/React/Modal\";\nimport { use } from \"../../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Paper from \"@mui/material/Paper\";\nimport Box from \"@mui/material/Box\";\nimport Button from \"@mui/material/Button\";\nimport TableBody from \"@mui/material/TableBody\";\nimport Table from \"@mui/material/Table\";\nimport { TableCell } from \"../../../ui/React/Table\";\nimport TableRow from \"@mui/material/TableRow\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n sleeve: Sleeve;\n}\n\nexport function SleeveAugmentationsModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 150);\n return () => clearInterval(id);\n }, []);\n\n // Array of all owned Augmentations. Names only\n const ownedAugNames = props.sleeve.augmentations.map((e) => e.name);\n\n // You can only purchase Augmentations that are actually available from\n // your factions. I.e. you must be in a faction that has the Augmentation\n // and you must also have enough rep in that faction in order to purchase it.\n const availableAugs = findSleevePurchasableAugs(props.sleeve, player);\n\n function purchaseAugmentation(aug: Augmentation): void {\n props.sleeve.tryBuyAugmentation(player, aug);\n rerender();\n }\n\n return (\n \n <>\n \n You can purchase Augmentations for your Duplicate Sleeves. These Augmentations have the same effect as they\n would for you. You can only purchase Augmentations that you have unlocked through Factions.\n
    \n
    \n When purchasing an Augmentation for a Duplicate Sleeve, they are immediately installed. This means that the\n Duplicate Sleeve will immediately lose all of its stat experience.\n
    \n \n \n {availableAugs.map((aug) => {\n return (\n \n \n \n \n \n \n \n {aug.name}\n \n \n \n \n \n \n \n );\n })}\n \n
    \n\n {ownedAugNames.length > 0 && (\n <>\n Owned Augmentations:\n {ownedAugNames.map((augName) => {\n const aug = Augmentations[augName];\n let tooltip = <>;\n if (typeof aug.info === \"string\") {\n tooltip = (\n <>\n {aug.info}\n
    \n
    \n {aug.stats}\n \n );\n } else {\n tooltip = (\n <>\n {aug.info}\n
    \n
    \n {aug.stats}\n \n );\n }\n\n return (\n {tooltip}}>\n \n {augName}\n \n \n );\n })}\n \n )}\n \n
    \n );\n}\n","import React from \"react\";\nimport { Sleeve } from \"../Sleeve\";\nimport { CONSTANTS } from \"../../../Constants\";\nimport { Money } from \"../../../ui/React/Money\";\nimport { WorldMap } from \"../../../ui/React/WorldMap\";\nimport { CityName } from \"../../../Locations/data/CityNames\";\nimport { Settings } from \"../../../Settings/Settings\";\nimport { dialogBoxCreate } from \"../../../ui/React/DialogBox\";\nimport { use } from \"../../../ui/Context\";\nimport { Modal } from \"../../../ui/React/Modal\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n sleeve: Sleeve;\n rerender: () => void;\n}\n\nexport function TravelModal(props: IProps): React.ReactElement {\n const player = use.Player();\n function travel(city: string): void {\n if (!player.canAfford(CONSTANTS.TravelCost)) {\n dialogBoxCreate(\"You cannot afford to have this sleeve travel to another city\");\n }\n props.sleeve.city = city as CityName;\n player.loseMoney(CONSTANTS.TravelCost);\n props.sleeve.resetTaskStatus();\n props.rerender();\n props.onClose();\n }\n\n return (\n \n <>\n \n Have this sleeve travel to a different city. This affects the gyms and universities at which this sleeve can\n study. Traveling to a different city costs . It will\n also set your current sleeve task to idle.\n \n {Settings.DisableASCIIArt ? (\n Object.values(CityName).map((city: CityName) => (\n \n ))\n ) : (\n travel(city)} />\n )}\n \n \n );\n}\n","import { Sleeve } from \"../Sleeve\";\nimport { numeralWrapper } from \"../../../ui/numeralFormat\";\nimport React from \"react\";\n\nimport { StatsTable } from \"../../../ui/React/StatsTable\";\n\ninterface IProps {\n sleeve: Sleeve;\n}\n\nexport function StatsElement(props: IProps): React.ReactElement {\n const rows = [\n [\n \"HP: \",\n <>\n {numeralWrapper.formatHp(props.sleeve.hp)} / {numeralWrapper.formatHp(props.sleeve.max_hp)}\n ,\n ],\n [\"City: \", <>{props.sleeve.city}],\n [\"Hacking: \", <>{numeralWrapper.formatSkill(props.sleeve.hacking_skill)}],\n [\"Strength: \", <>{numeralWrapper.formatSkill(props.sleeve.strength)}],\n [\"Defense: \", <>{numeralWrapper.formatSkill(props.sleeve.defense)}],\n [\"Dexterity: \", <>{numeralWrapper.formatSkill(props.sleeve.dexterity)}],\n [\"Agility: \", <>{numeralWrapper.formatSkill(props.sleeve.agility)}],\n [\"Charisma: \", <>{numeralWrapper.formatSkill(props.sleeve.charisma)}],\n [\"Shock: \", <>{numeralWrapper.formatSleeveShock(100 - props.sleeve.shock)}],\n [\"Sync: \", <>{numeralWrapper.formatSleeveSynchro(props.sleeve.sync)}],\n [\"Memory: \", <>{numeralWrapper.formatSleeveMemory(props.sleeve.memory)}],\n ];\n return ;\n}\n","import { Sleeve } from \"../Sleeve\";\nimport { numeralWrapper } from \"../../../ui/numeralFormat\";\nimport { StatsTable } from \"../../../ui/React/StatsTable\";\nimport { Modal } from \"../../../ui/React/Modal\";\nimport React from \"react\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n sleeve: Sleeve;\n}\n\nexport function MoreStatsModal(props: IProps): React.ReactElement {\n return (\n \n Hacking: ,\n props.sleeve.hacking_skill,\n <> ({numeralWrapper.formatExp(props.sleeve.hacking_exp)} exp),\n ],\n [\n <>Strength: ,\n props.sleeve.strength,\n <> ({numeralWrapper.formatExp(props.sleeve.strength_exp)} exp),\n ],\n [\n <>Defense: ,\n props.sleeve.defense,\n <> ({numeralWrapper.formatExp(props.sleeve.defense_exp)} exp),\n ],\n [\n <>Dexterity: ,\n props.sleeve.dexterity,\n <> ({numeralWrapper.formatExp(props.sleeve.dexterity_exp)} exp),\n ],\n [\n <>Agility: ,\n props.sleeve.agility,\n <> ({numeralWrapper.formatExp(props.sleeve.agility_exp)} exp),\n ],\n [\n <>Charisma: ,\n props.sleeve.charisma,\n <> ({numeralWrapper.formatExp(props.sleeve.charisma_exp)} exp),\n ],\n ]}\n title=\"Stats:\"\n />\n
    \n Hacking Level multiplier: , numeralWrapper.formatPercentage(props.sleeve.hacking_mult)],\n [<>Hacking Experience multiplier: , numeralWrapper.formatPercentage(props.sleeve.hacking_exp_mult)],\n [<>Strength Level multiplier: , numeralWrapper.formatPercentage(props.sleeve.strength_mult)],\n [<>Strength Experience multiplier: , numeralWrapper.formatPercentage(props.sleeve.strength_exp_mult)],\n [<>Defense Level multiplier: , numeralWrapper.formatPercentage(props.sleeve.defense_mult)],\n [<>Defense Experience multiplier: , numeralWrapper.formatPercentage(props.sleeve.defense_exp_mult)],\n [<>Dexterity Level multiplier: , numeralWrapper.formatPercentage(props.sleeve.dexterity_mult)],\n [\n <>Dexterity Experience multiplier: ,\n numeralWrapper.formatPercentage(props.sleeve.dexterity_exp_mult),\n ],\n [<>Agility Level multiplier: , numeralWrapper.formatPercentage(props.sleeve.agility_mult)],\n [<>Agility Experience multiplier: , numeralWrapper.formatPercentage(props.sleeve.agility_exp_mult)],\n [<>Charisma Level multiplier: , numeralWrapper.formatPercentage(props.sleeve.charisma_mult)],\n [<>Charisma Experience multiplier: , numeralWrapper.formatPercentage(props.sleeve.charisma_exp_mult)],\n [\n <>Faction Reputation Gain multiplier: ,\n numeralWrapper.formatPercentage(props.sleeve.faction_rep_mult),\n ],\n [\n <>Company Reputation Gain multiplier: ,\n numeralWrapper.formatPercentage(props.sleeve.company_rep_mult),\n ],\n [<>Salary multiplier: , numeralWrapper.formatPercentage(props.sleeve.work_money_mult)],\n [<>Crime Money multiplier: , numeralWrapper.formatPercentage(props.sleeve.crime_money_mult)],\n [<>Crime Success multiplier: , numeralWrapper.formatPercentage(props.sleeve.crime_success_mult)],\n ]}\n title=\"Multipliers:\"\n />\n
    \n );\n}\n","import { Sleeve } from \"../Sleeve\";\nimport { numeralWrapper } from \"../../../ui/numeralFormat\";\nimport { Money } from \"../../../ui/React/Money\";\nimport * as React from \"react\";\nimport { StatsTable } from \"../../../ui/React/StatsTable\";\nimport { Modal } from \"../../../ui/React/Modal\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n sleeve: Sleeve;\n}\n\nexport function MoreEarningsModal(props: IProps): React.ReactElement {\n return (\n \n ],\n [\"Hacking Exp \", numeralWrapper.formatExp(props.sleeve.earningsForTask.hack)],\n [\"Strength Exp \", numeralWrapper.formatExp(props.sleeve.earningsForTask.str)],\n [\"Defense Exp \", numeralWrapper.formatExp(props.sleeve.earningsForTask.def)],\n [\"Dexterity Exp \", numeralWrapper.formatExp(props.sleeve.earningsForTask.dex)],\n [\"Agility Exp \", numeralWrapper.formatExp(props.sleeve.earningsForTask.agi)],\n [\"Charisma Exp \", numeralWrapper.formatExp(props.sleeve.earningsForTask.cha)],\n ]}\n title=\"Earnings for Current Task:\"\n />\n
    \n ],\n [\"Hacking Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.hack)],\n [\"Strength Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.str)],\n [\"Defense Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.def)],\n [\"Dexterity Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.dex)],\n [\"Agility Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.agi)],\n [\"Charisma Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.cha)],\n ]}\n title=\"Total Earnings for Host Consciousness:\"\n />\n
    \n ],\n [\"Hacking Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.hack)],\n [\"Strength Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.str)],\n [\"Defense Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.def)],\n [\"Dexterity Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.dex)],\n [\"Agility Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.agi)],\n [\"Charisma Exp: \", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.cha)],\n ]}\n title=\"Total Earnings for Other Sleeves:\"\n />\n
    \n
    \n );\n}\n","import React, { useState } from \"react\";\nimport { Sleeve } from \"../Sleeve\";\nimport { IPlayer } from \"../../IPlayer\";\nimport { SleeveTaskType } from \"../SleeveTaskTypesEnum\";\nimport { Crimes } from \"../../../Crime/Crimes\";\nimport { LocationName } from \"../../../Locations/data/LocationNames\";\nimport { CityName } from \"../../../Locations/data/CityNames\";\nimport { Factions } from \"../../../Faction/Factions\";\nimport { FactionWorkType } from \"../../../Faction/FactionWorkTypeEnum\";\nimport Select, { SelectChangeEvent } from \"@mui/material/Select\";\nimport MenuItem from \"@mui/material/MenuItem\";\n\nconst universitySelectorOptions: string[] = [\n \"Study Computer Science\",\n \"Data Structures\",\n \"Networks\",\n \"Algorithms\",\n \"Management\",\n \"Leadership\",\n];\n\nconst gymSelectorOptions: string[] = [\"Train Strength\", \"Train Defense\", \"Train Dexterity\", \"Train Agility\"];\n\ninterface IProps {\n sleeve: Sleeve;\n player: IPlayer;\n setABC: (abc: string[]) => void;\n}\n\ninterface ITaskDetails {\n first: string[];\n second: (s1: string) => string[];\n}\n\nfunction possibleJobs(player: IPlayer, sleeve: Sleeve): string[] {\n // Array of all companies that other sleeves are working at\n const forbiddenCompanies = [];\n for (const otherSleeve of player.sleeves) {\n if (sleeve === otherSleeve) {\n continue;\n }\n if (otherSleeve.currentTask === SleeveTaskType.Company) {\n forbiddenCompanies.push(otherSleeve.currentTaskLocation);\n }\n }\n const allJobs: string[] = Object.keys(player.jobs);\n for (let i = 0; i < allJobs.length; ++i) {\n if (!forbiddenCompanies.includes(allJobs[i])) {\n allJobs[i];\n }\n }\n\n return allJobs;\n}\n\nfunction possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {\n // Array of all factions that other sleeves are working for\n const forbiddenFactions = [\"Bladeburners\"];\n if (player.gang) {\n forbiddenFactions.push(player.gang.facName);\n }\n for (const otherSleeve of player.sleeves) {\n if (sleeve === otherSleeve) {\n continue;\n }\n if (otherSleeve.currentTask === SleeveTaskType.Faction) {\n forbiddenFactions.push(otherSleeve.currentTaskLocation);\n }\n }\n\n const factions = [];\n for (const fac of player.factions) {\n if (!forbiddenFactions.includes(fac)) {\n factions.push(fac);\n }\n }\n\n return factions;\n}\n\nconst tasks: {\n [key: string]: undefined | ((player: IPlayer, sleeve: Sleeve) => ITaskDetails);\n [\"------\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Work for Company\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Work for Faction\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Commit Crime\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Take University Course\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Workout at Gym\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Shock Recovery\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n [\"Synchronize\"]: (player: IPlayer, sleeve: Sleeve) => ITaskDetails;\n} = {\n \"------\": (): ITaskDetails => {\n return { first: [\"------\"], second: () => [\"------\"] };\n },\n \"Work for Company\": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {\n let jobs = possibleJobs(player, sleeve);\n\n if (jobs.length === 0) jobs = [\"------\"];\n return { first: jobs, second: () => [\"------\"] };\n },\n \"Work for Faction\": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {\n let factions = possibleFactions(player, sleeve);\n if (factions.length === 0) factions = [\"------\"];\n\n return {\n first: factions,\n second: (s1: string) => {\n const faction = Factions[s1];\n const facInfo = faction.getInfo();\n const options: string[] = [];\n if (facInfo.offerHackingWork) {\n options.push(\"Hacking Contracts\");\n }\n if (facInfo.offerFieldWork) {\n options.push(\"Field Work\");\n }\n if (facInfo.offerSecurityWork) {\n options.push(\"Security Work\");\n }\n return options;\n },\n };\n },\n \"Commit Crime\": (): ITaskDetails => {\n return { first: Object.keys(Crimes), second: () => [\"------\"] };\n },\n \"Take University Course\": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {\n let universities: string[] = [];\n switch (sleeve.city) {\n case CityName.Aevum:\n universities = [LocationName.AevumSummitUniversity];\n break;\n case CityName.Sector12:\n universities = [LocationName.Sector12RothmanUniversity];\n break;\n case CityName.Volhaven:\n universities = [LocationName.VolhavenZBInstituteOfTechnology];\n break;\n default:\n universities = [\"No university available in city!\"];\n break;\n }\n\n return { first: universitySelectorOptions, second: () => universities };\n },\n \"Workout at Gym\": (player: IPlayer, sleeve: Sleeve): ITaskDetails => {\n let gyms: string[] = [];\n switch (sleeve.city) {\n case CityName.Aevum:\n gyms = [LocationName.AevumCrushFitnessGym, LocationName.AevumSnapFitnessGym];\n break;\n case CityName.Sector12:\n gyms = [LocationName.Sector12IronGym, LocationName.Sector12PowerhouseGym];\n break;\n case CityName.Volhaven:\n gyms = [LocationName.VolhavenMilleniumFitnessGym];\n break;\n default:\n gyms = [\"No gym available in city!\"];\n break;\n }\n\n return { first: gymSelectorOptions, second: () => gyms };\n },\n \"Shock Recovery\": (): ITaskDetails => {\n return { first: [\"------\"], second: () => [\"------\"] };\n },\n Synchronize: (): ITaskDetails => {\n return { first: [\"------\"], second: () => [\"------\"] };\n },\n};\n\nconst canDo: {\n [key: string]: undefined | ((player: IPlayer, sleeve: Sleeve) => boolean);\n [\"------\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Work for Company\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Work for Faction\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Commit Crime\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Take University Course\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Workout at Gym\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Shock Recovery\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n [\"Synchronize\"]: (player: IPlayer, sleeve: Sleeve) => boolean;\n} = {\n \"------\": () => true,\n \"Work for Company\": (player: IPlayer, sleeve: Sleeve) => possibleJobs(player, sleeve).length > 0,\n \"Work for Faction\": (player: IPlayer, sleeve: Sleeve) => possibleFactions(player, sleeve).length > 0,\n \"Commit Crime\": () => true,\n \"Take University Course\": (player: IPlayer, sleeve: Sleeve) =>\n [CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),\n \"Workout at Gym\": (player: IPlayer, sleeve: Sleeve) =>\n [CityName.Aevum, CityName.Sector12, CityName.Volhaven].includes(sleeve.city),\n \"Shock Recovery\": (player: IPlayer, sleeve: Sleeve) => sleeve.shock < 100,\n Synchronize: (player: IPlayer, sleeve: Sleeve) => sleeve.sync < 100,\n};\n\nfunction getABC(sleeve: Sleeve): [string, string, string] {\n switch (sleeve.currentTask) {\n case SleeveTaskType.Idle:\n return [\"------\", \"------\", \"------\"];\n case SleeveTaskType.Company:\n return [\"Work for Company\", sleeve.currentTaskLocation, \"------\"];\n case SleeveTaskType.Faction: {\n let workType = \"\";\n switch (sleeve.factionWorkType) {\n case FactionWorkType.Hacking:\n workType = \"Hacking Contracts\";\n break;\n case FactionWorkType.Field:\n workType = \"Field Work\";\n break;\n case FactionWorkType.Security:\n workType = \"Security Work\";\n break;\n }\n return [\"Work for Faction\", sleeve.currentTaskLocation, workType];\n }\n case SleeveTaskType.Crime:\n return [\"Commit Crime\", sleeve.crimeType, \"------\"];\n case SleeveTaskType.Class:\n return [\"Take University Course\", sleeve.className, sleeve.currentTaskLocation];\n case SleeveTaskType.Gym:\n return [\"Workout at Gym\", sleeve.gymStatType, sleeve.currentTaskLocation];\n case SleeveTaskType.Recovery:\n return [\"Shock Recovery\", \"------\", \"------\"];\n case SleeveTaskType.Synchro:\n return [\"Synchronize\", \"------\", \"------\"];\n }\n}\n\nexport function TaskSelector(props: IProps): React.ReactElement {\n const abc = getABC(props.sleeve);\n const [s0, setS0] = useState(abc[0]);\n const [s1, setS1] = useState(abc[1]);\n const [s2, setS2] = useState(abc[2]);\n\n const validActions = Object.keys(canDo).filter((k) =>\n (canDo[k] as (player: IPlayer, sleeve: Sleeve) => boolean)(props.player, props.sleeve),\n );\n\n const detailsF = tasks[s0];\n if (detailsF === undefined) throw new Error(`No function for task '${s0}'`);\n const details = detailsF(props.player, props.sleeve);\n const details2 = details.second(s1);\n\n if (details.first.length > 0 && !details.first.includes(s1)) {\n setS1(details.first[0]);\n props.setABC([s0, details.first[0], s2]);\n }\n if (details2.length > 0 && !details2.includes(s2)) {\n setS2(details2[0]);\n props.setABC([s0, s1, details2[0]]);\n }\n\n function onS0Change(event: SelectChangeEvent): void {\n const n = event.target.value;\n const detailsF = tasks[n];\n if (detailsF === undefined) throw new Error(`No function for task '${s0}'`);\n const details = detailsF(props.player, props.sleeve);\n const details2 = details.second(details.first[0]);\n setS2(details2[0]);\n setS1(details.first[0]);\n setS0(n);\n props.setABC([n, details.first[0], details2[0]]);\n }\n\n function onS1Change(event: SelectChangeEvent): void {\n setS1(event.target.value);\n props.setABC([s0, event.target.value, s2]);\n }\n\n function onS2Change(event: SelectChangeEvent): void {\n setS2(event.target.value);\n props.setABC([s0, s1, event.target.value]);\n }\n\n return (\n <>\n \n {!(details.first.length === 1 && details.first[0] === \"------\") && (\n <>\n
    \n \n \n )}\n {!(details2.length === 1 && details2[0] === \"------\") && (\n <>\n
    \n \n \n )}\n \n );\n}\n","import React from \"react\";\n\nimport { Modal } from \"../../../ui/React/Modal\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\nexport function FAQModal({ open, onClose }: IProps): React.ReactElement {\n return (\n \n <>\n How do Duplicate Sleeves work?\n
    \n \n Duplicate Sleeves are essentially clones. You can use them to perform any work type action, such as working\n for a company/faction or committing a crime. Having sleeves perform these tasks earns you money, experience,\n and reputation.\n \n
    \n
    \n \n Sleeves are their own individuals, which means they each have their own experience and stats.\n \n
    \n
    \n \n When a sleeve earns experience, it earns experience for itself, the player's original 'consciousness', as well\n as all of the player's other sleeves.\n \n
    \n
    \n What is Synchronization (Sync)?\n
    \n \n Synchronization is a measure of how aligned your consciousness is with that of your Duplicate Sleeves. It is a\n numerical value between 1 and 100, and it affects how much experience is earned when the sleeve is performing\n a task.\n \n
    \n
    \n \n Let N be the sleeve's synchronization. When the sleeve earns experience by performing a task, both the sleeve\n and the player's original host consciousness earn N% of the amount of experience normally earned by the task.\n All of the player's other sleeves earn ((N/100)^2 * 100)% of the experience.\n \n
    \n
    \n Synchronization can be increased by assigning sleeves to the 'Synchronize' task.\n
    \n
    \n What is Shock?\n
    \n \n Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new body. It is a\n numerical value between 0 and 99, where 99 indicates full shock and 0 indicates no shock. Shock affects the\n amount of experience earned by the sleeve.\n \n
    \n
    \n \n Sleeve shock slowly decreases over time. You can further increase the rate at which it decreases by assigning\n sleeves to the 'Shock Recovery' task.\n \n
    \n
    \n Why can't I work for this company or faction?\n
    \n \n Only one of your sleeves can work for a given company/faction a time. To clarify further, if you have two\n sleeves they can work for two different companies, but they cannot both work for the same company.\n \n
    \n
    \n Why did my Sleeve stop working?\n
    \n \n Sleeves are subject to the same time restrictions as you. This means that they automatically stop working at a\n company after 8 hours, and stop working for a faction after 20 hours.\n \n
    \n
    \n How do I buy Augmentations for my Sleeves?\n
    \n Your Sleeve needs to have a Shock of 0 in order for you to buy Augmentations for it.\n
    \n
    \n Why can't I buy the X Augmentation for my sleeve?\n
    \n \n Certain Augmentations, like Bladeburner-specific ones and NeuroFlux Governor, are not available for sleeves.\n \n
    \n
    \n Do sleeves get reset when installing Augmentations or switching BitNodes?\n
    \n Sleeves are reset when switching BitNodes, but not when installing Augmentations.\n
    \n
    \n What is Memory?\n
    \n \n Sleeve memory dictates what a sleeve's synchronization will be when its reset by switching BitNodes. For\n example, if a sleeve has a memory of 25, then when you switch BitNodes its synchronization will initially be\n set to 25, rather than 1.\n \n
    \n
    \n \n Memory can only be increased by purchasing upgrades from The Covenant. It is a persistent stat, meaning it\n never gets resets back to 1. The maximum possible value for a sleeve's memory is 100.\n \n \n
    \n );\n}\n","/**\n * Root React Component for the Hacknet Node UI\n */\nimport React, { useState, useEffect } from \"react\";\n\nimport { GeneralInfo } from \"./GeneralInfo\";\nimport { HacknetNodeElem } from \"./HacknetNodeElem\";\nimport { HacknetServerElem } from \"./HacknetServerElem\";\nimport { HacknetNode } from \"../HacknetNode\";\nimport { HashUpgradeModal } from \"./HashUpgradeModal\";\nimport { MultiplierButtons } from \"./MultiplierButtons\";\nimport { PlayerInfo } from \"./PlayerInfo\";\nimport { PurchaseButton } from \"./PurchaseButton\";\nimport { PurchaseMultipliers } from \"../data/Constants\";\n\nimport {\n getCostOfNextHacknetNode,\n getCostOfNextHacknetServer,\n hasHacknetServers,\n purchaseHacknet,\n} from \"../HacknetHelpers\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { AllServers } from \"../../Server/AllServers\";\nimport { Server } from \"../../Server/Server\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Grid from \"@mui/material/Grid\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n player: IPlayer;\n}\n\nexport function HacknetRoot(props: IProps): React.ReactElement {\n const [open, setOpen] = useState(false);\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n const [purchaseMultiplier, setPurchaseMultiplier] = useState(PurchaseMultipliers.x1);\n\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n let totalProduction = 0;\n for (let i = 0; i < props.player.hacknetNodes.length; ++i) {\n const node = props.player.hacknetNodes[i];\n if (hasHacknetServers(props.player)) {\n if (node instanceof HacknetNode) throw new Error(\"node was hacknet node\"); // should never happen\n const hserver = AllServers[node];\n if (hserver instanceof Server) throw new Error(\"node was a normal server\"); // should never happen\n if (hserver) {\n totalProduction += hserver.hashRate;\n } else {\n console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`);\n }\n } else {\n if (typeof node === \"string\") throw new Error(\"node was ip string\"); // should never happen\n totalProduction += node.moneyGainRatePerSecond;\n }\n }\n\n function handlePurchaseButtonClick(): void {\n purchaseHacknet(props.player);\n rerender();\n }\n\n // Cost to purchase a new Hacknet Node\n let purchaseCost;\n if (hasHacknetServers(props.player)) {\n purchaseCost = getCostOfNextHacknetServer(props.player);\n } else {\n purchaseCost = getCostOfNextHacknetNode(props.player);\n }\n\n // onClick event handlers for purchase multiplier buttons\n const purchaseMultiplierOnClicks = [\n () => setPurchaseMultiplier(PurchaseMultipliers.x1),\n () => setPurchaseMultiplier(PurchaseMultipliers.x5),\n () => setPurchaseMultiplier(PurchaseMultipliers.x10),\n () => setPurchaseMultiplier(PurchaseMultipliers.MAX),\n ];\n\n // HacknetNode components\n const nodes = props.player.hacknetNodes.map((node: string | HacknetNode) => {\n if (hasHacknetServers(props.player)) {\n if (node instanceof HacknetNode) throw new Error(\"node was hacknet node\"); // should never happen\n const hserver = AllServers[node];\n if (hserver == null) {\n throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`);\n }\n if (hserver instanceof Server) throw new Error(\"node was normal server\"); // should never happen\n return (\n \n );\n } else {\n if (typeof node === \"string\") throw new Error(\"node was ip string\"); // should never happen\n return (\n \n );\n }\n });\n\n return (\n <>\n Hacknet {hasHacknetServers(props.player) ? \"Servers\" : \"Nodes\"}\n \n\n \n\n
    \n\n \n \n \n \n \n \n \n \n\n {hasHacknetServers(props.player) && }\n\n {nodes}\n setOpen(false)} />\n \n );\n}\n","/**\n * React Component for the Hacknet Node UI\n *\n * Displays general information about Hacknet Nodes\n */\nimport React from \"react\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n hasHacknetServers: boolean;\n}\n\nexport function GeneralInfo(props: IProps): React.ReactElement {\n return (\n <>\n \n The Hacknet is a global, decentralized network of machines. It is used by hackers all around the world to\n anonymously share computing power and perform distributed cyberattacks without the fear of being traced.\n \n {!props.hasHacknetServers ? (\n <>\n \n {`Here, you can purchase a Hacknet Node, a specialized machine that can connect ` +\n `and contribute its resources to the Hacknet network. This allows you to take ` +\n `a small percentage of profits from hacks performed on the network. Essentially, ` +\n `you are renting out your Node's computing power.`}\n \n \n {`Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node ` +\n `can be upgraded in order to increase its computing power and thereby increase ` +\n `the profit you earn from it.`}\n \n \n ) : (\n <>\n \n {`Here, you can purchase a Hacknet Server, an upgraded version of the Hacknet Node. ` +\n `Hacknet Servers will perform computations and operations on the network, earning ` +\n `you hashes. Hashes can be spent on a variety of different upgrades.`}\n \n \n {`Hacknet Servers can also be used as servers to run scripts. However, running scripts ` +\n `on a server will reduce its hash rate (hashes generated per second). A Hacknet Server's hash ` +\n `rate will be reduced by the percentage of RAM that is being used by that Server to run ` +\n `scripts.`}\n \n \n )}\n \n );\n}\n","/**\n * React Component for the Hacknet Node UI.\n * This Component displays the panel for a single Hacknet Node\n */\nimport React from \"react\";\n\nimport { HacknetNodeConstants } from \"../data/Constants\";\nimport {\n getMaxNumberLevelUpgrades,\n getMaxNumberRamUpgrades,\n getMaxNumberCoreUpgrades,\n purchaseLevelUpgrade,\n purchaseRamUpgrade,\n purchaseCoreUpgrade,\n} from \"../HacknetHelpers\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { HacknetNode } from \"../HacknetNode\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { MoneyRate } from \"../../ui/React/MoneyRate\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Grid from \"@mui/material/Grid\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport { TableCell } from \"../../ui/React/Table\";\nimport TableBody from \"@mui/material/TableBody\";\nimport Table from \"@mui/material/Table\";\nimport TableRow from \"@mui/material/TableRow\";\n\ninterface IProps {\n node: HacknetNode;\n purchaseMultiplier: number | \"MAX\";\n rerender: () => void;\n player: IPlayer;\n}\n\nexport function HacknetNodeElem(props: IProps): React.ReactElement {\n const node = props.node;\n const purchaseMult = props.purchaseMultiplier;\n const rerender = props.rerender;\n\n // Upgrade Level Button\n let upgradeLevelContent;\n if (node.level >= HacknetNodeConstants.MaxLevel) {\n upgradeLevelContent = <>MAX LEVEL;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberLevelUpgrades(props.player, node, HacknetNodeConstants.MaxLevel);\n } else {\n const levelsToMax = HacknetNodeConstants.MaxLevel - node.level;\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, props.player.hacknet_node_level_cost_mult);\n upgradeLevelContent = (\n <>\n +{multiplier} - \n \n \n );\n }\n function upgradeLevelOnClick(): void {\n const numUpgrades =\n purchaseMult === \"MAX\"\n ? getMaxNumberLevelUpgrades(props.player, node, HacknetNodeConstants.MaxLevel)\n : purchaseMult;\n purchaseLevelUpgrade(props.player, node, numUpgrades);\n rerender();\n }\n\n let upgradeRamContent;\n if (node.ram >= HacknetNodeConstants.MaxRam) {\n upgradeRamContent = <>MAX RAM;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberRamUpgrades(props.player, node, HacknetNodeConstants.MaxRam);\n } else {\n const levelsToMax = Math.round(Math.log2(HacknetNodeConstants.MaxRam / node.ram));\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, props.player.hacknet_node_ram_cost_mult);\n upgradeRamContent = (\n <>\n +{multiplier} - \n \n \n );\n }\n function upgradeRamOnClick(): void {\n const numUpgrades =\n purchaseMult === \"MAX\" ? getMaxNumberRamUpgrades(props.player, node, HacknetNodeConstants.MaxRam) : purchaseMult;\n purchaseRamUpgrade(props.player, node, numUpgrades);\n rerender();\n }\n\n let upgradeCoresContent;\n if (node.cores >= HacknetNodeConstants.MaxCores) {\n upgradeCoresContent = <>MAX CORES;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberCoreUpgrades(props.player, node, HacknetNodeConstants.MaxCores);\n } else {\n const levelsToMax = HacknetNodeConstants.MaxCores - node.cores;\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, props.player.hacknet_node_core_cost_mult);\n upgradeCoresContent = (\n <>\n +{multiplier} - \n \n \n );\n }\n function upgradeCoresOnClick(): void {\n const numUpgrades =\n purchaseMult === \"MAX\"\n ? getMaxNumberCoreUpgrades(props.player, node, HacknetNodeConstants.MaxCores)\n : purchaseMult;\n purchaseCoreUpgrade(props.player, node, numUpgrades);\n rerender();\n }\n\n return (\n \n \n \n \n \n {node.name}\n \n \n \n \n Production:\n \n \n \n (\n )\n \n \n \n \n \n Level:\n \n \n {node.level}\n \n \n \n \n \n \n \n RAM:\n \n \n {node.ram}GB\n \n \n \n \n \n \n \n Cores:\n \n \n {node.cores}\n \n \n \n \n \n \n
    \n
    \n );\n}\n","/**\n * React Component for the Hacknet Node UI.\n * This Component displays the panel for a single Hacknet Node\n */\nimport React from \"react\";\n\nimport { HacknetServerConstants } from \"../data/Constants\";\nimport {\n getMaxNumberLevelUpgrades,\n getMaxNumberRamUpgrades,\n getMaxNumberCoreUpgrades,\n getMaxNumberCacheUpgrades,\n purchaseLevelUpgrade,\n purchaseRamUpgrade,\n purchaseCoreUpgrade,\n purchaseCacheUpgrade,\n updateHashManagerCapacity,\n} from \"../HacknetHelpers\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { HacknetServer } from \"../HacknetServer\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { Hashes } from \"../../ui/React/Hashes\";\nimport { HashRate } from \"../../ui/React/HashRate\";\nimport Typography from \"@mui/material/Typography\";\nimport Grid from \"@mui/material/Grid\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport { TableCell } from \"../../ui/React/Table\";\nimport TableBody from \"@mui/material/TableBody\";\nimport Table from \"@mui/material/Table\";\nimport TableRow from \"@mui/material/TableRow\";\n\ninterface IProps {\n node: HacknetServer;\n purchaseMultiplier: number | string;\n rerender: () => void;\n player: IPlayer;\n}\n\nexport function HacknetServerElem(props: IProps): React.ReactElement {\n const node = props.node;\n const purchaseMult = props.purchaseMultiplier;\n const rerender = props.rerender;\n\n // Upgrade Level Button\n let upgradeLevelContent;\n if (node.level >= HacknetServerConstants.MaxLevel) {\n upgradeLevelContent = <>MAX LEVEL;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberLevelUpgrades(props.player, node, HacknetServerConstants.MaxLevel);\n } else {\n const levelsToMax = HacknetServerConstants.MaxLevel - node.level;\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, props.player.hacknet_node_level_cost_mult);\n upgradeLevelContent = (\n <>\n +{multiplier} - \n \n \n );\n }\n function upgradeLevelOnClick(): void {\n let numUpgrades = purchaseMult;\n if (purchaseMult === \"MAX\") {\n numUpgrades = getMaxNumberLevelUpgrades(props.player, node, HacknetServerConstants.MaxLevel);\n }\n purchaseLevelUpgrade(props.player, node, numUpgrades as number);\n rerender();\n }\n\n // Upgrade RAM Button\n let upgradeRamContent;\n if (node.maxRam >= HacknetServerConstants.MaxRam) {\n upgradeRamContent = <>MAX RAM;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberRamUpgrades(props.player, node, HacknetServerConstants.MaxRam);\n } else {\n const levelsToMax = Math.round(Math.log2(HacknetServerConstants.MaxRam / node.maxRam));\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, props.player.hacknet_node_ram_cost_mult);\n upgradeRamContent = (\n <>\n +{multiplier} - \n \n \n );\n }\n function upgradeRamOnClick(): void {\n let numUpgrades = purchaseMult;\n if (purchaseMult === \"MAX\") {\n numUpgrades = getMaxNumberRamUpgrades(props.player, node, HacknetServerConstants.MaxRam);\n }\n purchaseRamUpgrade(props.player, node, numUpgrades as number);\n rerender();\n }\n\n // Upgrade Cores Button\n let upgradeCoresContent;\n if (node.cores >= HacknetServerConstants.MaxCores) {\n upgradeCoresContent = <>MAX CORES;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberCoreUpgrades(props.player, node, HacknetServerConstants.MaxCores);\n } else {\n const levelsToMax = HacknetServerConstants.MaxCores - node.cores;\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, props.player.hacknet_node_core_cost_mult);\n upgradeCoresContent = (\n <>\n +{multiplier} - \n \n \n );\n }\n function upgradeCoresOnClick(): void {\n let numUpgrades = purchaseMult;\n if (purchaseMult === \"MAX\") {\n numUpgrades = getMaxNumberCoreUpgrades(props.player, node, HacknetServerConstants.MaxCores);\n }\n purchaseCoreUpgrade(props.player, node, numUpgrades as number);\n rerender();\n }\n\n // Upgrade Cache button\n let upgradeCacheContent;\n if (node.cache >= HacknetServerConstants.MaxCache) {\n upgradeCacheContent = <>MAX CACHE;\n } else {\n let multiplier = 0;\n if (purchaseMult === \"MAX\") {\n multiplier = getMaxNumberCacheUpgrades(props.player, node, HacknetServerConstants.MaxCache);\n } else {\n const levelsToMax = HacknetServerConstants.MaxCache - node.cache;\n multiplier = Math.min(levelsToMax, purchaseMult as number);\n }\n\n const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier);\n upgradeCacheContent = (\n <>\n +{multiplier} - \n \n \n );\n if (props.player.money.lt(upgradeCacheCost)) {\n } else {\n }\n }\n function upgradeCacheOnClick(): void {\n let numUpgrades = purchaseMult;\n if (purchaseMult === \"MAX\") {\n numUpgrades = getMaxNumberCacheUpgrades(props.player, node, HacknetServerConstants.MaxCache);\n }\n purchaseCacheUpgrade(props.player, node, numUpgrades as number);\n rerender();\n updateHashManagerCapacity(props.player);\n }\n\n return (\n \n \n \n \n \n {node.hostname}\n \n \n \n \n Production:\n \n \n \n ()\n \n \n \n \n \n Hash Capacity:\n \n \n \n \n \n \n \n \n \n Level:\n \n \n {node.level}\n \n \n \n \n \n \n \n RAM:\n \n \n {node.maxRam}GB\n \n \n \n \n \n \n \n Cores:\n \n \n {node.cores}\n \n \n \n \n \n \n \n Cache Level:\n \n \n {node.cache}\n \n \n \n \n \n \n
    \n
    \n );\n}\n","/**\n * Create the pop-up for purchasing upgrades with hashes\n */\nimport React, { useState, useEffect } from \"react\";\n\nimport { HashManager } from \"../HashManager\";\nimport { HashUpgrades } from \"../HashUpgrades\";\n\nimport { Hashes } from \"../../ui/React/Hashes\";\nimport { HacknetUpgradeElem } from \"./HacknetUpgradeElem\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { use } from \"../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\nexport function HashUpgradeModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(() => setRerender((old) => !old), 200);\n return () => clearInterval(id);\n }, []);\n\n const hashManager = player.hashManager;\n if (!(hashManager instanceof HashManager)) {\n throw new Error(`Player does not have a HashManager)`);\n }\n\n return (\n \n <>\n Spend your hashes on a variety of different upgrades\n \n Hashes: \n \n {Object.keys(HashUpgrades).map((upgName) => {\n const upg = HashUpgrades[upgName];\n return (\n \n );\n })}\n \n \n );\n}\n","import React, { useState } from \"react\";\n\nimport { purchaseHashUpgrade } from \"../HacknetHelpers\";\nimport { HashManager } from \"../HashManager\";\nimport { HashUpgrade } from \"../HashUpgrade\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nimport { ServerDropdown, ServerType } from \"../../ui/React/ServerDropdown\";\n\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { CopyableText } from \"../../ui/React/CopyableText\";\nimport { Hashes } from \"../../ui/React/Hashes\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Paper from \"@mui/material/Paper\";\nimport Button from \"@mui/material/Button\";\nimport { SelectChangeEvent } from \"@mui/material/Select\";\n\ninterface IProps {\n player: IPlayer;\n hashManager: HashManager;\n upg: HashUpgrade;\n rerender: () => void;\n}\n\nexport function HacknetUpgradeElem(props: IProps): React.ReactElement {\n const [selectedServer, setSelectedServer] = useState(\"ecorp\");\n function changeTargetServer(event: SelectChangeEvent): void {\n setSelectedServer(event.target.value);\n }\n\n function purchase(): void {\n const canPurchase = props.hashManager.hashes >= props.hashManager.getUpgradeCost(props.upg.name);\n if (canPurchase) {\n const res = purchaseHashUpgrade(props.player, props.upg.name, selectedServer);\n if (!res) {\n dialogBoxCreate(\n \"Failed to purchase upgrade. This may be because you do not have enough hashes, \" +\n \"or because you do not have access to the feature upgrade affects.\",\n );\n }\n props.rerender();\n }\n }\n\n const hashManager = props.hashManager;\n const upg = props.upg;\n const cost = hashManager.getUpgradeCost(upg.name);\n const level = hashManager.upgrades[upg.name];\n const effect = upg.effectText(level);\n\n // Purchase button\n const canPurchase = hashManager.hashes >= cost;\n\n // We'll reuse a Bladeburner css class\n return (\n \n \n \n \n \n Cost: , Bought: {level} times\n \n\n {upg.desc}\n \n {level > 0 && effect && {effect}}\n {upg.hasTargetServer && (\n \n )}\n \n );\n}\n","/**\n * React Component for the Multiplier buttons on the Hacknet page.\n * These buttons let the player control how many Nodes/Upgrades they're\n * purchasing when using the UI (x1, x5, x10, MAX)\n */\nimport React from \"react\";\n\nimport { PurchaseMultipliers } from \"../data/Constants\";\nimport Button from \"@mui/material/Button\";\n\ninterface IMultiplierProps {\n disabled: boolean;\n onClick: () => void;\n text: string;\n}\n\nfunction MultiplierButton(props: IMultiplierProps): React.ReactElement {\n return (\n \n );\n}\n\ninterface IProps {\n purchaseMultiplier: number | string;\n onClicks: (() => void)[];\n}\n\nexport function MultiplierButtons(props: IProps): React.ReactElement {\n if (props.purchaseMultiplier == null) {\n throw new Error(`MultiplierButtons constructed without required props`);\n }\n\n const mults = [\"x1\", \"x5\", \"x10\", \"MAX\"];\n const onClicks = props.onClicks;\n const buttons = [];\n for (let i = 0; i < mults.length; ++i) {\n const mult = mults[i];\n const btnProps = {\n disabled: props.purchaseMultiplier === PurchaseMultipliers[mult],\n onClick: onClicks[i],\n text: mult,\n };\n\n buttons.push();\n }\n\n return <>{buttons};\n}\n","/**\n * React Component for displaying Player info and stats on the Hacknet Node UI.\n * This includes:\n * - Player's money\n * - Player's production from Hacknet Nodes\n */\nimport React from \"react\";\n\nimport { hasHacknetServers } from \"../HacknetHelpers\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { Money } from \"../../ui/React/Money\";\nimport { MoneyRate } from \"../../ui/React/MoneyRate\";\nimport { HashRate } from \"../../ui/React/HashRate\";\nimport { Hashes } from \"../../ui/React/Hashes\";\nimport Typography from \"@mui/material/Typography\";\n\ninterface IProps {\n totalProduction: number;\n player: IPlayer;\n}\n\nexport function PlayerInfo(props: IProps): React.ReactElement {\n const hasServers = hasHacknetServers(props.player);\n\n let prod;\n if (hasServers) {\n prod = ;\n } else {\n prod = ;\n }\n\n return (\n <>\n \n Money:\n \n \n\n {hasServers && (\n <>\n \n Hashes: /{\" \"}\n \n \n \n )}\n\n \n Total Hacknet {hasServers ? \"Server\" : \"Node\"} Production: {prod}\n \n \n );\n}\n","/**\n * React Component for the button that is used to purchase new Hacknet Nodes\n */\nimport React from \"react\";\n\nimport { hasHacknetServers, hasMaxNumberHacknetServers } from \"../HacknetHelpers\";\nimport { Player } from \"../../Player\";\nimport { Money } from \"../../ui/React/Money\";\n\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n multiplier: number | string;\n onClick: () => void;\n cost: number;\n}\n\nexport function PurchaseButton(props: IProps): React.ReactElement {\n const cost = props.cost;\n let text;\n if (hasHacknetServers(Player)) {\n if (hasMaxNumberHacknetServers(Player)) {\n text = <>Hacknet Server limit reached;\n } else {\n text = (\n <>\n Purchase Hacknet Server - \n \n \n );\n }\n } else {\n text = (\n <>\n Purchase Hacknet Node - \n \n \n );\n }\n\n return (\n \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a company\n *\n * This subcomponent renders all of the buttons for applying to jobs at a company\n */\nimport React, { useState } from \"react\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Box from \"@mui/material/Box\";\n\nimport { ApplyToJobButton } from \"./ApplyToJobButton\";\n\nimport { Locations } from \"../Locations\";\nimport { LocationName } from \"../data/LocationNames\";\n\nimport { Companies } from \"../../Company/Companies\";\nimport { CompanyPosition } from \"../../Company/CompanyPosition\";\nimport { CompanyPositions } from \"../../Company/CompanyPositions\";\nimport * as posNames from \"../../Company/data/companypositionnames\";\n\nimport { Reputation } from \"../../ui/React/Reputation\";\nimport { Favor } from \"../../ui/React/Favor\";\nimport { use } from \"../../ui/Context\";\nimport { QuitJobModal } from \"../../Company/ui/QuitJobModal\";\n\ntype IProps = {\n locName: LocationName;\n};\n\nexport function CompanyLocation(props: IProps): React.ReactElement {\n const p = use.Player();\n const router = use.Router();\n const [quitOpen, setQuitOpen] = useState(false);\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n /**\n * We'll keep a reference to the Company that this component is being rendered for,\n * so we don't have to look it up every time\n */\n const company = Companies[props.locName];\n if (company == null) throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);\n\n /**\n * Reference to the Location that this component is being rendered for\n */\n const location = Locations[props.locName];\n if (location == null) {\n throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`);\n }\n\n /**\n * Name of company position that player holds, if applicable\n */\n const jobTitle = p.jobs[props.locName] ? p.jobs[props.locName] : null;\n\n /**\n * CompanyPosition object for the job that the player holds at this company\n * (if he has one)\n */\n const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null;\n\n p.location = props.locName;\n\n function applyForAgentJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForAgentJob();\n rerender();\n }\n\n function applyForBusinessConsultantJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForBusinessConsultantJob();\n rerender();\n }\n\n function applyForBusinessJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForBusinessJob();\n rerender();\n }\n\n function applyForEmployeeJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForEmployeeJob();\n rerender();\n }\n\n function applyForItJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForItJob();\n rerender();\n }\n\n function applyForPartTimeEmployeeJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForPartTimeEmployeeJob();\n rerender();\n }\n\n function applyForPartTimeWaiterJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForPartTimeWaiterJob();\n rerender();\n }\n\n function applyForSecurityJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForSecurityJob();\n rerender();\n }\n\n function applyForSoftwareConsultantJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForSoftwareConsultantJob();\n rerender();\n }\n\n function applyForSoftwareJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForSoftwareJob();\n rerender();\n }\n\n function applyForWaiterJob(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n p.applyForWaiterJob();\n rerender();\n }\n\n function startInfiltration(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n const loc = location;\n if (!loc.infiltrationData)\n throw new Error(`trying to start infiltration at ${props.locName} but the infiltrationData is null`);\n\n router.toInfiltration(loc);\n }\n\n function work(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n\n const pos = companyPosition;\n if (pos instanceof CompanyPosition) {\n if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) {\n p.startWorkPartTime(router, props.locName);\n } else {\n p.startWork(router, props.locName);\n }\n router.toWork();\n }\n }\n\n const isEmployedHere = jobTitle != null;\n const favorGain = company.getFavorGain();\n\n return (\n <>\n {isEmployedHere && (\n <>\n Job Title: {jobTitle}\n -------------------------\n \n \n You will have company favor upon resetting after\n installing Augmentations\n \n }\n >\n \n Company reputation: \n \n \n \n -------------------------\n \n \n Company favor increases the rate at which you earn reputation for this company by 1% per favor.\n Company favor is gained whenever you reset after installing Augmentations. The amount of favor you\n gain depends on how much reputation you have with the company.\n \n }\n >\n \n Company Favor: \n \n \n \n -------------------------\n
    \n \n     \n \n setQuitOpen(false)}\n />\n \n )}\n
    \n {company.hasAgentPositions() && (\n \n )}\n {company.hasBusinessConsultantPositions() && (\n \n )}\n {company.hasBusinessPositions() && (\n \n )}\n {company.hasEmployeePositions() && (\n \n )}\n {company.hasEmployeePositions() && (\n \n )}\n {company.hasITPositions() && (\n \n )}\n {company.hasSecurityPositions() && (\n \n )}\n {company.hasSoftwareConsultantPositions() && (\n \n )}\n {company.hasSoftwarePositions() && (\n \n )}\n {company.hasWaiterPositions() && (\n \n )}\n {company.hasWaiterPositions() && (\n \n )}\n {location.infiltrationData != null && }\n \n );\n}\n","import React from \"react\";\nimport { Company } from \"../Company\";\nimport { use } from \"../../ui/Context\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n locName: string;\n company: Company;\n onQuit: () => void;\n}\n\nexport function QuitJobModal(props: IProps): React.ReactElement {\n const player = use.Player();\n function quit(): void {\n player.quitJob(props.locName);\n props.onQuit();\n props.onClose();\n }\n\n return (\n \n Would you like to quit your job at {props.company.name}?\n
    \n
    \n \n
    \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a gym\n *\n * This subcomponent renders all of the buttons for training at the gym\n */\nimport * as React from \"react\";\nimport Button from \"@mui/material/Button\";\n\nimport { Location } from \"../Location\";\n\nimport { CONSTANTS } from \"../../Constants\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { getServer } from \"../../Server/ServerHelpers\";\nimport { Server } from \"../../Server/Server\";\nimport { SpecialServerIps } from \"../../Server/SpecialServerIps\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { IRouter } from \"../../ui/Router\";\n\ntype IProps = {\n loc: Location;\n p: IPlayer;\n router: IRouter;\n};\n\nexport function GymLocation(props: IProps): React.ReactElement {\n function calculateCost(): number {\n const ip = SpecialServerIps.getIp(props.loc.name);\n const server = getServer(ip);\n if (server == null || !server.hasOwnProperty(\"backdoorInstalled\")) return props.loc.costMult;\n const discount = (server as Server).backdoorInstalled ? 0.9 : 1;\n return props.loc.costMult * discount;\n }\n\n function train(stat: string): void {\n const loc = props.loc;\n props.p.startClass(props.router, calculateCost(), loc.expMult, stat);\n }\n\n function trainStrength(): void {\n train(CONSTANTS.ClassGymStrength);\n }\n\n function trainDefense(): void {\n train(CONSTANTS.ClassGymDefense);\n }\n\n function trainDexterity(): void {\n train(CONSTANTS.ClassGymDexterity);\n }\n\n function trainAgility(): void {\n train(CONSTANTS.ClassGymAgility);\n }\n\n const cost = CONSTANTS.ClassGymBaseCost * calculateCost();\n\n return (\n <>\n \n
    \n \n
    \n \n
    \n \n \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a hospital\n *\n * This subcomponent renders all of the buttons for hospital options\n */\nimport * as React from \"react\";\nimport Button from \"@mui/material/Button\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { getHospitalizationCost } from \"../../Hospital/Hospital\";\n\nimport { Money } from \"../../ui/React/Money\";\n\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\n\ntype IProps = {\n p: IPlayer;\n};\n\ntype IState = {\n currHp: number;\n};\n\nexport class HospitalLocation extends React.Component {\n /**\n * Stores button styling that sets them all to block display\n */\n btnStyle: any;\n\n constructor(props: IProps) {\n super(props);\n\n this.btnStyle = { display: \"block\" };\n\n this.getCost = this.getCost.bind(this);\n this.getHealed = this.getHealed.bind(this);\n\n this.state = {\n currHp: this.props.p.hp,\n };\n }\n\n getCost(): number {\n return getHospitalizationCost(this.props.p);\n }\n\n getHealed(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n\n if (this.props.p.hp < 0) {\n this.props.p.hp = 0;\n }\n if (this.props.p.hp >= this.props.p.max_hp) {\n return;\n }\n\n const cost = this.getCost();\n this.props.p.loseMoney(cost);\n this.props.p.hp = this.props.p.max_hp;\n this.props.p.recordMoneySource(-1 * cost, \"hospitalization\");\n\n // This just forces a re-render to update the cost\n this.setState({\n currHp: this.props.p.hp,\n });\n\n dialogBoxCreate(\n <>\n You were healed to full health! The hospital billed you for \n ,\n );\n }\n\n render(): React.ReactNode {\n const cost = this.getCost();\n\n return (\n \n );\n }\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a slum\n *\n * This subcomponent renders all of the buttons for committing crimes\n */\nimport * as React from \"react\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\nimport { Crimes } from \"../../Crime/Crimes\";\n\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { use } from \"../../ui/Context\";\n\nexport function SlumsLocation(): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n function shoplift(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Shoplift.commit(router, player);\n }\n\n function robStore(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.RobStore.commit(router, player);\n }\n\n function mug(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Mug.commit(router, player);\n }\n\n function larceny(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Larceny.commit(router, player);\n }\n\n function dealDrugs(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.DealDrugs.commit(router, player);\n }\n\n function bondForgery(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.BondForgery.commit(router, player);\n }\n\n function traffickArms(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.TraffickArms.commit(router, player);\n }\n\n function homicide(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Homicide.commit(router, player);\n }\n\n function grandTheftAuto(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.GrandTheftAuto.commit(router, player);\n }\n\n function kidnap(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Kidnap.commit(router, player);\n }\n\n function assassinate(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Assassination.commit(router, player);\n }\n\n function heist(e: React.MouseEvent): void {\n if (!e.isTrusted) {\n return;\n }\n Crimes.Heist.commit(router, player);\n }\n\n const shopliftChance = Crimes.Shoplift.successRate(player);\n const robStoreChance = Crimes.RobStore.successRate(player);\n const mugChance = Crimes.Mug.successRate(player);\n const larcenyChance = Crimes.Larceny.successRate(player);\n const drugsChance = Crimes.DealDrugs.successRate(player);\n const bondChance = Crimes.BondForgery.successRate(player);\n const armsChance = Crimes.TraffickArms.successRate(player);\n const homicideChance = Crimes.Homicide.successRate(player);\n const gtaChance = Crimes.GrandTheftAuto.successRate(player);\n const kidnapChance = Crimes.Kidnap.successRate(player);\n const assassinateChance = Crimes.Assassination.successRate(player);\n const heistChance = Crimes.Heist.successRate(player);\n\n return (\n <>\n Attempt to shoplift from a low-end retailer}>\n \n \n
    \n Attempt to commit armed robbery on a high-end store}>\n \n \n
    \n Attempt to mug a random person on the street}>\n \n \n
    \n Attempt to rob property from someone's house}>\n \n \n
    \n Attempt to deal drugs}>\n \n \n
    \n Attempt to forge corporate bonds}>\n \n \n
    \n Attempt to smuggle illegal arms into the city}>\n \n \n
    \n Attempt to murder a random person on the street}>\n \n \n
    \n Attempt to commit grand theft auto}>\n \n \n
    \n Attempt to kidnap and ransom a high-profile-target}>\n \n \n
    \n Attempt to assassinate a high-profile target}>\n \n \n
    \n Attempt to pull off the ultimate heist}>\n \n \n
    \n \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location has special\n * actions/options/properties\n *\n * Examples:\n * - Bladeburner @ NSA\n * - Re-sleeving @ VitaLife\n * - Create Corporation @ City Hall\n *\n * This subcomponent creates all of the buttons for interacting with those special\n * properties\n */\nimport React, { useState } from \"react\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\nimport { Location } from \"../Location\";\nimport { CreateCorporationModal } from \"../../Corporation/ui/CreateCorporationModal\";\nimport { LocationName } from \"../data/LocationNames\";\n\nimport { use } from \"../../ui/Context\";\n\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\n\ntype IProps = {\n loc: Location;\n};\n\nexport function SpecialLocation(props: IProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const setRerender = useState(false)[1];\n const inBladeburner = player.inBladeburner();\n\n /**\n * Click handler for Bladeburner button at Sector-12 NSA\n */\n function handleBladeburner(): void {\n const p = player;\n if (p.inBladeburner()) {\n // Enter Bladeburner division\n router.toBladeburner();\n } else {\n // Apply for Bladeburner division\n if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {\n p.startBladeburner({ new: true });\n dialogBoxCreate(\"You have been accepted into the Bladeburner division!\");\n setRerender((old) => !old);\n\n const worldHeader = document.getElementById(\"world-menu-header\");\n if (worldHeader instanceof HTMLElement) {\n worldHeader.click();\n worldHeader.click();\n }\n } else {\n dialogBoxCreate(\"Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)\");\n }\n }\n }\n\n /**\n * Click handler for Resleeving button at New Tokyo VitaLife\n */\n function handleResleeving(): void {\n router.toResleeves();\n }\n\n function renderBladeburner(): React.ReactElement {\n if (!player.canAccessBladeburner()) {\n return <>;\n }\n const text = inBladeburner ? \"Enter Bladeburner Headquarters\" : \"Apply to Bladeburner Division\";\n return ;\n }\n\n function renderNoodleBar(): React.ReactElement {\n function EatNoodles(): void {\n dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.);\n }\n\n return ;\n }\n\n function CreateCorporation(): React.ReactElement {\n const [open, setOpen] = useState(false);\n if (!player.canAccessCorporation()) {\n return (\n <>\n \n A business man is yelling at a clerk. You should come back later.\n \n \n );\n }\n return (\n <>\n \n setOpen(false)} />\n \n );\n }\n\n function renderResleeving(): React.ReactElement {\n if (!player.canAccessResleeving()) {\n return <>;\n }\n return ;\n }\n\n switch (props.loc.name) {\n case LocationName.NewTokyoVitaLife: {\n return renderResleeving();\n }\n case LocationName.Sector12CityHall: {\n return ;\n }\n case LocationName.Sector12NSA: {\n return renderBladeburner();\n }\n case LocationName.NewTokyoNoodleBar: {\n return renderNoodleBar();\n }\n default:\n console.error(`Location ${props.loc.name} doesn't have any special properties`);\n return <>;\n }\n}\n","import React, { useState } from \"react\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { use } from \"../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n}\n\nexport function CreateCorporationModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const canSelfFund = player.canAfford(150e9);\n if (!player.canAccessCorporation() || player.hasCorporation()) {\n props.onClose();\n return <>;\n }\n\n const [name, setName] = useState(\"\");\n function onChange(event: React.ChangeEvent): void {\n setName(event.target.value);\n }\n\n function selfFund(): void {\n if (!canSelfFund) {\n return;\n }\n\n if (name == \"\") {\n return;\n }\n\n player.startCorporation(name);\n player.loseMoney(150e9);\n\n props.onClose();\n router.toCorporation();\n }\n\n function seed(): void {\n if (name == \"\") {\n return;\n }\n\n player.startCorporation(name, 500e6);\n\n props.onClose();\n router.toCorporation();\n }\n\n return (\n \n \n Would you like to start a corporation? This will require $150b for registration and initial funding. This $150b\n can either be self-funded, or you can obtain the seed money from the government in exchange for 500 million\n shares\n
    \n
    \n If you would like to start one, please enter a name for your corporation below:\n
    \n \n \n \n
    \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a tech vendor\n *\n * This subcomponent renders all of the buttons for purchasing things from tech vendors\n */\nimport React, { useState, useEffect } from \"react\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\nimport { Location } from \"../Location\";\nimport { RamButton } from \"./RamButton\";\nimport { TorButton } from \"./TorButton\";\nimport { CoresButton } from \"./CoresButton\";\n\nimport { getPurchaseServerCost } from \"../../Server/ServerPurchases\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { use } from \"../../ui/Context\";\nimport { PurchaseServerModal } from \"./PurchaseServerModal\";\n\ninterface IServerProps {\n ram: number;\n rerender: () => void;\n}\n\nfunction ServerButton(props: IServerProps): React.ReactElement {\n const [open, setOpen] = useState(false);\n const player = use.Player();\n const cost = getPurchaseServerCost(props.ram);\n return (\n <>\n \n setOpen(false)}\n ram={props.ram}\n cost={cost}\n rerender={props.rerender}\n />\n
    \n \n );\n}\n\ntype IProps = {\n loc: Location;\n};\n\nexport function TechVendorLocation(props: IProps): React.ReactElement {\n const player = use.Player();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 1000);\n return () => clearInterval(id);\n }, []);\n\n const purchaseServerButtons: React.ReactNode[] = [];\n for (let i = props.loc.techVendorMinRam; i <= props.loc.techVendorMaxRam; i *= 2) {\n purchaseServerButtons.push();\n }\n\n return (\n <>\n {purchaseServerButtons}\n
    \n \n \"You can order bigger servers via scripts. We don't take custom order in person.\"\n \n
    \n \n
    \n \n
    \n \n \n );\n}\n","import React from \"react\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\nimport { CONSTANTS } from \"../../Constants\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { purchaseRamForHomeComputer } from \"../../Server/ServerPurchases\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { MathComponent } from \"mathjax-react\";\n\ntype IProps = {\n p: IPlayer;\n rerender: () => void;\n};\n\nexport function RamButton(props: IProps): React.ReactElement {\n const homeComputer = props.p.getHomeComputer();\n if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {\n return ;\n }\n\n const cost = props.p.getUpgradeHomeRamCost();\n\n function buy(): void {\n purchaseRamForHomeComputer(props.p);\n props.rerender();\n }\n\n return (\n }>\n \n \n \n \n );\n}\n","import React from \"react\";\nimport Button from \"@mui/material/Button\";\n\nimport { purchaseTorRouter } from \"../LocationsHelpers\";\n\nimport { CONSTANTS } from \"../../Constants\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nimport { Money } from \"../../ui/React/Money\";\n\ntype IProps = {\n p: IPlayer;\n rerender: () => void;\n};\n\nexport function TorButton(props: IProps): React.ReactElement {\n function buy(): void {\n purchaseTorRouter(props.p);\n props.rerender();\n }\n\n if (props.p.hasTorRouter()) {\n return ;\n }\n\n return (\n \n );\n}\n","/**\n * Location and traveling-related helper functions.\n * Mostly used for UI\n */\nimport { CONSTANTS } from \"../Constants\";\n\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { AddToAllServers, createUniqueRandomIp } from \"../Server/AllServers\";\nimport { safetlyCreateUniqueServer } from \"../Server/ServerHelpers\";\nimport { SpecialServerIps } from \"../Server/SpecialServerIps\";\n\nimport { dialogBoxCreate } from \"../ui/React/DialogBox\";\n\n/**\n * Attempt to purchase a TOR router\n * @param {IPlayer} p - Player object\n */\nexport function purchaseTorRouter(p: IPlayer): void {\n if (p.hasTorRouter()) {\n dialogBoxCreate(`You already have a TOR Router!`);\n return;\n }\n if (!p.canAfford(CONSTANTS.TorRouterCost)) {\n dialogBoxCreate(\"You cannot afford to purchase the TOR router!\");\n return;\n }\n p.loseMoney(CONSTANTS.TorRouterCost);\n\n const darkweb = safetlyCreateUniqueServer({\n ip: createUniqueRandomIp(),\n hostname: \"darkweb\",\n organizationName: \"\",\n isConnectedTo: false,\n adminRights: false,\n purchasedByPlayer: false,\n maxRam: 1,\n });\n AddToAllServers(darkweb);\n SpecialServerIps.addIp(\"Darkweb Server\", darkweb.ip);\n\n p.getHomeComputer().serversOnNetwork.push(darkweb.ip);\n darkweb.serversOnNetwork.push(p.getHomeComputer().ip);\n dialogBoxCreate(\n \"You have purchased a TOR router!
    \" +\n \"You now have access to the dark web from your home computer.
    \" +\n \"Use the scan/scan-analyze commands to search for the dark web connection.\",\n );\n}\n","import React from \"react\";\nimport Button from \"@mui/material/Button\";\nimport Tooltip from \"@mui/material/Tooltip\";\n\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { MathComponent } from \"mathjax-react\";\n\ntype IProps = {\n p: IPlayer;\n rerender: () => void;\n};\n\nexport function CoresButton(props: IProps): React.ReactElement {\n const homeComputer = props.p.getHomeComputer();\n const maxCores = homeComputer.cpuCores >= 8;\n if (maxCores) {\n return ;\n }\n\n const cost = 1e9 * Math.pow(7.5, homeComputer.cpuCores);\n\n function buy(): void {\n if (maxCores) return;\n if (!props.p.canAfford(cost)) return;\n props.p.loseMoney(cost);\n homeComputer.cpuCores++;\n props.rerender();\n }\n\n return (\n }>\n \n \n \n \n );\n}\n","/**\n * React Component for the popup used to purchase a new server.\n */\nimport React, { useState } from \"react\";\nimport { purchaseServer } from \"../../Server/ServerPurchases\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { Money } from \"../../ui/React/Money\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { use } from \"../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n open: boolean;\n onClose: () => void;\n ram: number;\n cost: number;\n rerender: () => void;\n}\n\nexport function PurchaseServerModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const [hostname, setHostname] = useState(\"\");\n\n function tryToPurchaseServer(): void {\n purchaseServer(hostname, props.ram, props.cost, player);\n props.onClose();\n }\n\n function onKeyUp(event: React.KeyboardEvent): void {\n if (event.keyCode === 13) tryToPurchaseServer();\n }\n\n function onChange(event: React.ChangeEvent): void {\n setHostname(event.target.value);\n }\n\n return (\n \n \n Would you like to purchase a new server with {numeralWrapper.formatRAM(props.ram)} of RAM for{\" \"}\n ?\n \n
    \n
    \n Please enter the server hostname below:\n
    \n\n \n Buy\n \n ),\n }}\n />\n
    \n );\n}\n","import React from \"react\";\nimport { CONSTANTS } from \"../../Constants\";\nimport { Money } from \"../../ui/React/Money\";\nimport { Modal } from \"../../ui/React/Modal\";\nimport { use } from \"../../ui/Context\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ninterface IProps {\n city: string;\n travel: () => void;\n\n open: boolean;\n onClose: () => void;\n}\n\nexport function TravelConfirmationModal(props: IProps): React.ReactElement {\n const player = use.Player();\n const cost = CONSTANTS.TravelCost;\n function travel(): void {\n props.travel();\n }\n\n return (\n \n \n Would you like to travel to {props.city}? The trip will cost .\n \n
    \n
    \n \n
    \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a university\n *\n * This subcomponent renders all of the buttons for studying/taking courses\n */\nimport * as React from \"react\";\nimport Tooltip from \"@mui/material/Tooltip\";\nimport Button from \"@mui/material/Button\";\n\nimport { Location } from \"../Location\";\n\nimport { CONSTANTS } from \"../../Constants\";\nimport { getServer } from \"../../Server/ServerHelpers\";\nimport { Server } from \"../../Server/Server\";\nimport { SpecialServerIps } from \"../../Server/SpecialServerIps\";\n\nimport { Money } from \"../../ui/React/Money\";\nimport { use } from \"../../ui/Context\";\n\ntype IProps = {\n loc: Location;\n};\n\nexport function UniversityLocation(props: IProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n\n function calculateCost(): number {\n const ip = SpecialServerIps.getIp(props.loc.name);\n const server = getServer(ip);\n if (server == null || !server.hasOwnProperty(\"backdoorInstalled\")) return props.loc.costMult;\n const discount = (server as Server).backdoorInstalled ? 0.9 : 1;\n return props.loc.costMult * discount;\n }\n\n function take(stat: string): void {\n const loc = props.loc;\n player.startClass(router, calculateCost(), loc.expMult, stat);\n }\n\n function study(): void {\n take(CONSTANTS.ClassStudyComputerScience);\n }\n\n function dataStructures(): void {\n take(CONSTANTS.ClassDataStructures);\n }\n\n function networks(): void {\n take(CONSTANTS.ClassNetworks);\n }\n\n function algorithms(): void {\n take(CONSTANTS.ClassAlgorithms);\n }\n\n function management(): void {\n take(CONSTANTS.ClassManagement);\n }\n\n function leadership(): void {\n take(CONSTANTS.ClassLeadership);\n }\n\n const costMult: number = calculateCost();\n\n const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;\n const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;\n const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;\n const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;\n const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;\n\n const earnHackingExpTooltip = `Gain hacking experience!`;\n const earnCharismaExpTooltip = `Gain charisma experience!`;\n\n return (\n <>\n \n \n \n
    \n \n \n \n
    \n \n \n \n
    \n \n \n \n
    \n \n \n \n
    \n \n \n \n \n );\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a gym\n *\n * This subcomponent renders all of the buttons for training at the gym\n */\nimport React, { useState } from \"react\";\nimport Button from \"@mui/material/Button\";\nimport { Blackjack } from \"../../Casino/Blackjack\";\nimport { CoinFlip } from \"../../Casino/CoinFlip\";\nimport { Roulette } from \"../../Casino/Roulette\";\nimport { SlotMachine } from \"../../Casino/SlotMachine\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\n\nenum GameType {\n None = \"none\",\n Coin = \"coin\",\n Slots = \"slots\",\n Roulette = \"roulette\",\n Blackjack = \"blackjack\",\n}\n\ntype IProps = {\n p: IPlayer;\n};\n\nexport function CasinoLocation(props: IProps): React.ReactElement {\n const [game, setGame] = useState(GameType.None);\n\n function updateGame(game: GameType): void {\n setGame(game);\n }\n\n return (\n <>\n {game === GameType.None && (\n <>\n \n
    \n \n
    \n \n
    \n \n \n )}\n {game !== GameType.None && (\n <>\n \n {game === GameType.Coin && }\n {game === GameType.Slots && }\n {game === GameType.Roulette && }\n {game === GameType.Blackjack && }\n \n )}\n \n );\n}\n","import * as React from \"react\";\n\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { Money } from \"../ui/React/Money\";\nimport { Game } from \"./Game\";\nimport { Deck } from \"./CardDeck/Deck\";\nimport { Hand } from \"./CardDeck/Hand\";\nimport { InputAdornment } from \"@mui/material\";\nimport { ReactCard } from \"./CardDeck/ReactCard\";\nimport Button from \"@mui/material/Button\";\nimport Paper from \"@mui/material/Paper\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\n\nconst MAX_BET = 100e6;\n\nenum Result {\n Pending = \"\",\n PlayerWon = \"You won!\",\n PlayerWonByBlackjack = \"You Won! Blackjack!\",\n DealerWon = \"You lost!\",\n Tie = \"Push! (Tie)\",\n}\n\ntype Props = {\n p: IPlayer;\n};\n\ntype State = {\n playerHand: Hand;\n dealerHand: Hand;\n bet: number;\n betInput: string;\n gameInProgress: boolean;\n result: Result;\n gains: number; // Track gains only for this session\n wagerInvalid: boolean;\n wagerInvalidHelperText: string;\n};\n\nexport class Blackjack extends Game {\n deck: Deck;\n\n constructor(props: Props) {\n super(props);\n\n this.deck = new Deck(5); // 5-deck multideck\n\n const initialBet = 1e6;\n\n this.state = {\n playerHand: new Hand([]),\n dealerHand: new Hand([]),\n bet: initialBet,\n betInput: String(initialBet),\n gameInProgress: false,\n result: Result.Pending,\n gains: 0,\n wagerInvalid: false,\n wagerInvalidHelperText: \"\",\n };\n }\n\n canStartGame = (): boolean => {\n const { p } = this.props;\n const { bet } = this.state;\n\n return p.canAfford(bet);\n };\n\n startGame = (): void => {\n if (!this.canStartGame()) {\n return;\n }\n\n // Take money from player right away so that player's dont just \"leave\" to avoid the loss (I mean they could\n // always reload without saving but w.e)\n this.props.p.loseMoney(this.state.bet);\n\n const playerHand = new Hand([this.deck.safeDrawCard(), this.deck.safeDrawCard()]);\n const dealerHand = new Hand([this.deck.safeDrawCard(), this.deck.safeDrawCard()]);\n\n this.setState({\n playerHand,\n dealerHand,\n gameInProgress: true,\n result: Result.Pending,\n });\n\n // If the player is dealt a blackjack and the dealer is not, then the player\n // immediately wins\n if (this.getTrueHandValue(playerHand) === 21) {\n if (this.getTrueHandValue(dealerHand) === 21) {\n this.finishGame(Result.Tie);\n } else {\n this.finishGame(Result.PlayerWonByBlackjack);\n }\n } else if (this.getTrueHandValue(dealerHand) === 21) {\n // Check if dealer won by blackjack. We know at this point that the player does not also have blackjack.\n this.finishGame(Result.DealerWon);\n }\n };\n\n // Returns an array of numbers representing all possible values of the given Hand. The reason it needs to be\n // an array is because an Ace can count as both 1 and 11.\n getHandValue = (hand: Hand): number[] => {\n let result: number[] = [0];\n\n for (let i = 0; i < hand.cards.length; ++i) {\n const value = hand.cards[i].value;\n if (value >= 10) {\n result = result.map((x) => x + 10);\n } else if (value === 1) {\n result = result.flatMap((x) => [x + 1, x + 11]);\n } else {\n result = result.map((x) => x + value);\n }\n }\n\n return result;\n };\n\n // Returns the single hand value used for determine things like victory and whether or not\n // the dealer has to hit. Essentially this uses the biggest value that's 21 or under. If no such value exists,\n // then it means the hand is busted and we can just return whatever\n getTrueHandValue = (hand: Hand): number => {\n const handValues = this.getHandValue(hand);\n const valuesUnder21 = handValues.filter((x) => x <= 21);\n\n if (valuesUnder21.length > 0) {\n valuesUnder21.sort((a, b) => a - b);\n return valuesUnder21[valuesUnder21.length - 1];\n } else {\n // Just return the first value. It doesnt really matter anyways since hand is buted\n return handValues[0];\n }\n };\n\n // Returns all hand values that are 21 or under. If no values are 21 or under, then the first value is returned.\n getHandDisplayValues = (hand: Hand): number[] => {\n const handValues = this.getHandValue(hand);\n if (this.isHandBusted(hand)) {\n // Hand is busted so just return the 1st value, doesn't really matter\n return [...new Set([handValues[0]])];\n } else {\n return [...new Set(handValues.filter((x) => x <= 21))];\n }\n };\n\n isHandBusted = (hand: Hand): boolean => {\n return this.getTrueHandValue(hand) > 21;\n };\n\n playerHit = (event: React.MouseEvent): void => {\n if (!event.isTrusted) {\n return;\n }\n\n const newHand = this.state.playerHand.addCards(this.deck.safeDrawCard());\n\n this.setState({\n playerHand: newHand,\n });\n\n // Check if player busted, and finish the game if so\n if (this.isHandBusted(newHand)) {\n this.finishGame(Result.DealerWon);\n }\n };\n\n playerStay = (event: React.MouseEvent): void => {\n if (!event.isTrusted) {\n return;\n }\n\n // Determine if Dealer needs to hit. A dealer must hit if they have 16 or lower.\n // If the dealer has a Soft 17 (Ace + 6), then they stay.\n let newDealerHand = this.state.dealerHand;\n while (true) {\n // The dealer's \"true\" hand value is the 2nd one if its 21 or less (the 2nd value is always guaranteed\n // to be equal or larger). Otherwise its the 1st.\n const dealerHandValue = this.getTrueHandValue(newDealerHand);\n\n if (dealerHandValue <= 16) {\n newDealerHand = newDealerHand.addCards(this.deck.safeDrawCard());\n } else {\n break;\n }\n }\n\n this.setState({\n dealerHand: newDealerHand,\n });\n\n // If dealer has busted, then player wins\n if (this.isHandBusted(newDealerHand)) {\n this.finishGame(Result.PlayerWon);\n } else {\n const dealerHandValue = this.getTrueHandValue(newDealerHand);\n const playerHandValue = this.getTrueHandValue(this.state.playerHand);\n\n // We expect nobody to have busted. If someone busted, there is an error\n // in our game logic\n if (dealerHandValue > 21 || playerHandValue > 21) {\n throw new Error(\"Someone busted when not expected to\");\n }\n\n if (playerHandValue > dealerHandValue) {\n this.finishGame(Result.PlayerWon);\n } else if (playerHandValue < dealerHandValue) {\n this.finishGame(Result.DealerWon);\n } else {\n this.finishGame(Result.Tie);\n }\n }\n };\n\n finishGame = (result: Result): void => {\n let gains = 0;\n if (this.isPlayerWinResult(result)) {\n gains = this.state.bet;\n\n // We 2x the gains because we took away money at the start, so we need to give the original bet back.\n this.win(this.props.p, 2 * gains);\n } else if (result === Result.DealerWon) {\n gains = -1 * this.state.bet;\n this.win(this.props.p, -this.state.bet); // Get the original bet back\n // Dont need to take money here since we already did it at the start\n } else if (result === Result.Tie) {\n this.win(this.props.p, this.state.bet); // Get the original bet back\n }\n\n this.setState({\n gameInProgress: false,\n result,\n gains: this.state.gains + gains,\n });\n };\n\n isPlayerWinResult = (result: Result): boolean => {\n return result === Result.PlayerWon || result === Result.PlayerWonByBlackjack;\n };\n\n wagerOnChange = (event: React.ChangeEvent): void => {\n const { p } = this.props;\n const betInput = event.target.value;\n const wager = Math.round(parseFloat(betInput));\n if (isNaN(wager)) {\n this.setState({\n bet: 0,\n betInput,\n wagerInvalid: true,\n wagerInvalidHelperText: \"Not a valid number\",\n });\n } else if (wager <= 0) {\n this.setState({\n bet: 0,\n betInput,\n wagerInvalid: true,\n wagerInvalidHelperText: \"Must bet a postive amount\",\n });\n } else if (wager > MAX_BET) {\n this.setState({\n bet: 0,\n betInput,\n wagerInvalid: true,\n wagerInvalidHelperText: \"Exceeds max bet\",\n });\n } else if (!p.canAfford(wager)) {\n this.setState({\n bet: 0,\n betInput,\n wagerInvalid: true,\n wagerInvalidHelperText: \"Not enough money\",\n });\n } else {\n // Valid wager\n this.setState({\n bet: wager,\n betInput,\n wagerInvalid: false,\n wagerInvalidHelperText: \"\",\n result: Result.Pending, // Reset previous game status to clear the win/lose text UI\n });\n }\n };\n\n // Start game button\n startOnClick = (event: React.MouseEvent): void => {\n // Protect against scripting...although maybe this would be fun to automate\n if (!event.isTrusted) {\n return;\n }\n\n if (!this.state.wagerInvalid) {\n this.startGame();\n }\n };\n\n render(): React.ReactNode {\n const { betInput, playerHand, dealerHand, gameInProgress, result, wagerInvalid, wagerInvalidHelperText, gains } =\n this.state;\n\n // Get the player totals to display.\n const playerHandValues = this.getHandDisplayValues(playerHand);\n const dealerHandValues = this.getHandDisplayValues(dealerHand);\n\n return (\n <>\n {/* Wager input */}\n \n \n {\"Wager (Max: \"}\n \n {\")\"}\n \n }\n disabled={gameInProgress}\n onChange={this.wagerOnChange}\n error={wagerInvalid}\n helperText={wagerInvalid ? wagerInvalidHelperText : \"\"}\n type=\"number\"\n style={{\n width: \"200px\",\n }}\n InputProps={{\n startAdornment: (\n \n $\n \n ),\n }}\n />\n\n \n {\"Total earnings this session: \"}\n \n \n \n\n {/* Buttons */}\n {!gameInProgress ? (\n \n ) : (\n <>\n \n \n \n )}\n\n {/* Main game part. Displays both if the game is in progress OR if there's a result so you can see\n * the cards that led to that result. */}\n {(gameInProgress || result !== Result.Pending) && (\n <>\n \n \n
    Player
    \n {playerHand.cards.map((card, i) => (\n \n ))}\n\n
    Value(s): 
    \n {playerHandValues.map((value, i) => (\n
    {value}
    \n ))}\n
    \n
    \n\n
    \n\n \n \n
    Dealer
    \n {dealerHand.cards.map((card, i) => (\n // Hide every card except the first while game is in progress\n
    \n
    \n \n )}\n\n {/* Results from previous round */}\n {result !== Result.Pending && (\n \n {result}\n {this.isPlayerWinResult(result) && }\n {result === Result.DealerWon && }\n \n )}\n \n );\n }\n}\n","import { Card, Suit } from \"./Card\";\nimport { shuffle } from \"lodash\";\n\nexport class Deck {\n private cards: Card[] = [];\n\n // Support multiple decks\n constructor(private numDecks = 1) {\n this.reset();\n }\n\n shuffle(): void {\n this.cards = shuffle(this.cards); // Just use lodash\n }\n\n drawCard(): Card {\n if (this.cards.length == 0) {\n throw new Error(\"Tried to draw card from empty deck\");\n }\n\n return this.cards.shift() as Card; // Guaranteed to return a Card since we throw an Error if array is empty\n }\n\n // Draws a card, resetting the deck beforehands if the Deck is empty\n safeDrawCard(): Card {\n if (this.cards.length === 0) {\n this.reset();\n }\n\n return this.drawCard();\n }\n\n // Reset the deck back to the original 52 cards and shuffle it\n reset(): void {\n this.cards = [];\n\n for (let i = 1; i <= 13; ++i) {\n for (let j = 0; j < this.numDecks; ++j) {\n this.cards.push(new Card(i, Suit.Clubs));\n this.cards.push(new Card(i, Suit.Diamonds));\n this.cards.push(new Card(i, Suit.Hearts));\n this.cards.push(new Card(i, Suit.Spades));\n }\n }\n\n this.shuffle();\n }\n\n size(): number {\n return this.cards.length;\n }\n\n isEmpty(): boolean {\n return this.cards.length === 0;\n }\n}\n","/**\n * React Subcomponent for displaying a location's UI, when that location is a gym\n *\n * This subcomponent renders all of the buttons for training at the gym\n */\nimport React, { useState } from \"react\";\n\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { BadRNG } from \"./RNG\";\nimport { win, reachedLimit } from \"./Game\";\nimport { trusted } from \"./utils\";\n\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\n\ntype IProps = {\n p: IPlayer;\n};\n\nconst minPlay = 0;\nconst maxPlay = 10e3;\n\nexport function CoinFlip(props: IProps): React.ReactElement {\n const [investment, setInvestment] = useState(1000);\n const [result, setResult] = useState( );\n const [status, setStatus] = useState(\"\");\n const [playLock, setPlayLock] = useState(false);\n\n function updateInvestment(e: React.ChangeEvent): void {\n let investment: number = parseInt(e.currentTarget.value);\n if (isNaN(investment)) {\n investment = minPlay;\n }\n if (investment > maxPlay) {\n investment = maxPlay;\n }\n if (investment < minPlay) {\n investment = minPlay;\n }\n setInvestment(investment);\n }\n\n function play(guess: string): void {\n if (reachedLimit(props.p)) return;\n const v = BadRNG.random();\n let letter: string;\n if (v < 0.5) {\n letter = \"H\";\n } else {\n letter = \"T\";\n }\n const correct: boolean = guess === letter;\n\n setResult(\n \n \n {letter}\n \n ,\n );\n setStatus(correct ? \" win!\" : \"lose!\");\n setPlayLock(true);\n\n setTimeout(() => setPlayLock(false), 250);\n if (correct) {\n win(props.p, investment);\n } else {\n win(props.p, -investment);\n }\n if (reachedLimit(props.p)) return;\n }\n\n return (\n <>\n Result: {result}\n \n \n \n \n \n ),\n }}\n />\n \n {status}\n \n );\n}\n","import React, { useState, useEffect } from \"react\";\n\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { Money } from \"../ui/React/Money\";\nimport { win, reachedLimit } from \"./Game\";\nimport { WHRNG } from \"./RNG\";\nimport { trusted } from \"./utils\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport TextField from \"@mui/material/TextField\";\n\ntype IProps = {\n p: IPlayer;\n};\n\nconst minPlay = 0;\nconst maxPlay = 1e7;\n\nfunction isRed(n: number): boolean {\n return [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36].includes(n);\n}\n\ntype Strategy = {\n match: (n: number) => boolean;\n payout: number;\n};\n\nconst redNumbers: number[] = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36];\n\nconst strategies: {\n Red: Strategy;\n Black: Strategy;\n Odd: Strategy;\n Even: Strategy;\n High: Strategy;\n Low: Strategy;\n Third1: Strategy;\n Third2: Strategy;\n Third3: Strategy;\n} = {\n Red: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return redNumbers.includes(n);\n },\n payout: 1,\n },\n Black: {\n match: (n: number): boolean => {\n return !redNumbers.includes(n);\n },\n payout: 1,\n },\n Odd: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n % 2 === 1;\n },\n payout: 1,\n },\n Even: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n % 2 === 0;\n },\n payout: 1,\n },\n High: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n > 18;\n },\n payout: 1,\n },\n Low: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n < 19;\n },\n payout: 1,\n },\n Third1: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n <= 12;\n },\n payout: 2,\n },\n Third2: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n >= 13 && n <= 24;\n },\n payout: 2,\n },\n Third3: {\n match: (n: number): boolean => {\n if (n === 0) return false;\n return n >= 25;\n },\n payout: 2,\n },\n};\n\nfunction Single(s: number): Strategy {\n return {\n match: (n: number): boolean => {\n return s === n;\n },\n payout: 36,\n };\n}\n\nexport function Roulette(props: IProps): React.ReactElement {\n const [rng] = useState(new WHRNG(new Date().getTime()));\n const [investment, setInvestment] = useState(1000);\n const [canPlay, setCanPlay] = useState(true);\n const [status, setStatus] = useState(\"waiting\");\n const [n, setN] = useState(0);\n const [lock, setLock] = useState(true);\n const [strategy, setStrategy] = useState({\n payout: 0,\n match: (): boolean => {\n return false;\n },\n });\n\n useEffect(() => {\n const i = window.setInterval(step, 50);\n return () => clearInterval(i);\n });\n\n function step(): void {\n if (!lock) {\n setN(Math.floor(Math.random() * 37));\n }\n }\n\n function updateInvestment(e: React.ChangeEvent): void {\n let investment: number = parseInt(e.currentTarget.value);\n if (isNaN(investment)) {\n investment = minPlay;\n }\n if (investment > maxPlay) {\n investment = maxPlay;\n }\n if (investment < minPlay) {\n investment = minPlay;\n }\n setInvestment(investment);\n }\n\n function currentNumber(): string {\n if (n === 0) return \"0\";\n const color = isRed(n) ? \"R\" : \"B\";\n return `${n}${color}`;\n }\n\n function play(s: Strategy): void {\n if (reachedLimit(props.p)) return;\n\n setCanPlay(false);\n setLock(false);\n setStatus(\"playing\");\n setStrategy(s);\n\n setTimeout(() => {\n let n = Math.floor(rng.random() * 37);\n let status = <>;\n let gain = 0;\n let playerWin = strategy.match(n);\n // oh yeah, the house straight up cheats. Try finding the seed now!\n if (playerWin && Math.random() > 0.9) {\n playerWin = false;\n while (strategy.match(n)) {\n n = (n + 1) % 36;\n }\n }\n if (playerWin) {\n gain = investment * strategy.payout;\n status = (\n <>\n won \n \n );\n } else {\n gain = -investment;\n status = (\n <>\n lost \n \n );\n }\n win(props.p, gain);\n\n setCanPlay(true);\n setLock(true);\n setStatus(status);\n setN(n);\n\n reachedLimit(props.p);\n }, 1600);\n }\n\n return (\n <>\n {currentNumber()}\n \n {status}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n \n \n \n \n
    \n \n \n \n \n \n \n \n \n \n \n \n
    \n \n
    \n \n );\n}\n","import React, { useState, useEffect } from \"react\";\n\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { Money } from \"../ui/React/Money\";\nimport { WHRNG } from \"./RNG\";\nimport { win, reachedLimit } from \"./Game\";\nimport { trusted } from \"./utils\";\nimport Typography from \"@mui/material/Typography\";\nimport TextField from \"@mui/material/TextField\";\nimport Button from \"@mui/material/Button\";\n\ntype IProps = {\n p: IPlayer;\n};\n\n// statically shuffled array of symbols.\nconst symbols = [\n \"D\",\n \"C\",\n \"$\",\n \"?\",\n \"♥\",\n \"A\",\n \"C\",\n \"B\",\n \"C\",\n \"E\",\n \"B\",\n \"E\",\n \"C\",\n \"*\",\n \"D\",\n \"♥\",\n \"B\",\n \"A\",\n \"A\",\n \"A\",\n \"C\",\n \"A\",\n \"D\",\n \"B\",\n \"E\",\n \"?\",\n \"D\",\n \"*\",\n \"@\",\n \"♥\",\n \"B\",\n \"E\",\n \"?\",\n];\n\nfunction getPayout(s: string, n: number): number {\n switch (s) {\n case \"$\":\n return [20, 200, 1000][n];\n case \"@\":\n return [8, 80, 400][n];\n case \"♥\":\n case \"?\":\n return [6, 20, 150][n];\n case \"D\":\n case \"E\":\n return [1, 8, 30][n];\n default:\n return [1, 5, 20][n];\n }\n}\n\nconst payLines = [\n // lines\n [\n [0, 0],\n [0, 1],\n [0, 2],\n [0, 3],\n [0, 4],\n ],\n [\n [1, 0],\n [1, 1],\n [1, 2],\n [1, 3],\n [1, 4],\n ],\n [\n [2, 0],\n [2, 1],\n [2, 2],\n [2, 3],\n [2, 4],\n ],\n\n // Vs\n [\n [2, 0],\n [1, 1],\n [0, 2],\n [1, 3],\n [2, 4],\n ],\n [\n [0, 0],\n [1, 1],\n [2, 2],\n [1, 3],\n [0, 4],\n ],\n\n // rest\n [\n [0, 0],\n [1, 1],\n [1, 2],\n [1, 3],\n [0, 4],\n ],\n [\n [2, 0],\n [1, 1],\n [1, 2],\n [1, 3],\n [2, 4],\n ],\n [\n [1, 0],\n [0, 1],\n [0, 2],\n [0, 3],\n [1, 4],\n ],\n [\n [1, 0],\n [2, 1],\n [2, 2],\n [2, 3],\n [1, 4],\n ],\n];\n\nconst minPlay = 0;\nconst maxPlay = 1e6;\n\nexport function SlotMachine(props: IProps): React.ReactElement {\n const [rng] = useState(new WHRNG(props.p.totalPlaytime));\n const [index, setIndex] = useState([0, 0, 0, 0, 0]);\n const [locks, setLocks] = useState([0, 0, 0, 0, 0]);\n const [investment, setInvestment] = useState(1000);\n const [canPlay, setCanPlay] = useState(true);\n const [status, setStatus] = useState(\"waiting\");\n\n useEffect(() => {\n const i = window.setInterval(step, 50);\n return () => clearInterval(i);\n });\n\n function step(): void {\n let stoppedOne = false;\n const copy = index.slice();\n for (const i in copy) {\n if (copy[i] === locks[i] && !stoppedOne) continue;\n copy[i] = (copy[i] + 1) % symbols.length;\n stoppedOne = true;\n }\n\n setIndex(copy);\n\n if (stoppedOne && copy.every((e, i) => e === locks[i])) {\n checkWinnings();\n }\n }\n\n function getTable(): string[][] {\n return [\n [\n symbols[(index[0] + symbols.length - 1) % symbols.length],\n symbols[(index[1] + symbols.length - 1) % symbols.length],\n symbols[(index[2] + symbols.length - 1) % symbols.length],\n symbols[(index[3] + symbols.length - 1) % symbols.length],\n symbols[(index[4] + symbols.length - 1) % symbols.length],\n ],\n [symbols[index[0]], symbols[index[1]], symbols[index[2]], symbols[index[3]], symbols[index[4]]],\n [\n symbols[(index[0] + 1) % symbols.length],\n symbols[(index[1] + 1) % symbols.length],\n symbols[(index[2] + 1) % symbols.length],\n symbols[(index[3] + 1) % symbols.length],\n symbols[(index[4] + 1) % symbols.length],\n ],\n ];\n }\n\n function play(): void {\n if (reachedLimit(props.p)) return;\n setStatus(\"playing\");\n win(props.p, -investment);\n if (!canPlay) return;\n unlock();\n setTimeout(lock, rng.random() * 2000 + 1000);\n }\n\n function lock(): void {\n setLocks([\n Math.floor(rng.random() * symbols.length),\n Math.floor(rng.random() * symbols.length),\n Math.floor(rng.random() * symbols.length),\n Math.floor(rng.random() * symbols.length),\n Math.floor(rng.random() * symbols.length),\n ]);\n }\n\n function checkWinnings(): void {\n const t = getTable();\n const getPaylineData = function (payline: number[][]): string[] {\n const data = [];\n for (const point of payline) {\n data.push(t[point[0]][point[1]]);\n }\n return data;\n };\n\n const countSequence = function (data: string[]): number {\n let count = 1;\n for (let i = 1; i < data.length; i++) {\n if (data[i] !== data[i - 1]) break;\n count++;\n }\n\n return count;\n };\n\n let gains = -investment;\n for (const payline of payLines) {\n const data = getPaylineData(payline);\n const count = countSequence(data);\n if (count < 3) continue;\n const payout = getPayout(data[0], count - 3);\n gains += investment * payout;\n win(props.p, investment * payout);\n }\n\n setStatus(\n <>\n {gains > 0 ? \"gained\" : \"lost\"} \n ,\n );\n setCanPlay(true);\n if (reachedLimit(props.p)) return;\n }\n\n function unlock(): void {\n setLocks([-1, -1, -1, -1, -1]);\n setCanPlay(false);\n }\n\n function updateInvestment(e: React.ChangeEvent): void {\n let investment: number = parseInt(e.currentTarget.value);\n if (isNaN(investment)) {\n investment = minPlay;\n }\n if (investment > maxPlay) {\n investment = maxPlay;\n }\n if (investment < minPlay) {\n investment = minPlay;\n }\n setInvestment(investment);\n }\n\n const t = getTable();\n // prettier-ignore\n return (\n <>\n+———————————————————————+\n| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |\n| | | | | | | |\n| | {symbols[index[0]]} | {symbols[index[1]]} | {symbols[index[2]]} | {symbols[index[3]]} | {symbols[index[4]]} | |\n| | | | | | | |\n| | {symbols[(index[0]+1)%symbols.length]} | {symbols[(index[1]+1)%symbols.length]} | {symbols[(index[2]+1)%symbols.length]} | {symbols[(index[3]+1)%symbols.length]} | {symbols[(index[4]+1)%symbols.length]} | |\n+———————————————————————+\n Spin!)}}\n />\n \n {status}\n Pay lines\n\n----- ····· ·····\n····· ----- ·····\n····· ····· -----\n
    \n\n··^·· \\···/ \\···/\n·/·\\· ·\\·/· ·---·\n/···\\ ··v·· ·····\n
    \n\n····· ·---· ·····\n·---· /···\\ \\···/\n/···\\ ····· ·---·\n \n );\n}\n\n// https://felgo.com/doc/how-to-make-a-slot-game-tutorial/\n","import React, { useEffect, useState } from \"react\";\n\nfunction replace(str: string, i: number, char: string): string {\n return str.substring(0, i) + char + str.substring(i + 1);\n}\n\ninterface IProps {\n content: string;\n}\n\nfunction randomize(char: string): string {\n const randFrom = (str: string): string => str[Math.floor(Math.random() * str.length)];\n const classes = [\"abcdefghijklmnopqrstuvwxyz\", \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", \"1234567890\", \" _\", \"()[]{}<>\"];\n const other = `!@#$%^&*()_+|\\\\';\"/.,?\\`~`;\n\n for (const c of classes) {\n if (c.includes(char)) return randFrom(c);\n }\n\n return randFrom(other);\n}\n\nexport function CorruptableText(props: IProps): JSX.Element {\n const [content, setContent] = useState(props.content);\n\n useEffect(() => {\n let counter = 5;\n const id = setInterval(() => {\n counter--;\n if (counter > 0) return;\n counter = Math.random() * 5;\n const index = Math.random() * content.length;\n const letter = content.charAt(index);\n setContent(replace(content, index, randomize(letter)));\n setTimeout(() => {\n setContent(content);\n }, 50);\n }, 100);\n\n return () => {\n clearInterval(id);\n };\n }, []);\n\n return {content};\n}\n","/**\n * React Component for displaying a City's UI.\n * This UI shows all of the available locations in the city, and lets the player\n * visit those locations\n */\nimport * as React from \"react\";\n\nimport { City } from \"../City\";\nimport { Cities } from \"../Cities\";\nimport { LocationName } from \"../data/LocationNames\";\nimport { Locations } from \"../Locations\";\nimport { Location } from \"../Location\";\nimport { Settings } from \"../../Settings/Settings\";\n\nimport { use } from \"../../ui/Context\";\nimport { IRouter } from \"../../ui/Router\";\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\n\ntype IProps = {\n city: City;\n};\n\nfunction toLocation(router: IRouter, location: Location): void {\n if (location.name === LocationName.TravelAgency) {\n router.toTravel();\n } else if (location.name === LocationName.WorldStockExchange) {\n router.toStockMarket();\n } else {\n router.toLocation(location);\n }\n}\n\nfunction LocationLetter(location: Location): React.ReactElement {\n const router = use.Router();\n if (!location) return *;\n return (\n toLocation(router, location)}\n >\n X\n \n );\n}\n\nfunction ASCIICity(props: IProps): React.ReactElement {\n const locationLettersRegex = /[A-Z]/g;\n const letterMap: any = {\n A: 0,\n B: 1,\n C: 2,\n D: 3,\n E: 4,\n F: 5,\n G: 6,\n H: 7,\n I: 8,\n J: 9,\n K: 10,\n L: 11,\n M: 12,\n N: 13,\n O: 14,\n P: 15,\n Q: 16,\n R: 17,\n S: 18,\n T: 19,\n U: 20,\n V: 21,\n W: 22,\n X: 23,\n Y: 24,\n Z: 25,\n };\n\n const lineElems = (s: string): JSX.Element[] => {\n const elems: any[] = [];\n const matches: any[] = [];\n let match: any;\n while ((match = locationLettersRegex.exec(s)) !== null) {\n matches.push(match);\n }\n if (matches.length === 0) {\n elems.push(s);\n return elems;\n }\n\n for (let i = 0; i < matches.length; i++) {\n const startI = i === 0 ? 0 : matches[i - 1].index + 1;\n const endI = matches[i].index;\n elems.push(s.slice(startI, endI));\n const locationI = letterMap[s[matches[i].index]];\n elems.push(LocationLetter(Locations[props.city.locations[locationI]]));\n }\n elems.push(s.slice(matches[matches.length - 1].index + 1));\n return elems;\n };\n\n const elems: JSX.Element[] = [];\n const lines = props.city.asciiArt.split(\"\\n\");\n for (const i in lines) {\n elems.push(
    {lineElems(lines[i])}
    );\n }\n\n return <>{elems};\n}\n\nfunction ListCity(props: IProps): React.ReactElement {\n const router = use.Router();\n const locationButtons = props.city.locations.map((locName) => {\n return (\n \n \n
    \n
    \n );\n });\n\n return <>{locationButtons};\n}\n\nexport function LocationCity(): React.ReactElement {\n const player = use.Player();\n const city = Cities[player.city];\n return (\n <>\n {city.name}\n {Settings.DisableASCIIArt ? : }\n \n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport { use } from \"../../ui/Context\";\nimport { getAvailableCreatePrograms } from \"../ProgramHelpers\";\n\nimport { Tooltip, Typography } from \"@mui/material\";\nimport Button from \"@mui/material/Button\";\n\nexport function ProgramsRoot(): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n return (\n <>\n Create program\n \n This page displays any programs that you are able to create. Writing the code for a program takes time, which\n can vary based on how complex the program is. If you are working on creating a program you can cancel at any\n time. Your progress will be saved and you can continue later.\n \n\n {getAvailableCreatePrograms(player).map((program) => {\n const create = program.create;\n if (create === null) return <>;\n\n return (\n \n \n {\n if (!event.isTrusted) return;\n player.startCreateProgramWork(router, program.name, create.time, create.level);\n }}\n >\n {program.name}\n \n \n
    \n
    \n );\n })}\n \n );\n}\n","import React, { useState, useEffect, useRef } from \"react\";\nimport Editor from \"@monaco-editor/react\";\nimport * as monaco from \"monaco-editor\";\ntype IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;\nimport { OptionsModal } from \"./OptionsModal\";\nimport { Options } from \"./Options\";\nimport { js_beautify as beautifyCode } from \"js-beautify\";\nimport { isValidFilePath } from \"../../Terminal/DirectoryHelpers\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { IRouter } from \"../../ui/Router\";\nimport { dialogBoxCreate } from \"../../ui/React/DialogBox\";\nimport { isScriptFilename } from \"../../Script/isScriptFilename\";\nimport { Script } from \"../../Script/Script\";\nimport { TextFile } from \"../../TextFile\";\nimport { calculateRamUsage } from \"../../Script/RamCalculations\";\nimport { RamCalculationErrorCode } from \"../../Script/RamCalculationErrorCodes\";\nimport { numeralWrapper } from \"../../ui/numeralFormat\";\nimport { CursorPositions } from \"../../ScriptEditor/CursorPositions\";\nimport { libSource } from \"../NetscriptDefinitions\";\nimport { NetscriptFunctions } from \"../../NetscriptFunctions\";\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\nimport { Settings } from \"../../Settings/Settings\";\nimport { iTutorialNextStep, ITutorial, iTutorialSteps } from \"../../InteractiveTutorial\";\n\nimport Button from \"@mui/material/Button\";\nimport Typography from \"@mui/material/Typography\";\nimport Link from \"@mui/material/Link\";\nimport Box from \"@mui/material/Box\";\nimport TextField from \"@mui/material/TextField\";\nimport IconButton from \"@mui/material/IconButton\";\nimport SettingsIcon from \"@mui/icons-material/Settings\";\n\nlet symbols: string[] = [];\n(function () {\n const ns = NetscriptFunctions({} as WorkerScript);\n\n function populate(ns: any): string[] {\n let symbols: string[] = [];\n const keys = Object.keys(ns);\n for (const key of keys) {\n if (typeof ns[key] === \"object\") {\n symbols.push(key);\n symbols = symbols.concat(populate(ns[key]));\n }\n if (typeof ns[key] === \"function\") {\n symbols.push(key);\n }\n }\n return symbols;\n }\n symbols = populate(ns);\n\n const exclude = [\"heart\", \"break\", \"exploit\", \"bypass\", \"corporation\"];\n symbols = symbols.filter((symbol: string) => !exclude.includes(symbol));\n})();\n\ninterface IProps {\n filename: string;\n code: string;\n player: IPlayer;\n router: IRouter;\n}\n\n/*\n\n*/\n\n// How to load function definition in monaco\n// https://github.com/Microsoft/monaco-editor/issues/1415\n// https://microsoft.github.io/monaco-editor/api/modules/monaco.languages.html\n// https://www.npmjs.com/package/@monaco-editor/react#development-playground\n// https://microsoft.github.io/monaco-editor/playground.html#extending-language-services-custom-languages\n// https://github.com/threehams/typescript-error-guide/blob/master/stories/components/Editor.tsx#L11-L39\n\n// These variables are used to reload a script when it's clicked on. Because we\n// won't have references to the old script.\nlet lastFilename = \"\";\nlet lastCode = \"\";\nlet lastPosition: monaco.Position | null = null;\n\nexport function Root(props: IProps): React.ReactElement {\n const editorRef = useRef(null);\n const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename);\n const [code, setCode] = useState(props.filename ? props.code : lastCode);\n const [ram, setRAM] = useState(\"RAM: ???\");\n const [optionsOpen, setOptionsOpen] = useState(false);\n const [options, setOptions] = useState({\n theme: Settings.MonacoTheme,\n insertSpaces: Settings.MonacoInsertSpaces,\n });\n\n // store the last known state in case we need to restart without nano.\n useEffect(() => {\n if (props.filename === undefined) return;\n lastFilename = props.filename;\n lastCode = props.code;\n lastPosition = null;\n }, []);\n\n function save(): void {\n if (editorRef.current !== null) {\n const position = editorRef.current.getPosition();\n if (position !== null) {\n CursorPositions.saveCursor(filename, {\n row: position.lineNumber,\n column: position.column,\n });\n }\n }\n lastPosition = null;\n\n // this is duplicate code with saving later.\n if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {\n //Make sure filename + code properly follow tutorial\n if (filename !== \"n00dles.script\") {\n dialogBoxCreate(\"Leave the script name as 'n00dles'!\");\n return;\n }\n if (code.replace(/\\s/g, \"\").indexOf(\"while(true){hack('n00dles');}\") == -1) {\n dialogBoxCreate(\"Please copy and paste the code from the tutorial!\");\n return;\n }\n\n //Save the script\n const server = props.player.getCurrentServer();\n if (server === null) throw new Error(\"Server should not be null but it is.\");\n let found = false;\n for (let i = 0; i < server.scripts.length; i++) {\n if (filename == server.scripts[i].filename) {\n server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);\n found = true;\n }\n }\n\n if (!found) {\n const script = new Script();\n script.saveScript(filename, code, props.player.currentServer, server.scripts);\n server.scripts.push(script);\n }\n\n iTutorialNextStep();\n\n props.router.toTerminal();\n return;\n }\n\n if (filename == \"\") {\n dialogBoxCreate(\"You must specify a filename!\");\n return;\n }\n\n if (!isValidFilePath(filename)) {\n dialogBoxCreate(\n \"Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.\",\n );\n return;\n }\n\n const server = props.player.getCurrentServer();\n if (server === null) throw new Error(\"Server should not be null but it is.\");\n if (isScriptFilename(filename)) {\n //If the current script already exists on the server, overwrite it\n for (let i = 0; i < server.scripts.length; i++) {\n if (filename == server.scripts[i].filename) {\n server.scripts[i].saveScript(filename, code, props.player.currentServer, server.scripts);\n props.router.toTerminal();\n return;\n }\n }\n\n //If the current script does NOT exist, create a new one\n const script = new Script();\n script.saveScript(filename, code, props.player.currentServer, server.scripts);\n server.scripts.push(script);\n } else if (filename.endsWith(\".txt\")) {\n for (let i = 0; i < server.textFiles.length; ++i) {\n if (server.textFiles[i].fn === filename) {\n server.textFiles[i].write(code);\n props.router.toTerminal();\n return;\n }\n }\n const textFile = new TextFile(filename, code);\n server.textFiles.push(textFile);\n } else {\n dialogBoxCreate(\"Invalid filename. Must be either a script (.script, .js, or .ns) or \" + \" or text file (.txt)\");\n return;\n }\n props.router.toTerminal();\n }\n\n function beautify(): void {\n if (editorRef.current === null) return;\n const pretty = beautifyCode(code, {\n indent_with_tabs: !options.insertSpaces,\n indent_size: 4,\n brace_style: \"preserve-inline\",\n });\n editorRef.current.setValue(pretty);\n }\n\n function onFilenameChange(event: React.ChangeEvent): void {\n lastFilename = filename;\n setFilename(event.target.value);\n }\n\n function updateCode(newCode?: string): void {\n if (newCode === undefined) return;\n lastCode = newCode;\n if (editorRef.current !== null) {\n lastPosition = editorRef.current.getPosition();\n }\n setCode(newCode);\n }\n\n async function updateRAM(): Promise {\n const codeCopy = code + \"\";\n const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts);\n if (ramUsage > 0) {\n setRAM(\"RAM: \" + numeralWrapper.formatRAM(ramUsage));\n return;\n }\n switch (ramUsage) {\n case RamCalculationErrorCode.ImportError: {\n setRAM(\"RAM: Import Error\");\n break;\n }\n case RamCalculationErrorCode.URLImportError: {\n setRAM(\"RAM: HTTP Import Error\");\n break;\n }\n case RamCalculationErrorCode.SyntaxError:\n default: {\n setRAM(\"RAM: Syntax Error\");\n break;\n }\n }\n return new Promise(() => undefined);\n }\n\n useEffect(() => {\n const id = setInterval(updateRAM, 1000);\n return () => clearInterval(id);\n }, [code]);\n\n useEffect(() => {\n function maybeSave(event: KeyboardEvent): void {\n if (Settings.DisableHotkeys) return;\n //Ctrl + b\n if (event.keyCode == 66 && (event.ctrlKey || event.metaKey)) {\n event.preventDefault();\n save();\n }\n }\n document.addEventListener(\"keydown\", maybeSave);\n return () => document.removeEventListener(\"keydown\", maybeSave);\n });\n\n function onMount(editor: IStandaloneCodeEditor): void {\n editorRef.current = editor;\n if (editorRef.current === null) return;\n const position = CursorPositions.getCursor(filename);\n if (position.row !== -1)\n editorRef.current.setPosition({\n lineNumber: position.row,\n column: position.column,\n });\n else if (lastPosition !== null)\n editorRef.current.setPosition({\n lineNumber: lastPosition.lineNumber,\n column: lastPosition.column + 1,\n });\n editorRef.current.focus();\n }\n\n function beforeMount(monaco: any): void {\n monaco.languages.registerCompletionItemProvider(\"javascript\", {\n provideCompletionItems: () => {\n const suggestions = [];\n for (const symbol of symbols) {\n suggestions.push({\n label: symbol,\n kind: monaco.languages.CompletionItemKind.Function,\n insertText: symbol,\n insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,\n });\n }\n return { suggestions: suggestions };\n },\n });\n monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, \"netscript.d.ts\");\n monaco.languages.typescript.typescriptDefaults.addExtraLib(libSource, \"netscript.d.ts\");\n }\n\n return (\n <>\n \n Script name: \n \n setOptionsOpen(true)}>\n <>\n \n options\n \n \n \n Loading script editor!}\n height=\"90%\"\n defaultLanguage=\"javascript\"\n defaultValue={code}\n onChange={updateCode}\n theme={options.theme}\n options={options}\n />\n \n \n {ram}\n \n \n Netscript Documentation\n \n \n setOptionsOpen(false)}\n options={{\n theme: Settings.MonacoTheme,\n insertSpaces: Settings.MonacoInsertSpaces,\n }}\n save={(options: Options) => {\n setOptions(options);\n Settings.MonacoTheme = options.theme;\n Settings.MonacoInsertSpaces = options.insertSpaces;\n }}\n />\n \n );\n}\n","import React, { useState } from \"react\";\nimport { Options } from \"./Options\";\nimport { Modal } from \"../../ui/React/Modal\";\n\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\nimport Select from \"@mui/material/Select\";\nimport Switch from \"@mui/material/Switch\";\nimport MenuItem from \"@mui/material/MenuItem\";\n\ninterface IProps {\n options: Options;\n save: (options: Options) => void;\n onClose: () => void;\n open: boolean;\n}\n\nexport function OptionsModal(props: IProps): React.ReactElement {\n const [theme, setTheme] = useState(props.options.theme);\n const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces);\n\n function save(): void {\n props.save({\n theme: theme,\n insertSpaces: insertSpaces,\n });\n props.onClose();\n }\n\n return (\n \n \n Theme: \n \n \n\n \n Use whitespace over tabs: \n setInsertSpaces(event.target.checked)} checked={insertSpaces} />\n \n
    \n \n
    \n );\n}\n","import { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { Milestones } from \"../Milestones\";\nimport { Milestone } from \"../Milestone\";\nimport * as React from \"react\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Box from \"@mui/material/Box\";\n\ninterface IProps {\n player: IPlayer;\n}\n\nfunction highestMilestone(p: IPlayer, milestones: Milestone[]): number {\n let n = -1;\n for (let i = 0; i < milestones.length; i++) {\n if (milestones[i].fulfilled(p)) n = i;\n }\n\n return n;\n}\n\nexport function MilestonesRoot(props: IProps): JSX.Element {\n const n = highestMilestone(props.player, Milestones);\n const milestones = Milestones.map((milestone: Milestone, i: number) => {\n if (i <= n + 1) {\n return (\n \n [{milestone.fulfilled(props.player) ? \"x\" : \" \"}] {milestone.title}\n \n );\n }\n });\n return (\n <>\n Milestones\n \n \n Milestones don't reward you for completing them. They are here to guide you if you're lost. They will reset\n when you install Augmentations.\n \n
    \n\n Completing fl1ght.exe\n {milestones}\n
    \n \n );\n}\n","import React, { useState, useEffect, useRef } from \"react\";\nimport Typography from \"@mui/material/Typography\";\nimport List from \"@mui/material/List\";\nimport ListItem from \"@mui/material/ListItem\";\nimport { Link as MuiLink } from \"@mui/material\";\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport Box from \"@mui/material/Box\";\nimport { ITerminal, Output, Link } from \"../ITerminal\";\nimport { IRouter } from \"../../ui/Router\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { TerminalInput } from \"./TerminalInput\";\nimport { TerminalEvents, TerminalClearEvents } from \"../TerminalEvents\";\nimport { BitFlumeModal } from \"../../BitNode/ui/BitFlumeModal\";\nimport { CodingContractModal } from \"../../ui/React/CodingContractModal\";\n\nimport _ from \"lodash\";\n\ninterface IActionTimerProps {\n terminal: ITerminal;\n}\n\nfunction ActionTimer({ terminal }: IActionTimerProps): React.ReactElement {\n return (\n \n {terminal.getProgressText()}\n \n );\n}\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n nopadding: {\n padding: theme.spacing(0),\n },\n preformatted: {\n whiteSpace: \"pre-wrap\",\n overflowWrap: \"anywhere\",\n margin: theme.spacing(0),\n },\n list: {\n padding: theme.spacing(0),\n height: \"100%\",\n },\n }),\n);\n\ninterface IProps {\n terminal: ITerminal;\n router: IRouter;\n player: IPlayer;\n}\n\nexport function TerminalRoot({ terminal, router, player }: IProps): React.ReactElement {\n const scrollHook = useRef(null);\n const setRerender = useState(0)[1];\n const [key, setKey] = useState(0);\n function rerender(): void {\n setRerender((old) => old + 1);\n }\n\n function clear(): void {\n setKey((key) => key + 1);\n }\n\n useEffect(() => TerminalEvents.subscribe(_.debounce(async () => rerender(), 25, { maxWait: 50 })), []);\n useEffect(() => TerminalClearEvents.subscribe(_.debounce(async () => clear(), 25, { maxWait: 50 })), []);\n\n function doScroll(): void {\n const hook = scrollHook.current;\n if (hook !== null) {\n setTimeout(() => hook.scrollIntoView(true), 50);\n }\n }\n\n doScroll();\n\n useEffect(() => {\n setTimeout(doScroll, 50);\n }, []);\n\n const classes = useStyles();\n return (\n <>\n \n \n {terminal.outputHistory.map((item, i) => {\n if (item instanceof Output)\n return (\n \n \n {item.text}\n \n \n );\n if (item instanceof Link)\n return (\n \n {item.dashes}> \n terminal.connectToServer(player, item.hostname)}\n >\n {item.hostname}\n \n \n );\n })}\n\n {terminal.action !== null && (\n \n {\" \"}\n \n )}\n \n
    \n
    \n \n \n \n \n \n \n );\n}\n","import React, { useState, useEffect, useRef } from \"react\";\nimport Typography from \"@mui/material/Typography\";\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport TextField from \"@mui/material/TextField\";\nimport Paper from \"@mui/material/Paper\";\n\nimport { KEY } from \"../../utils/helpers/keyCodes\";\nimport { ITerminal } from \"../ITerminal\";\nimport { IRouter } from \"../../ui/Router\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { determineAllPossibilitiesForTabCompletion } from \"../determineAllPossibilitiesForTabCompletion\";\nimport { tabCompletion } from \"../tabCompletion\";\nimport { Settings } from \"../../Settings/Settings\";\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n textfield: {\n margin: theme.spacing(0),\n width: \"100%\",\n },\n input: {\n backgroundColor: \"#000\",\n },\n nopadding: {\n padding: theme.spacing(0),\n },\n preformatted: {\n whiteSpace: \"pre-wrap\",\n margin: theme.spacing(0),\n },\n list: {\n padding: theme.spacing(0),\n height: \"100%\",\n },\n }),\n);\n\ninterface IProps {\n terminal: ITerminal;\n router: IRouter;\n player: IPlayer;\n}\n// Save command in case we de-load this screen.\nlet command = \"\";\n\nexport function TerminalInput({ terminal, router, player }: IProps): React.ReactElement {\n const terminalInput = useRef(null);\n\n const [value, setValue] = useState(command);\n const [possibilities, setPossibilities] = useState([]);\n const classes = useStyles();\n\n function saveValue(value: string): void {\n command = value;\n setValue(value);\n }\n\n function handleValueChange(event: React.ChangeEvent): void {\n saveValue(event.target.value);\n setPossibilities([]);\n }\n\n function modifyInput(mod: string): void {\n const ref = terminalInput.current;\n if (!ref) return;\n const inputLength = value.length;\n const start = ref.selectionStart;\n if (start === null) return;\n const inputText = ref.value;\n\n switch (mod.toLowerCase()) {\n case \"backspace\":\n if (start > 0 && start <= inputLength + 1) {\n saveValue(inputText.substr(0, start - 1) + inputText.substr(start));\n }\n break;\n case \"deletewordbefore\": // Delete rest of word before the cursor\n for (let delStart = start - 1; delStart > 0; --delStart) {\n if (inputText.charAt(delStart) === \" \") {\n saveValue(inputText.substr(0, delStart) + inputText.substr(start));\n return;\n }\n }\n break;\n case \"deletewordafter\": // Delete rest of word after the cursor\n for (let delStart = start + 1; delStart <= value.length + 1; ++delStart) {\n if (inputText.charAt(delStart) === \" \") {\n saveValue(inputText.substr(0, start) + inputText.substr(delStart));\n return;\n }\n }\n break;\n case \"clearafter\": // Deletes everything after cursor\n break;\n case \"clearbefore:\": // Deleetes everything before cursor\n break;\n }\n }\n\n function moveTextCursor(loc: string): void {\n const ref = terminalInput.current;\n if (!ref) return;\n const inputLength = value.length;\n const start = ref.selectionStart;\n if (start === null) return;\n\n switch (loc.toLowerCase()) {\n case \"home\":\n ref.setSelectionRange(0, 0);\n break;\n case \"end\":\n ref.setSelectionRange(inputLength, inputLength);\n break;\n case \"prevchar\":\n if (start > 0) {\n ref.setSelectionRange(start - 1, start - 1);\n }\n break;\n case \"prevword\":\n for (let i = start - 2; i >= 0; --i) {\n if (ref.value.charAt(i) === \" \") {\n ref.setSelectionRange(i + 1, i + 1);\n return;\n }\n }\n ref.setSelectionRange(0, 0);\n break;\n case \"nextchar\":\n ref.setSelectionRange(start + 1, start + 1);\n break;\n case \"nextword\":\n for (let i = start + 1; i <= inputLength; ++i) {\n if (ref.value.charAt(i) === \" \") {\n ref.setSelectionRange(i, i);\n return;\n }\n }\n ref.setSelectionRange(inputLength, inputLength);\n break;\n default:\n console.warn(\"Invalid loc argument in Terminal.moveTextCursor()\");\n break;\n }\n }\n\n // Catch all key inputs and redirect them to the terminal.\n useEffect(() => {\n function keyDown(this: Document, event: KeyboardEvent): void {\n if (terminal.contractOpen) return;\n if (terminal.action !== null && event.keyCode === KEY.C && event.ctrlKey) {\n terminal.finishAction(router, player, true);\n return;\n }\n const ref = terminalInput.current;\n if (event.ctrlKey || event.metaKey) return;\n if (event.keyCode === KEY.C && (event.ctrlKey || event.metaKey)) return; // trying to copy\n\n if (ref) ref.focus();\n }\n document.addEventListener(\"keydown\", keyDown);\n return () => document.removeEventListener(\"keydown\", keyDown);\n });\n\n function onKeyDown(event: React.KeyboardEvent): void {\n // Run command.\n if (event.keyCode === KEY.ENTER && value !== \"\") {\n event.preventDefault();\n terminal.print(`[${player.getCurrentServer().hostname} ~${terminal.cwd()}]> ${value}`);\n terminal.executeCommands(router, player, value);\n saveValue(\"\");\n return;\n }\n\n // Autocomplete\n if (event.keyCode === KEY.TAB && value !== \"\") {\n event.preventDefault();\n\n let copy = value;\n const semiColonIndex = copy.lastIndexOf(\";\");\n if (semiColonIndex !== -1) {\n copy = copy.slice(semiColonIndex + 1);\n }\n\n copy = copy.trim();\n copy = copy.replace(/\\s\\s+/g, \" \");\n\n const commandArray = copy.split(\" \");\n let index = commandArray.length - 2;\n if (index < -1) {\n index = 0;\n }\n const allPos = determineAllPossibilitiesForTabCompletion(player, copy, index, terminal.cwd());\n if (allPos.length == 0) {\n return;\n }\n\n let arg = \"\";\n let command = \"\";\n if (commandArray.length == 0) {\n return;\n }\n if (commandArray.length == 1) {\n command = commandArray[0];\n } else if (commandArray.length == 2) {\n command = commandArray[0];\n arg = commandArray[1];\n } else if (commandArray.length == 3) {\n command = commandArray[0] + \" \" + commandArray[1];\n arg = commandArray[2];\n } else {\n arg = commandArray.pop() + \"\";\n command = commandArray.join(\" \");\n }\n\n const newValue = tabCompletion(command, arg, allPos, value);\n if (typeof newValue === \"string\" && newValue !== \"\") {\n saveValue(newValue);\n }\n if (Array.isArray(newValue)) {\n setPossibilities(newValue);\n }\n }\n\n // Clear screen.\n if (event.keyCode === KEY.L && event.ctrlKey) {\n event.preventDefault();\n terminal.clear();\n }\n\n // Select previous command.\n if (event.keyCode === KEY.UPARROW || (Settings.EnableBashHotkeys && event.keyCode === KEY.P && event.ctrlKey)) {\n if (Settings.EnableBashHotkeys) {\n event.preventDefault();\n }\n const i = terminal.commandHistoryIndex;\n const len = terminal.commandHistory.length;\n\n if (len == 0) {\n return;\n }\n if (i < 0 || i > len) {\n terminal.commandHistoryIndex = len;\n }\n\n if (i != 0) {\n --terminal.commandHistoryIndex;\n }\n const prevCommand = terminal.commandHistory[terminal.commandHistoryIndex];\n saveValue(prevCommand);\n const ref = terminalInput.current;\n if (ref) {\n setTimeout(function () {\n ref.selectionStart = ref.selectionEnd = 10000;\n }, 10);\n }\n }\n\n // Select next command\n if (event.keyCode === KEY.DOWNARROW || (Settings.EnableBashHotkeys && event.keyCode === KEY.M && event.ctrlKey)) {\n if (Settings.EnableBashHotkeys) {\n event.preventDefault();\n }\n const i = terminal.commandHistoryIndex;\n const len = terminal.commandHistory.length;\n\n if (len == 0) {\n return;\n }\n if (i < 0 || i > len) {\n terminal.commandHistoryIndex = len;\n }\n\n // Latest command, put nothing\n if (i == len || i == len - 1) {\n terminal.commandHistoryIndex = len;\n saveValue(\"\");\n } else {\n ++terminal.commandHistoryIndex;\n const prevCommand = terminal.commandHistory[terminal.commandHistoryIndex];\n saveValue(prevCommand);\n }\n }\n\n // Extra Bash Emulation Hotkeys, must be enabled through .fconf\n if (Settings.EnableBashHotkeys) {\n if (event.keyCode === KEY.A && event.ctrlKey) {\n event.preventDefault();\n moveTextCursor(\"home\");\n }\n\n if (event.keyCode === KEY.E && event.ctrlKey) {\n event.preventDefault();\n moveTextCursor(\"end\");\n }\n\n if (event.keyCode === KEY.B && event.ctrlKey) {\n event.preventDefault();\n moveTextCursor(\"prevchar\");\n }\n\n if (event.keyCode === KEY.B && event.altKey) {\n event.preventDefault();\n moveTextCursor(\"prevword\");\n }\n\n if (event.keyCode === KEY.F && event.ctrlKey) {\n event.preventDefault();\n moveTextCursor(\"nextchar\");\n }\n\n if (event.keyCode === KEY.F && event.altKey) {\n event.preventDefault();\n moveTextCursor(\"nextword\");\n }\n\n if ((event.keyCode === KEY.H || event.keyCode === KEY.D) && event.ctrlKey) {\n modifyInput(\"backspace\");\n event.preventDefault();\n }\n\n // TODO AFTER THIS:\n // alt + d deletes word after cursor\n // ^w deletes word before cursor\n // ^k clears line after cursor\n // ^u clears line before cursor\n }\n }\n\n return (\n <>\n {possibilities.length > 0 && (\n \n \n Possible autocomplete candidate:\n \n \n {possibilities.join(\" \")}\n \n \n )}\n \n \n [{player.getCurrentServer().hostname} ~{terminal.cwd()}]> \n \n \n ),\n spellCheck: false,\n onKeyDown: onKeyDown,\n }}\n >
    \n \n );\n}\n","import { evaluateDirectoryPath, getAllParentDirectories } from \"./DirectoryHelpers\";\nimport { getSubdirectories } from \"./DirectoryServerHelpers\";\n\nimport { Aliases, GlobalAliases } from \"../Alias\";\nimport { DarkWebItems } from \"../DarkWeb/DarkWebItems\";\nimport { Message } from \"../Message/Message\";\nimport { IPlayer } from \"../PersonObjects/IPlayer\";\nimport { AllServers } from \"../Server/AllServers\";\n\n// An array of all Terminal commands\nconst commands = [\n \"alias\",\n \"analyze\",\n \"backdoor\",\n \"cat\",\n \"cd\",\n \"check\",\n \"clear\",\n \"cls\",\n \"connect\",\n \"download\",\n \"expr\",\n \"free\",\n \"hack\",\n \"help\",\n \"home\",\n \"hostname\",\n \"ifconfig\",\n \"kill\",\n \"killall\",\n \"ls\",\n \"lscpu\",\n \"mem\",\n \"mv\",\n \"nano\",\n \"ps\",\n \"rm\",\n \"run\",\n \"scan\",\n \"scan-analyze\",\n \"scp\",\n \"sudov\",\n \"tail\",\n \"theme\",\n \"top\",\n];\n\nexport function determineAllPossibilitiesForTabCompletion(\n p: IPlayer,\n input: string,\n index: number,\n currPath = \"\",\n): string[] {\n let allPos: string[] = [];\n allPos = allPos.concat(Object.keys(GlobalAliases));\n const currServ = p.getCurrentServer();\n const homeComputer = p.getHomeComputer();\n\n let parentDirPath = \"\";\n let evaledParentDirPath: string | null = null;\n\n // Helper functions\n function addAllCodingContracts(): void {\n for (const cct of currServ.contracts) {\n allPos.push(cct.fn);\n }\n }\n\n function addAllLitFiles(): void {\n for (const file of currServ.messages) {\n if (!(file instanceof Message)) {\n allPos.push(file);\n }\n }\n }\n\n function addAllMessages(): void {\n for (const file of currServ.messages) {\n if (file instanceof Message) {\n allPos.push(file.filename);\n }\n }\n }\n\n function addAllPrograms(): void {\n for (const program of homeComputer.programs) {\n allPos.push(program);\n }\n }\n\n function addAllScripts(): void {\n for (const script of currServ.scripts) {\n const res = processFilepath(script.filename);\n if (res) {\n allPos.push(res);\n }\n }\n }\n\n function addAllTextFiles(): void {\n for (const txt of currServ.textFiles) {\n const res = processFilepath(txt.fn);\n if (res) {\n allPos.push(res);\n }\n }\n }\n\n function addAllDirectories(): void {\n // Directories are based on the currently evaluated path\n const subdirs = getSubdirectories(currServ, evaledParentDirPath == null ? \"/\" : evaledParentDirPath);\n\n for (let i = 0; i < subdirs.length; ++i) {\n const assembledDirPath = evaledParentDirPath == null ? subdirs[i] : evaledParentDirPath + subdirs[i];\n const res = processFilepath(assembledDirPath);\n if (res != null) {\n subdirs[i] = res;\n }\n }\n\n allPos = allPos.concat(subdirs);\n }\n\n // Convert from the real absolute path back to the original path used in the input\n function convertParentPath(filepath: string): string {\n if (parentDirPath == null || evaledParentDirPath == null) {\n console.warn(`convertParentPath() called when paths are null`);\n return filepath;\n }\n\n if (!filepath.startsWith(evaledParentDirPath)) {\n console.warn(\n `convertParentPath() called for invalid path. (filepath=${filepath}) (evaledParentDirPath=${evaledParentDirPath})`,\n );\n return filepath;\n }\n\n return parentDirPath + filepath.slice(evaledParentDirPath.length);\n }\n\n // Given an a full, absolute filepath, converts it to the proper value\n // for autocompletion purposes\n function processFilepath(filepath: string): string | null {\n if (evaledParentDirPath) {\n if (filepath.startsWith(evaledParentDirPath)) {\n return convertParentPath(filepath);\n }\n } else if (parentDirPath !== \"\") {\n // If the parent directory is the root directory, but we're not searching\n // it from the root directory, we have to add the original path\n let t_parentDirPath = parentDirPath;\n if (!t_parentDirPath.endsWith(\"/\")) {\n t_parentDirPath += \"/\";\n }\n return parentDirPath + filepath;\n } else {\n return filepath;\n }\n\n return null;\n }\n\n function isCommand(cmd: string): boolean {\n let t_cmd = cmd;\n if (!t_cmd.endsWith(\" \")) {\n t_cmd += \" \";\n }\n\n return input.startsWith(t_cmd);\n }\n\n /**\n * If the command starts with './' and the index == -1, then the user\n * has input ./partialexecutablename so autocomplete the script or program.\n * Put './' in front of each script/executable\n */\n if (isCommand(\"./\") && index == -1) {\n //All programs and scripts\n for (let i = 0; i < currServ.scripts.length; ++i) {\n allPos.push(\"./\" + currServ.scripts[i].filename);\n }\n\n //Programs are on home computer\n for (let i = 0; i < homeComputer.programs.length; ++i) {\n allPos.push(\"./\" + homeComputer.programs[i]);\n }\n return allPos;\n }\n\n // Autocomplete the command\n if (index === -1) {\n return commands.concat(Object.keys(Aliases)).concat(Object.keys(GlobalAliases));\n }\n\n // Since we're autocompleting an argument and not a command, the argument might\n // be a file/directory path. We have to account for that when autocompleting\n const commandArray = input.split(\" \");\n if (commandArray.length === 0) {\n console.warn(`Tab autocompletion logic reached invalid branch`);\n return allPos;\n }\n const arg = commandArray[commandArray.length - 1];\n parentDirPath = getAllParentDirectories(arg);\n evaledParentDirPath = evaluateDirectoryPath(parentDirPath, currPath);\n if (evaledParentDirPath === \"/\") {\n evaledParentDirPath = null;\n } else if (evaledParentDirPath == null) {\n return allPos; // Invalid path\n } else {\n evaledParentDirPath += \"/\";\n }\n\n if (isCommand(\"buy\")) {\n const options = [];\n for (const i in DarkWebItems) {\n const item = DarkWebItems[i];\n options.push(item.program);\n }\n\n return options.concat(Object.keys(GlobalAliases));\n }\n\n if (isCommand(\"scp\") && index === 1) {\n for (const iphostname in AllServers) {\n allPos.push(AllServers[iphostname].ip);\n allPos.push(AllServers[iphostname].hostname);\n }\n\n return allPos;\n }\n\n if (isCommand(\"scp\") && index === 0) {\n addAllScripts();\n addAllLitFiles();\n addAllTextFiles();\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"connect\")) {\n // All network connections\n for (let i = 0; i < currServ.serversOnNetwork.length; ++i) {\n const serv = AllServers[currServ.serversOnNetwork[i]];\n if (serv == null) {\n continue;\n }\n allPos.push(serv.ip);\n allPos.push(serv.hostname);\n }\n\n return allPos;\n }\n\n if (isCommand(\"kill\") || isCommand(\"tail\") || isCommand(\"mem\") || isCommand(\"check\")) {\n addAllScripts();\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"nano\")) {\n addAllScripts();\n addAllTextFiles();\n allPos.push(\".fconf\");\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"rm\")) {\n addAllScripts();\n addAllPrograms();\n addAllLitFiles();\n addAllTextFiles();\n addAllCodingContracts();\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"run\")) {\n addAllScripts();\n addAllPrograms();\n addAllCodingContracts();\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"cat\")) {\n addAllMessages();\n addAllLitFiles();\n addAllTextFiles();\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"download\") || isCommand(\"mv\")) {\n addAllScripts();\n addAllTextFiles();\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"cd\")) {\n addAllDirectories();\n\n return allPos;\n }\n\n if (isCommand(\"ls\") && index === 0) {\n addAllDirectories();\n }\n\n return allPos;\n}\n","/**\n * Helper functions that implement \"directory\" functionality in the Terminal.\n * These aren't \"real\" directories, it's more of a pseudo-directory implementation\n * that uses mainly string manipulation.\n *\n * This file contains function that deal with Server-related directory things.\n * Functions that deal with the string manipulation can be found in\n * ./DirectoryHelpers.ts\n */\nimport { isValidDirectoryPath, isInRootDirectory, getFirstParentDirectory } from \"./DirectoryHelpers\";\nimport { BaseServer } from \"../Server/BaseServer\";\n\n/**\n * Given a directory (by the full directory path) and a server, returns all\n * subdirectories of that directory. This is only for FIRST-LEVEl/immediate subdirectories\n */\nexport function getSubdirectories(serv: BaseServer, dir: string): string[] {\n const res: string[] = [];\n\n if (!isValidDirectoryPath(dir)) {\n return res;\n }\n\n let t_dir = dir;\n if (!t_dir.endsWith(\"/\")) {\n t_dir += \"/\";\n }\n\n function processFile(fn: string): void {\n if (t_dir === \"/\" && isInRootDirectory(fn)) {\n const subdir = getFirstParentDirectory(fn);\n if (subdir !== \"/\" && !res.includes(subdir)) {\n res.push(subdir);\n }\n } else if (fn.startsWith(t_dir)) {\n const remaining = fn.slice(t_dir.length);\n const subdir = getFirstParentDirectory(remaining);\n if (subdir !== \"/\" && !res.includes(subdir)) {\n res.push(subdir);\n }\n }\n }\n\n for (const script of serv.scripts) {\n processFile(script.filename);\n }\n\n for (const txt of serv.textFiles) {\n processFile(txt.fn);\n }\n\n return res;\n}\n","import { containsAllStrings, longestCommonStart } from \"../utils/StringHelperFunctions\";\n\n/**\n * Implements tab completion for the Terminal\n *\n * @param command {string} Terminal command, excluding the last incomplete argument\n * @param arg {string} Last argument that is being completed\n * @param allPossibilities {string[]} All values that `arg` can complete to\n */\nexport function tabCompletion(\n command: string,\n arg: string,\n allPossibilities: string[],\n oldValue: string,\n): string[] | string | undefined {\n if (!(allPossibilities.constructor === Array)) {\n return;\n }\n if (!containsAllStrings(allPossibilities)) {\n return;\n }\n\n // Remove all options in allPossibilities that do not match the current string\n // that we are attempting to autocomplete\n if (arg === \"\") {\n for (let i = allPossibilities.length - 1; i >= 0; --i) {\n if (!allPossibilities[i].toLowerCase().startsWith(command.toLowerCase())) {\n allPossibilities.splice(i, 1);\n }\n }\n } else {\n for (let i = allPossibilities.length - 1; i >= 0; --i) {\n if (!allPossibilities[i].toLowerCase().startsWith(arg.toLowerCase())) {\n allPossibilities.splice(i, 1);\n }\n }\n }\n\n const semiColonIndex = oldValue.lastIndexOf(\";\");\n\n let val = \"\";\n if (allPossibilities.length === 0) {\n return;\n } else if (allPossibilities.length === 1) {\n if (arg === \"\") {\n //Autocomplete command\n val = allPossibilities[0] + \" \";\n } else {\n val = command + \" \" + allPossibilities[0];\n }\n\n if (semiColonIndex === -1) {\n // No semicolon, so replace the whole command\n return val;\n } else {\n // Replace only after the last semicolon\n return oldValue.slice(0, semiColonIndex + 1) + \" \" + val;\n }\n } else {\n const longestStartSubstr = longestCommonStart(allPossibilities);\n /**\n * If the longest common starting substring of remaining possibilities is the same\n * as whatevers already in terminal, just list all possible options. Otherwise,\n * change the input in the terminal to the longest common starting substr\n */\n if (arg === \"\") {\n if (longestStartSubstr === command) {\n return allPossibilities;\n } else {\n if (semiColonIndex === -1) {\n // No semicolon, so replace the whole command\n return longestStartSubstr;\n } else {\n // Replace only after the last semicolon\n return `${oldValue.slice(0, semiColonIndex + 1)} ${longestStartSubstr}`;\n }\n }\n } else {\n if (longestStartSubstr === arg) {\n // List all possible options\n return allPossibilities;\n } else {\n if (semiColonIndex == -1) {\n // No semicolon, so replace the whole command\n return `${command} ${longestStartSubstr}`;\n } else {\n // Replace only after the last semicolon\n return `${oldValue.slice(0, semiColonIndex + 1)} ${command} ${longestStartSubstr}`;\n }\n }\n }\n }\n}\n","import React from \"react\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Link from \"@mui/material/Link\";\nimport Box from \"@mui/material/Box\";\nexport function TutorialRoot(): React.ReactElement {\n return (\n <>\n Tutorial / Documentation\n \n \n Getting Started\n \n
    \n \n Servers & Networking\n \n
    \n \n Hacking\n \n
    \n \n Scripts\n \n
    \n \n Netscript Programming Language\n \n
    \n \n Traveling\n \n
    \n \n Companies\n \n
    \n \n Infiltration\n \n
    \n \n Factions\n \n
    \n \n Augmentations\n \n
    \n \n Keyboard Shortcuts\n \n
    \n \n );\n}\n","/**\n * Root React Component for the \"Active Scripts\" UI page. This page displays\n * and provides information about all of the player's scripts that are currently running\n */\nimport React, { useState, useEffect } from \"react\";\n\nimport { ScriptProduction } from \"./ScriptProduction\";\nimport { ServerAccordions } from \"./ServerAccordions\";\n\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\n\nimport Typography from \"@mui/material/Typography\";\n\ntype IProps = {\n workerScripts: Map;\n};\n\nexport function ActiveScriptsRoot(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n\n return (\n <>\n Active Scripts\n \n This page displays a list of all of your scripts that are currently running across every machine. It also\n provides information about each script's production. The scripts are categorized by the hostname of the servers\n on which they are running.\n \n\n \n \n \n );\n}\n","/**\n * React Component for displaying the total production and production rate\n * of scripts on the 'Active Scripts' UI page\n */\nimport * as React from \"react\";\n\nimport { Money } from \"../React/Money\";\nimport { MoneyRate } from \"../React/MoneyRate\";\nimport { use } from \"../Context\";\n\nimport Typography from \"@mui/material/Typography\";\n\nimport { Theme } from \"@mui/material/styles\";\nimport makeStyles from \"@mui/styles/makeStyles\";\nimport createStyles from \"@mui/styles/createStyles\";\nimport Table from \"@mui/material/Table\";\nimport TableBody from \"@mui/material/TableBody\";\nimport TableCell from \"@mui/material/TableCell\";\nimport TableRow from \"@mui/material/TableRow\";\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n cell: {\n borderBottom: \"none\",\n padding: theme.spacing(1),\n margin: theme.spacing(1),\n whiteSpace: \"nowrap\",\n },\n size: {\n width: \"1px\",\n },\n }),\n);\nexport function ScriptProduction(): React.ReactElement {\n const player = use.Player();\n const classes = useStyles();\n const prodRateSinceLastAug = player.scriptProdSinceLastAug / (player.playtimeSinceLastAug / 1000);\n\n return (\n \n \n \n \n Total production:\n \n \n \n \n \n \n \n \n ()\n \n \n \n \n
    \n );\n}\n","/**\n * React Component for rendering the Accordion elements for all servers\n * on which scripts are running\n */\nimport React, { useState, useEffect } from \"react\";\n\nimport { ServerAccordion } from \"./ServerAccordion\";\n\nimport TextField from \"@mui/material/TextField\";\nimport List from \"@mui/material/List\";\nimport TablePagination from \"@mui/material/TablePagination\";\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\nimport { WorkerScriptStartStopEventEmitter } from \"../../Netscript/WorkerScriptStartStopEventEmitter\";\nimport { getServer } from \"../../Server/ServerHelpers\";\nimport { BaseServer } from \"../../Server/BaseServer\";\nimport { Settings } from \"../../Settings/Settings\";\nimport { TablePaginationActionsAll } from \"../React/TablePaginationActionsAll\";\nimport SearchIcon from \"@mui/icons-material/Search\";\n\n// Map of server hostname -> all workerscripts on that server for all active scripts\ninterface IServerData {\n server: BaseServer;\n workerScripts: WorkerScript[];\n}\n\ninterface IServerToScriptsMap {\n [key: string]: IServerData | undefined;\n}\n\ntype IProps = {\n workerScripts: Map;\n};\n\nexport function ServerAccordions(props: IProps): React.ReactElement {\n const [filter, setFilter] = useState(\"\");\n const [page, setPage] = useState(0);\n const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsServerPageSize);\n const setRerender = useState(false)[1];\n\n const handleChangePage = (event: unknown, newPage: number): void => {\n setPage(newPage);\n };\n\n const handleChangeRowsPerPage = (event: React.ChangeEvent): void => {\n Settings.ActiveScriptsServerPageSize = parseInt(event.target.value, 10);\n setRowsPerPage(parseInt(event.target.value, 10));\n setPage(0);\n };\n\n function handleFilterChange(event: React.ChangeEvent): void {\n setFilter(event.target.value);\n setPage(0);\n }\n\n const serverToScriptMap: IServerToScriptsMap = {};\n for (const ws of props.workerScripts.values()) {\n const server = getServer(ws.serverIp);\n if (server == null) {\n console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`);\n continue;\n }\n\n let data = serverToScriptMap[server.hostname];\n\n if (data === undefined) {\n serverToScriptMap[server.hostname] = {\n server: server,\n workerScripts: [],\n };\n data = serverToScriptMap[server.hostname];\n }\n if (data !== undefined) data.workerScripts.push(ws);\n }\n\n const filtered = Object.values(serverToScriptMap).filter((data) => data && data.server.hostname.includes(filter));\n\n function rerender(): void {\n setRerender((old) => !old);\n }\n\n useEffect(() => WorkerScriptStartStopEventEmitter.subscribe(rerender));\n\n return (\n <>\n ,\n spellCheck: false,\n }}\n />\n \n {filtered.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((data) => {\n return (\n data && (\n \n )\n );\n })}\n \n \n \n );\n}\n","/**\n * React Component for rendering the Accordion element for a single\n * server in the 'Active Scripts' UI page\n */\nimport * as React from \"react\";\n\nimport Typography from \"@mui/material/Typography\";\n\nimport ListItemButton from \"@mui/material/ListItemButton\";\nimport ListItemText from \"@mui/material/ListItemText\";\n\nimport Paper from \"@mui/material/Paper\";\nimport Box from \"@mui/material/Box\";\nimport Collapse from \"@mui/material/Collapse\";\nimport ExpandMore from \"@mui/icons-material/ExpandMore\";\nimport ExpandLess from \"@mui/icons-material/ExpandLess\";\nimport { ServerAccordionContent } from \"./ServerAccordionContent\";\n\nimport { BaseServer } from \"../../Server/BaseServer\";\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\n\nimport { createProgressBarText } from \"../../utils/helpers/createProgressBarText\";\n\ntype IProps = {\n server: BaseServer;\n workerScripts: WorkerScript[];\n};\n\nexport function ServerAccordion(props: IProps): React.ReactElement {\n const [open, setOpen] = React.useState(false);\n const server = props.server;\n\n // Accordion's header text\n // TODO: calculate the longest hostname length rather than hard coding it\n const longestHostnameLength = 18;\n const paddedName = `${server.hostname}${\" \".repeat(longestHostnameLength)}`.slice(\n 0,\n Math.max(server.hostname.length, longestHostnameLength),\n );\n const barOptions = {\n progress: server.ramUsed / server.maxRam,\n totalTicks: 30,\n };\n const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`;\n\n return (\n \n setOpen((old) => !old)}>\n {headerTxt}} />\n {open ? : }\n \n \n \n \n \n \n \n );\n}\n","import React, { useState } from \"react\";\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\nimport { WorkerScriptAccordion } from \"./WorkerScriptAccordion\";\nimport List from \"@mui/material/List\";\nimport TablePagination from \"@mui/material/TablePagination\";\nimport { TablePaginationActionsAll } from \"../React/TablePaginationActionsAll\";\nimport { Settings } from \"../../Settings/Settings\";\n\ninterface IProps {\n workerScripts: WorkerScript[];\n}\n\nexport function ServerAccordionContent(props: IProps): React.ReactElement {\n const [page, setPage] = useState(0);\n const [rowsPerPage, setRowsPerPage] = useState(Settings.ActiveScriptsScriptPageSize);\n const handleChangePage = (event: unknown, newPage: number): void => {\n setPage(newPage);\n };\n\n const handleChangeRowsPerPage = (event: React.ChangeEvent): void => {\n Settings.ActiveScriptsScriptPageSize = parseInt(event.target.value, 10);\n setRowsPerPage(parseInt(event.target.value, 10));\n setPage(0);\n };\n\n return (\n <>\n \n {props.workerScripts.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((ws) => (\n \n ))}\n \n \n \n );\n}\n","/**\n * React Component for displaying a single WorkerScript's info as an\n * Accordion element\n */\nimport * as React from \"react\";\n\nimport { numeralWrapper } from \"../numeralFormat\";\n\nimport Table from \"@mui/material/Table\";\nimport TableCell from \"@mui/material/TableCell\";\nimport TableRow from \"@mui/material/TableRow\";\nimport TableBody from \"@mui/material/TableBody\";\nimport Button from \"@mui/material/Button\";\nimport Box from \"@mui/material/Box\";\nimport Paper from \"@mui/material/Paper\";\nimport Typography from \"@mui/material/Typography\";\nimport IconButton from \"@mui/material/IconButton\";\nimport DeleteIcon from \"@mui/icons-material/Delete\";\nimport ListItemButton from \"@mui/material/ListItemButton\";\nimport ListItemText from \"@mui/material/ListItemText\";\nimport makeStyles from \"@mui/styles/makeStyles\";\n\nimport Collapse from \"@mui/material/Collapse\";\nimport ExpandLess from \"@mui/icons-material/ExpandLess\";\nimport ExpandMore from \"@mui/icons-material/ExpandMore\";\n\nimport { killWorkerScript } from \"../../Netscript/killWorkerScript\";\nimport { WorkerScript } from \"../../Netscript/WorkerScript\";\n\nimport { dialogBoxCreate } from \"../React/DialogBox\";\nimport { LogBoxEvents } from \"../React/LogBoxManager\";\nimport { convertTimeMsToTimeElapsedString } from \"../../utils/StringHelperFunctions\";\nimport { arrayToString } from \"../../utils/helpers/arrayToString\";\nimport { Money } from \"../React/Money\";\nimport { MoneyRate } from \"../React/MoneyRate\";\n\nconst useStyles = makeStyles({\n noborder: {\n borderBottom: \"none\",\n },\n});\n\ntype IProps = {\n workerScript: WorkerScript;\n};\n\nexport function WorkerScriptAccordion(props: IProps): React.ReactElement {\n const classes = useStyles();\n const [open, setOpen] = React.useState(false);\n const workerScript = props.workerScript;\n const scriptRef = workerScript.scriptRef;\n\n function logClickHandler(): void {\n LogBoxEvents.emit(scriptRef);\n }\n const killScript = killWorkerScript.bind(null, scriptRef as any, scriptRef.server);\n\n function killScriptClickHandler(): void {\n killScript();\n dialogBoxCreate(\"Killing script\");\n }\n\n // Calculations for script stats\n const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime;\n const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime;\n\n return (\n <>\n setOpen((old) => !old)} component={Paper}>\n └ {props.workerScript.name}} />\n {open ? : }\n \n \n \n \n \n \n \n └ Threads:\n \n \n {numeralWrapper.formatThreads(props.workerScript.scriptRef.threads)}\n \n \n \n \n └ Args: {arrayToString(props.workerScript.args)}\n \n \n \n \n └ Online Time:\n \n \n {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}\n \n \n \n \n └ Offline Time:\n \n \n {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}\n \n \n \n \n └ Total online production:\n \n \n \n \n \n \n \n \n \n \n  {numeralWrapper.formatExp(scriptRef.onlineExpGained) + \" hacking exp\"}\n \n \n\n \n \n └ Online production rate:\n \n \n \n \n \n \n \n \n \n \n  {numeralWrapper.formatExp(onlineEps) + \" hacking exp / sec\"}\n \n \n\n \n \n └ Total offline production:\n \n \n \n \n \n \n \n \n \n \n  {numeralWrapper.formatExp(scriptRef.offlineExpGained) + \" hacking exp\"}\n \n \n \n
    \n\n \n \n \n \n
    \n
    \n \n );\n}\n","import React, { useState, useEffect } from \"react\";\nimport { IPlayer } from \"../../PersonObjects/IPlayer\";\nimport { IRouter } from \"../../ui/Router\";\nimport { Factions } from \"../Factions\";\nimport { Faction } from \"../Faction\";\nimport { joinFaction } from \"../FactionHelpers\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Box from \"@mui/material/Box\";\nimport Link from \"@mui/material/Link\";\nimport Button from \"@mui/material/Button\";\nimport TableBody from \"@mui/material/TableBody\";\nimport { Table, TableCell } from \"../../ui/React/Table\";\nimport TableRow from \"@mui/material/TableRow\";\n\ninterface IProps {\n player: IPlayer;\n router: IRouter;\n}\n\nexport function FactionsRoot(props: IProps): React.ReactElement {\n const setRerender = useState(false)[1];\n function rerender(): void {\n setRerender((old) => !old);\n }\n useEffect(() => {\n const id = setInterval(rerender, 200);\n return () => clearInterval(id);\n }, []);\n function openFaction(faction: Faction): void {\n props.router.toFaction(faction);\n }\n\n function acceptInvitation(event: React.MouseEvent, faction: string): void {\n if (!event.isTrusted) return;\n joinFaction(Factions[faction]);\n setRerender((x) => !x);\n }\n\n return (\n <>\n Factions\n Lists all factions you have joined\n
    \n \n {props.player.factions.map((faction: string) => (\n openFaction(Factions[faction])}>\n {faction}\n \n ))}\n \n
    \n {props.player.factionInvitations.length > 0 && (\n <>\n \n Outstanding Faction Invitations\n \n \n Lists factions you have been invited to. You can accept these faction invitations at any time.\n \n \n \n {props.player.factionInvitations.map((faction: string) => (\n \n \n {faction}\n \n \n \n \n \n ))}\n \n
    \n \n )}\n \n );\n}\n","/**\n * Root React Component for displaying a Faction's UI.\n * This is the component for displaying a single faction's UI, not the list of all\n * accessible factions\n */\nimport React, { useState, useEffect } from \"react\";\n\nimport { AugmentationsPage } from \"./AugmentationsPage\";\nimport { DonateOption } from \"./DonateOption\";\nimport { Info } from \"./Info\";\nimport { Option } from \"./Option\";\n\nimport { CONSTANTS } from \"../../Constants\";\n\nimport { BitNodeMultipliers } from \"../../BitNode/BitNodeMultipliers\";\nimport { Faction } from \"../../Faction/Faction\";\nimport { SourceFileFlags } from \"../../SourceFile/SourceFileFlags\";\n\nimport { use } from \"../../ui/Context\";\nimport { CreateGangModal } from \"./CreateGangModal\";\n\nimport Typography from \"@mui/material/Typography\";\nimport Button from \"@mui/material/Button\";\nimport { CovenantPurchasesRoot } from \"../../PersonObjects/Sleeve/ui/CovenantPurchasesRoot\";\n\ntype IProps = {\n faction: Faction;\n};\n\n// Info text for all options on the UI\nconst gangInfo = \"Create and manage a gang for this Faction. Gangs will earn you money and \" + \"faction reputation\";\nconst hackingContractsInfo =\n \"Complete hacking contracts for your faction. \" +\n \"Your effectiveness, which determines how much \" +\n \"reputation you gain for this faction, is based on your hacking skill. \" +\n \"You will gain hacking exp.\";\nconst fieldWorkInfo =\n \"Carry out field missions for your faction. \" +\n \"Your effectiveness, which determines how much \" +\n \"reputation you gain for this faction, is based on all of your stats. \" +\n \"You will gain exp for all stats.\";\nconst securityWorkInfo =\n \"Serve in a security detail for your faction. \" +\n \"Your effectiveness, which determines how much \" +\n \"reputation you gain for this faction, is based on your combat stats. \" +\n \"You will gain exp for all combat stats.\";\nconst augmentationsInfo =\n \"As your reputation with this faction rises, you will \" +\n \"unlock Augmentations, which you can purchase to enhance \" +\n \"your abilities.\";\nconst sleevePurchasesInfo = \"Purchase Duplicate Sleeves and upgrades. These are permanent!\";\n\nconst GangNames = [\n \"Slum Snakes\",\n \"Tetrads\",\n \"The Syndicate\",\n \"The Dark Army\",\n \"Speakers for the Dead\",\n \"NiteSec\",\n \"The Black Hand\",\n];\n\ninterface IMainProps {\n faction: Faction;\n rerender: () => void;\n onAugmentations: () => void;\n}\n\nfunction MainPage({ faction, rerender, onAugmentations }: IMainProps): React.ReactElement {\n const player = use.Player();\n const router = use.Router();\n const [sleevesOpen, setSleevesOpen] = useState(false);\n const [gangOpen, setGangOpen] = useState(false);\n const p = player;\n const factionInfo = faction.getInfo();\n\n function manageGang(): void {\n // If player already has a gang, just go to the gang UI\n if (player.inGang()) {\n return router.toGang();\n }\n\n setGangOpen(true);\n }\n\n function startFieldWork(faction: Faction): void {\n player.startFactionFieldWork(router, faction);\n }\n\n function startHackingContracts(faction: Faction): void {\n player.startFactionHackWork(router, faction);\n }\n\n function startSecurityWork(faction: Faction): void {\n player.startFactionSecurityWork(router, faction);\n }\n\n // We have a special flag for whether the player this faction is the player's\n // gang faction because if the player has a gang, they cannot do any other action\n const isPlayersGang = p.inGang() && p.getGangName() === faction.name;\n\n // Flags for whether special options (gang, sleeve purchases, donate, etc.)\n // should be shown\n const favorToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);\n const canDonate = faction.favor >= favorToDonate;\n\n const canPurchaseSleeves = faction.name === \"The Covenant\" && p.bitNodeN >= 10 && SourceFileFlags[10];\n\n let canAccessGang = p.canAccessGang() && GangNames.includes(faction.name);\n if (p.inGang()) {\n if (p.getGangName() !== faction.name) {\n canAccessGang = false;\n } else if (p.getGangName() === faction.name) {\n canAccessGang = true;\n }\n }\n\n return (\n <>\n \n \n {faction.name}\n \n \n {canAccessGang && (\n <>\n