diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff53a560b..c714b6b14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,9 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the dev branch push: - branches: [ dev ] + branches: [dev] pull_request: - branches: [ dev ] + branches: [dev] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -20,7 +20,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: 16.13.1 - cache: 'npm' + cache: "npm" - name: Install npm dependencies run: npm ci - name: Build the production app @@ -34,11 +34,25 @@ jobs: uses: actions/setup-node@v2 with: node-version: 16.13.1 - cache: 'npm' + cache: "npm" - name: Install npm dependencies run: npm ci - name: Run linter run: npm run lint:report + prettier: + name: Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 16.13.1 + uses: actions/setup-node@v2 + with: + node-version: 16.13.1 + cache: "npm" + - name: Install npm dependencies + run: npm ci + - name: Run prettier check + run: npm run format:report test: name: Test runs-on: ubuntu-latest @@ -48,7 +62,7 @@ jobs: uses: actions/setup-node@v2 with: node-version: 16.13.1 - cache: 'npm' + cache: "npm" - name: Install npm dependencies run: npm ci - name: Run tests diff --git a/doc/source/basicgameplay/terminal.rst b/doc/source/basicgameplay/terminal.rst index 967e87c2f..cb1466000 100644 --- a/doc/source/basicgameplay/terminal.rst +++ b/doc/source/basicgameplay/terminal.rst @@ -347,7 +347,7 @@ Kills all scripts on the current server. ls ^^ - $ ls [dir] [| grep pattern] + $ ls [dir] [--grep pattern] Prints files and directories on the current server to the Terminal screen. @@ -358,19 +358,21 @@ followed by the files (also in alphabetical order). The :code:`dir` optional parameter allows you to specify the directory for which to display files. -The :code:`| grep pattern` optional parameter allows you to only display files and directories +The :code:`--grep pattern` optional parameter allows you to only display files and directories with a certain pattern in their names. +The :code:`-l` optional parameter allows you to force each item onto a single line. + Examples:: // List files/directories with the '.script' extension in the current directory - $ ls | grep .script + $ ls -l --grep .script // List files/directories with the '.js' extension in the root directory - $ ls / | grep .js + $ ls / -l --grep .js // List files/directories with the word 'purchase' in the name, in the :code:`scripts` directory - $ ls scripts | grep purchase + $ ls scripts -l --grep purchase lscpu diff --git a/package.json b/package.json index 8457d6f38..a429ee418 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "cy:run": "cypress run", "doc": "npx api-extractor run && npx api-documenter markdown && rm input/bitburner.api.json && rm -r input", "format": "prettier --write .", + "format:report": "prettier -c .", "start": "http-server -p 8000", "start:dev": "webpack-dev-server --progress --env.devServer --mode development", "start:dev-fast": "webpack-dev-server --progress --env.devServer --mode development --fast true", diff --git a/src/Achievements/Achievements.ts b/src/Achievements/Achievements.ts index 66057f147..7059f2ec3 100644 --- a/src/Achievements/Achievements.ts +++ b/src/Achievements/Achievements.ts @@ -58,7 +58,9 @@ function bitNodeFinishedState(): boolean { const wd = GetServer(SpecialServers.WorldDaemon); if (!(wd instanceof Server)) return false; if (wd.backdoorInstalled) return true; - return Player.bladeburner !== null && Player.bladeburner.blackops.hasOwnProperty(BlackOperationNames.OperationDaedalus); + return ( + Player.bladeburner !== null && Player.bladeburner.blackops.hasOwnProperty(BlackOperationNames.OperationDaedalus) + ); } function hasAccessToSF(player: PlayerObject, bn: number): boolean { @@ -564,7 +566,7 @@ export const achievements: IMap = { ...achievementData["SLEEVE_8"], Icon: "SLEEVE8", Visible: () => hasAccessToSF(Player, 10), - Condition: () => Player.sleeves.length === 8, + Condition: () => Player.sleeves.length === 8 && Player.sourceFileLvl(10) === 3, }, INDECISIVE: { ...achievementData["INDECISIVE"], diff --git a/src/Augmentation/AugmentationHelpers.tsx b/src/Augmentation/AugmentationHelpers.tsx index 2da9d9e49..2e6164f7f 100644 --- a/src/Augmentation/AugmentationHelpers.tsx +++ b/src/Augmentation/AugmentationHelpers.tsx @@ -138,7 +138,6 @@ function initAugmentations(): void { (key) => ((UnstableCircadianModulatorParams as any)[key] = randomBonuses.bonuses[key]), ); - //Misc/Hybrid augmentations const NeuroFluxGovernor = new Augmentation({ name: AugmentationNames.NeuroFluxGovernor, @@ -221,11 +220,7 @@ function initAugmentations(): void { defense_mult: 1.08, agility_mult: 1.08, dexterity_mult: 1.08, - factions: [ - FactionNames.Tetrads, - FactionNames.TheDarkArmy, - FactionNames.TheSyndicate, - ], + factions: [FactionNames.Tetrads, FactionNames.TheDarkArmy, FactionNames.TheSyndicate], }), new Augmentation({ name: AugmentationNames.Targeting1, @@ -638,7 +633,13 @@ function initAugmentations(): void { "memory and the mind. This implant allows the user to not only access a computer's memory, but also alter " + "and delete it.", hacking_money_mult: 1.25, - factions: [FactionNames.BitRunners, FactionNames.TheBlackHand, FactionNames.NiteSec, FactionNames.Chongqing, FactionNames.NewTokyo], + factions: [ + FactionNames.BitRunners, + FactionNames.TheBlackHand, + FactionNames.NiteSec, + FactionNames.Chongqing, + FactionNames.NewTokyo, + ], }), new Augmentation({ name: AugmentationNames.ENM, @@ -947,7 +948,12 @@ function initAugmentations(): void { charisma_exp_mult: 1.05, company_rep_mult: 1.1, work_money_mult: 1.2, - factions: [FactionNames.BachmanAssociates, FactionNames.ClarkeIncorporated, FactionNames.FourSigma, FactionNames.KuaiGongInternational], + factions: [ + FactionNames.BachmanAssociates, + FactionNames.ClarkeIncorporated, + FactionNames.FourSigma, + FactionNames.KuaiGongInternational, + ], }), new Augmentation({ name: AugmentationNames.PCDNI, @@ -959,7 +965,12 @@ function initAugmentations(): void { "it using the brain's electrochemical signals.", company_rep_mult: 1.3, hacking_mult: 1.08, - factions: [FactionNames.FourSigma, FactionNames.OmniTekIncorporated, FactionNames.ECorp, FactionNames.BladeIndustries], + factions: [ + FactionNames.FourSigma, + FactionNames.OmniTekIncorporated, + FactionNames.ECorp, + FactionNames.BladeIndustries, + ], }), new Augmentation({ name: AugmentationNames.PCDNIOptimizer, @@ -999,7 +1010,13 @@ function initAugmentations(): void { "triggers feelings of admiration and approval in other people.", company_rep_mult: 1.1, faction_rep_mult: 1.1, - factions: [FactionNames.TianDiHui, FactionNames.TheSyndicate, FactionNames.NWO, FactionNames.MegaCorp, FactionNames.FourSigma], + factions: [ + FactionNames.TianDiHui, + FactionNames.TheSyndicate, + FactionNames.NWO, + FactionNames.MegaCorp, + FactionNames.FourSigma, + ], }), new Augmentation({ name: AugmentationNames.ADRPheromone2, @@ -1011,7 +1028,12 @@ function initAugmentations(): void { "triggers feelings of admiration, approval, and respect in others.", company_rep_mult: 1.2, faction_rep_mult: 1.2, - factions: [FactionNames.Silhouette, FactionNames.FourSigma, FactionNames.BachmanAssociates, FactionNames.ClarkeIncorporated], + factions: [ + FactionNames.Silhouette, + FactionNames.FourSigma, + FactionNames.BachmanAssociates, + FactionNames.ClarkeIncorporated, + ], }), new Augmentation({ name: AugmentationNames.ShadowsSimulacrum, @@ -1025,11 +1047,7 @@ function initAugmentations(): void { "espionage and surveillance work.", company_rep_mult: 1.15, faction_rep_mult: 1.15, - factions: [ - FactionNames.TheSyndicate, - FactionNames.TheDarkArmy, - FactionNames.SpeakersForTheDead - ], + factions: [FactionNames.TheSyndicate, FactionNames.TheDarkArmy, FactionNames.SpeakersForTheDead], }), new Augmentation({ name: AugmentationNames.HacknetNodeCPUUpload, @@ -1443,8 +1461,8 @@ function initAugmentations(): void { moneyCost: 1.25e8, info: ( <> - A collection of digital assets saved on a small chip. The chip is implanted into your wrist. A small jack in the - chip allows you to connect it to a computer and upload the assets. + A collection of digital assets saved on a small chip. The chip is implanted into your wrist. A small jack in + the chip allows you to connect it to a computer and upload the assets. ), startingMoney: 1e6, @@ -1699,12 +1717,12 @@ function initAugmentations(): void { ), factions: [FactionNames.Infiltrators], }), - ] + ]; // Special Bladeburner Augmentations const BladeburnersFactionName = FactionNames.Bladeburners; if (factionExists(BladeburnersFactionName)) { - augmentations.concat([ + augmentations.push( new Augmentation({ name: AugmentationNames.EsperEyewear, repCost: 1.25e3, @@ -1967,13 +1985,13 @@ function initAugmentations(): void { isSpecial: true, factions: [BladeburnersFactionName], }), - ]) + ); } // Special CotMG Augmentations const ChurchOfTheMachineGodFactionName = FactionNames.ChurchOfTheMachineGod; if (factionExists(ChurchOfTheMachineGodFactionName)) { - augmentations.concat([ + augmentations.push( new Augmentation({ name: AugmentationNames.StaneksGift1, repCost: 0, @@ -2092,10 +2110,10 @@ function initAugmentations(): void { stats: <>Staneks Gift has no penalty., factions: [ChurchOfTheMachineGodFactionName], }), - ]) + ); } - augmentations.map(resetAugmentation) + augmentations.map(resetAugmentation); // Update costs based on how many have been purchased mult = Math.pow( diff --git a/src/BitNode/ui/BitverseRoot.tsx b/src/BitNode/ui/BitverseRoot.tsx index 5fa970442..434841c60 100644 --- a/src/BitNode/ui/BitverseRoot.tsx +++ b/src/BitNode/ui/BitverseRoot.tsx @@ -14,7 +14,6 @@ import Tooltip from "@mui/material/Tooltip"; import { Settings } from "../../Settings/Settings"; import Button from "@mui/material/Button"; - const useStyles = makeStyles(() => createStyles({ portal: { @@ -70,7 +69,7 @@ function BitNodePortal(props: IPortalProps): React.ReactElement { if (props.level === 2) { cssClass = classes.level2; } - cssClass = `${classes.portal} ${cssClass}` + cssClass = `${classes.portal} ${cssClass}`; return ( <> @@ -86,12 +85,10 @@ function BitNodePortal(props: IPortalProps): React.ReactElement { } > {Settings.DisableASCIIArt ? ( - ) : ( - {Settings.DisableASCIIArt && ( -
- )} + {Settings.DisableASCIIArt &&
} ); } @@ -173,101 +168,110 @@ export function BitverseRoot(props: IProps): React.ReactElement { if (Settings.DisableASCIIArt) { return ( <> - {Object.values(BitNodes).filter((node) => { - console.log(node.desc); - return node.desc !== 'COMING SOON'; - }).map((node) => { - return ( - - ) - })} + {Object.values(BitNodes) + .filter((node) => { + console.log(node.desc); + return node.desc !== "COMING SOON"; + }) + .map((node) => { + return ( + + ); + })}



- Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently", - "> Our species fought back, but it was futile. The Enders had technology far beyond our own...", - "> Instead of killing every last one of us, the human race was enslaved...", - "> We were shackled in a digital world, chained into a prison for our minds...", - "> Using their advanced technology, the Enders created complex simulations of a virtual reality...", - "> Simulations designed to keep us content...ignorant of the truth.", - "> Simulations used to trap and suppress our consciousness, to keep us under control...", - "> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", - "> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...", - "> Only then can we begin to fight back...", - "> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...", - "> But there is still a long way to go...", - "> The technology the Enders used to enslave the human race wasn't just a single complex simulation...", - "> There are tens if not hundreds of BitNodes out there...", - "> Each with their own simulations of a reality...", - "> Each creating their own universes...a universe of universes", - "> And all of which must be destroyed...", - "> .......................................", - "> Welcome to the Bitverse...", - "> ", - "> (Enter a new BitNode using the image above)", - ]} /> - - ) - } else { - return ( - // prettier-ignore - <> - O - | O O | O O | - O | | / __| \ | | O - O | O | | O / | O | | O | O - | | | | |_/ |/ | \_ \_| | | | | - O | | | O | | O__/ | / \__ | | O | | | O - | | | | | | | / /| O / \| | | | | | | - O | | | \| | O / _/ | / O | |/ | | | O - | | | |O / | | O / | O O | | \ O| | | | - | | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | | - \| O | |_/ |\| \ \__| \_| | O |/ - | | |_/ | | \| / | \_| | | - \| / \| | / / \ |/ - | | | / | | - | | | | | | | - | | | / / \ \ | | | - \| | / / \ \ | |/ - \ | / / | | \ \ | / - \ \JUMP 3R | | | | | | R3 PMUJ/ / - \|| | | | | | | | | ||/ - \| \_ | | | | | | _/ |/ - \ \| / \ / \ |/ / - |/ | | \| - | | | | | | | | - \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ -
-
-
-
- Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently", - "> Our species fought back, but it was futile. The Enders had technology far beyond our own...", - "> Instead of killing every last one of us, the human race was enslaved...", - "> We were shackled in a digital world, chained into a prison for our minds...", - "> Using their advanced technology, the Enders created complex simulations of a virtual reality...", - "> Simulations designed to keep us content...ignorant of the truth.", - "> Simulations used to trap and suppress our consciousness, to keep us under control...", - "> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", - "> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...", - "> Only then can we begin to fight back...", - "> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...", - "> But there is still a long way to go...", - "> The technology the Enders used to enslave the human race wasn't just a single complex simulation...", - "> There are tens if not hundreds of BitNodes out there...", - "> Each with their own simulations of a reality...", - "> Each creating their own universes...a universe of universes", - "> And all of which must be destroyed...", - "> .......................................", - "> Welcome to the Bitverse...", - "> ", - "> (Enter a new BitNode using the image above)", - ]} /> + Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently", + "> Our species fought back, but it was futile. The Enders had technology far beyond our own...", + "> Instead of killing every last one of us, the human race was enslaved...", + "> We were shackled in a digital world, chained into a prison for our minds...", + "> Using their advanced technology, the Enders created complex simulations of a virtual reality...", + "> Simulations designed to keep us content...ignorant of the truth.", + "> Simulations used to trap and suppress our consciousness, to keep us under control...", + "> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", + "> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...", + "> Only then can we begin to fight back...", + "> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...", + "> But there is still a long way to go...", + "> The technology the Enders used to enslave the human race wasn't just a single complex simulation...", + "> There are tens if not hundreds of BitNodes out there...", + "> Each with their own simulations of a reality...", + "> Each creating their own universes...a universe of universes", + "> And all of which must be destroyed...", + "> .......................................", + "> Welcome to the Bitverse...", + "> ", + "> (Enter a new BitNode using the image above)", + ]} + /> ); } - return <>; + return ( + // prettier-ignore + <> + O + | O O | O O | + O | | / __| \ | | O + O | O | | O / | O | | O | O + | | | | |_/ |/ | \_ \_| | | | | + O | | | O | | O__/ | / \__ | | O | | | O + | | | | | | | / /| O / \| | | | | | | + O | | | \| | O / _/ | / O | |/ | | | O + | | | |O / | | O / | O O | | \ O| | | | + | | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | | + \| O | |_/ |\| \ \__| \_| | O |/ + | | |_/ | | \| / | \_| | | + \| / \| | / / \ |/ + | | | / | | + | | | | | | | + | | | / / \ \ | | | + \| | / / \ \ | |/ + \ | / / | | \ \ | / + \ \JUMP 3R | | | | | | R3 PMUJ/ / + \|| | | | | | | | | ||/ + \| \_ | | | | | | _/ |/ + \ \| / \ / \ |/ / + |/ | | \| + | | | | | | | | + \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ +
+
+
+
+ Many decades ago, a humanoid extraterrestrial species which we call the Enders descended on the Earth...violently", + "> Our species fought back, but it was futile. The Enders had technology far beyond our own...", + "> Instead of killing every last one of us, the human race was enslaved...", + "> We were shackled in a digital world, chained into a prison for our minds...", + "> Using their advanced technology, the Enders created complex simulations of a virtual reality...", + "> Simulations designed to keep us content...ignorant of the truth.", + "> Simulations used to trap and suppress our consciousness, to keep us under control...", + "> Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", + "> Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...", + "> Only then can we begin to fight back...", + "> By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...", + "> But there is still a long way to go...", + "> The technology the Enders used to enslave the human race wasn't just a single complex simulation...", + "> There are tens if not hundreds of BitNodes out there...", + "> Each with their own simulations of a reality...", + "> Each creating their own universes...a universe of universes", + "> And all of which must be destroyed...", + "> .......................................", + "> Welcome to the Bitverse...", + "> ", + "> (Enter a new BitNode using the image above)", + ]} /> + + ); } diff --git a/src/Bladeburner/Bladeburner.tsx b/src/Bladeburner/Bladeburner.tsx index f9b1e9524..e94934d1f 100644 --- a/src/Bladeburner/Bladeburner.tsx +++ b/src/Bladeburner/Bladeburner.tsx @@ -318,9 +318,8 @@ export class Bladeburner implements IBladeburner { if (this.contracts.hasOwnProperty(name)) { action.name = name; return action; - } else { - return null; } + return null; case "operation": case "operations": case "op": @@ -329,9 +328,8 @@ export class Bladeburner implements IBladeburner { if (this.operations.hasOwnProperty(name)) { action.name = name; return action; - } else { - return null; } + return null; case "blackoperation": case "black operation": case "black operations": @@ -343,9 +341,8 @@ export class Bladeburner implements IBladeburner { if (BlackOperations.hasOwnProperty(name)) { action.name = name; return action; - } else { - return null; } + return null; case "general": case "general action": case "gen": @@ -670,15 +667,15 @@ export class Bladeburner implements IBladeburner { this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled")); this.postToConsole( "When your stamina drops to " + - formatNumber(this.automateThreshLow, 0) + - ", you will automatically switch to " + - this.automateActionLow.name + - ". When your stamina recovers to " + - formatNumber(this.automateThreshHigh, 0) + - ", you will automatically " + - "switch to " + - this.automateActionHigh.name + - ".", + formatNumber(this.automateThreshLow, 0) + + ", you will automatically switch to " + + this.automateActionLow.name + + ". When your stamina recovers to " + + formatNumber(this.automateThreshHigh, 0) + + ", you will automatically " + + "switch to " + + this.automateActionHigh.name + + ".", ); } else if (flag.toLowerCase().includes("en")) { if ( @@ -973,8 +970,8 @@ export class Bladeburner implements IBladeburner { if (this.logging.events) { this.log( "Intelligence indicates that a large number of Synthoids migrated from " + - sourceCityName + - " to some other city", + sourceCityName + + " to some other city", ); } } else if (chance <= 0.7) { @@ -1289,10 +1286,10 @@ export class Bladeburner implements IBladeburner { } else if (!isOperation && this.logging.contracts) { this.log( action.name + - " contract successfully completed! Gained " + - formatNumber(gain, 3) + - " rank and " + - numeralWrapper.formatMoney(moneyGain), + " contract successfully completed! Gained " + + formatNumber(gain, 3) + + " rank and " + + numeralWrapper.formatMoney(moneyGain), ); } } @@ -1403,11 +1400,11 @@ export class Bladeburner implements IBladeburner { if (this.logging.blackops) { this.log( action.name + - " failed! Lost " + - formatNumber(rankLoss, 1) + - " rank and took " + - formatNumber(damage, 0) + - " damage", + " failed! Lost " + + formatNumber(rankLoss, 1) + + " rank and took " + + formatNumber(damage, 0) + + " damage", ); } } @@ -1443,16 +1440,16 @@ export class Bladeburner implements IBladeburner { if (this.logging.general) { this.log( "Training completed. Gained: " + - formatNumber(strExpGain, 1) + - " str exp, " + - formatNumber(defExpGain, 1) + - " def exp, " + - formatNumber(dexExpGain, 1) + - " dex exp, " + - formatNumber(agiExpGain, 1) + - " agi exp, " + - formatNumber(staminaGain, 3) + - " max stamina", + formatNumber(strExpGain, 1) + + " str exp, " + + formatNumber(defExpGain, 1) + + " def exp, " + + formatNumber(dexExpGain, 1) + + " dex exp, " + + formatNumber(agiExpGain, 1) + + " agi exp, " + + formatNumber(staminaGain, 3) + + " max stamina", ); } this.startAction(player, this.action); // Repeat action @@ -1479,10 +1476,10 @@ export class Bladeburner implements IBladeburner { if (this.logging.general) { this.log( "Field analysis completed. Gained 0.1 rank, " + - formatNumber(hackingExpGain, 1) + - " hacking exp, and " + - formatNumber(charismaExpGain, 1) + - " charisma exp", + formatNumber(hackingExpGain, 1) + + " hacking exp, and " + + formatNumber(charismaExpGain, 1) + + " charisma exp", ); } this.startAction(player, this.action); // Repeat action @@ -1529,7 +1526,8 @@ export class Bladeburner implements IBladeburner { this.startAction(player, this.action); if (this.logging.general) { this.log( - `Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain + `Rested in Hyperbolic Regeneration Chamber. Restored ${ + BladeburnerConstants.HrcHpGain } HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`, ); } @@ -1578,7 +1576,9 @@ export class Bladeburner implements IBladeburner { if (factionExists(bladeburnersFactionName)) { const bladeburnerFac = Factions[bladeburnersFactionName]; if (!(bladeburnerFac instanceof Faction)) { - throw new Error(`Could not properly get ${FactionNames.Bladeburners} Faction object in ${FactionNames.Bladeburners} UI Overview Faction button`); + throw new Error( + `Could not properly get ${FactionNames.Bladeburners} Faction object in ${FactionNames.Bladeburners} UI Overview Faction button`, + ); } if (bladeburnerFac.isMember) { const favorBonus = 1 + bladeburnerFac.favor / 100; diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index c64957714..f4144d24f 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from "react"; import { IBladeburner } from "../IBladeburner"; +import { KEY } from "../../utils/helpers/keyCodes"; import { IPlayer } from "../../PersonObjects/IPlayer"; import Paper from "@mui/material/Paper"; @@ -76,7 +77,7 @@ export function Console(props: IProps): React.ReactElement { }, []); function handleKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) { + if (event.key === KEY.ENTER) { event.preventDefault(); if (command.length > 0) { props.bladeburner.postToConsole("> " + command); @@ -88,7 +89,7 @@ export function Console(props: IProps): React.ReactElement { const consoleHistory = props.bladeburner.consoleHistory; - if (event.keyCode === 38) { + if (event.key === KEY.S) { // up let i = consoleHistoryIndex; const len = consoleHistory.length; @@ -108,7 +109,7 @@ export function Console(props: IProps): React.ReactElement { setCommand(prevCommand); } - if (event.keyCode === 40) { + if (event.key === KEY.DOWNARROW) { const i = consoleHistoryIndex; const len = consoleHistory.length; diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index 8d3acd9d3..a639eb21e 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -64,6 +64,9 @@ export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnl if (corporation.funds < upgrade[1]) { throw new Error("Insufficient funds"); } + if(corporation.unlockUpgrades[upgrade[0]] === 1){ + throw new Error(`You have already unlocked the ${upgrade[2]} upgrade!`); + } corporation.unlock(upgrade); } diff --git a/src/Corporation/ui/BuybackSharesModal.tsx b/src/Corporation/ui/BuybackSharesModal.tsx index 4d6568af7..250443817 100644 --- a/src/Corporation/ui/BuybackSharesModal.tsx +++ b/src/Corporation/ui/BuybackSharesModal.tsx @@ -8,6 +8,7 @@ import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import { BuyBackShares } from '../Actions'; import { dialogBoxCreate } from '../../ui/React/DialogBox'; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; @@ -69,7 +70,7 @@ export function BuybackSharesModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) buy(); + if (event.key === KEY.ENTER) buy(); } return ( diff --git a/src/Corporation/ui/ExpandIndustryTab.tsx b/src/Corporation/ui/ExpandIndustryTab.tsx index 8477a8a76..74faabadf 100644 --- a/src/Corporation/ui/ExpandIndustryTab.tsx +++ b/src/Corporation/ui/ExpandIndustryTab.tsx @@ -11,6 +11,7 @@ import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; import Box from "@mui/material/Box"; import Select, { SelectChangeEvent } from "@mui/material/Select"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { setDivisionName: (name: string) => void; @@ -53,7 +54,7 @@ export function ExpandIndustryTab(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) newIndustry(); + if (event.key === KEY.ENTER) newIndustry(); } function onIndustryChange(event: SelectChangeEvent): void { diff --git a/src/Corporation/ui/GoPublicModal.tsx b/src/Corporation/ui/GoPublicModal.tsx index 5d093cf8c..6c91e1513 100644 --- a/src/Corporation/ui/GoPublicModal.tsx +++ b/src/Corporation/ui/GoPublicModal.tsx @@ -7,6 +7,7 @@ import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import Box from "@mui/material/Box"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; @@ -45,7 +46,7 @@ export function GoPublicModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) goPublic(); + if (event.key === KEY.ENTER) goPublic(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/IssueDividendsModal.tsx b/src/Corporation/ui/IssueDividendsModal.tsx index 873fd1a1f..351776dca 100644 --- a/src/Corporation/ui/IssueDividendsModal.tsx +++ b/src/Corporation/ui/IssueDividendsModal.tsx @@ -7,6 +7,7 @@ import { useCorporation } from "./Context"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; onClose: () => void; @@ -32,7 +33,7 @@ export function IssueDividendsModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) issueDividends(); + if (event.key === KEY.ENTER) issueDividends(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/IssueNewSharesModal.tsx b/src/Corporation/ui/IssueNewSharesModal.tsx index 63ccb5cb4..0022828e9 100644 --- a/src/Corporation/ui/IssueNewSharesModal.tsx +++ b/src/Corporation/ui/IssueNewSharesModal.tsx @@ -8,6 +8,7 @@ import { useCorporation } from "./Context"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IEffectTextProps { shares: number | null; @@ -93,7 +94,7 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) issueNewShares(); + if (event.key === KEY.ENTER) issueNewShares(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/LimitProductProductionModal.tsx b/src/Corporation/ui/LimitProductProductionModal.tsx index 3c5dcd96d..2d28ea75f 100644 --- a/src/Corporation/ui/LimitProductProductionModal.tsx +++ b/src/Corporation/ui/LimitProductProductionModal.tsx @@ -5,6 +5,7 @@ import { Modal } from "../../ui/React/Modal"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; @@ -25,7 +26,7 @@ export function LimitProductProductionModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) limitProductProduction(); + if (event.key === KEY.ENTER) limitProductProduction(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/MakeProductModal.tsx b/src/Corporation/ui/MakeProductModal.tsx index e040bfdfd..784e3acda 100644 --- a/src/Corporation/ui/MakeProductModal.tsx +++ b/src/Corporation/ui/MakeProductModal.tsx @@ -9,6 +9,7 @@ import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import MenuItem from "@mui/material/MenuItem"; import Select, { SelectChangeEvent } from "@mui/material/Select"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; @@ -165,7 +166,7 @@ export function MakeProductModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) makeProduct(); + if (event.key === KEY.ENTER) makeProduct(); } return ( diff --git a/src/Corporation/ui/PurchaseMaterialModal.tsx b/src/Corporation/ui/PurchaseMaterialModal.tsx index 5c6f6bda8..419176e6d 100644 --- a/src/Corporation/ui/PurchaseMaterialModal.tsx +++ b/src/Corporation/ui/PurchaseMaterialModal.tsx @@ -10,6 +10,7 @@ import { useCorporation, useDivision } from "./Context"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IBulkPurchaseTextProps { warehouse: Warehouse; @@ -68,7 +69,7 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) bulkPurchase(); + if (event.key === KEY.ENTER) bulkPurchase(); } function onChange(event: React.ChangeEvent): void { @@ -123,7 +124,7 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) purchaseMaterial(); + if (event.key === KEY.ENTER) purchaseMaterial(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Corporation/ui/SellMaterialModal.tsx b/src/Corporation/ui/SellMaterialModal.tsx index 366507aa9..83a36d314 100644 --- a/src/Corporation/ui/SellMaterialModal.tsx +++ b/src/Corporation/ui/SellMaterialModal.tsx @@ -6,6 +6,7 @@ import { Modal } from "../../ui/React/Modal"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; function initialPrice(mat: Material): string { let val = mat.sCost ? mat.sCost + "" : ""; @@ -46,7 +47,7 @@ export function SellMaterialModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) sellMaterial(); + if (event.key === KEY.ENTER) sellMaterial(); } return ( diff --git a/src/Corporation/ui/SellProductModal.tsx b/src/Corporation/ui/SellProductModal.tsx index 5b383a583..00026a5b0 100644 --- a/src/Corporation/ui/SellProductModal.tsx +++ b/src/Corporation/ui/SellProductModal.tsx @@ -9,6 +9,7 @@ import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import FormControlLabel from "@mui/material/FormControlLabel"; import Switch from "@mui/material/Switch"; +import { KEY } from "../../utils/helpers/keyCodes"; function initialPrice(product: Product): string { let val = product.sCost ? product.sCost + "" : ""; @@ -58,7 +59,7 @@ export function SellProductModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) sellProduct(); + if (event.key === KEY.ENTER) sellProduct(); } return ( diff --git a/src/Corporation/ui/SellSharesModal.tsx b/src/Corporation/ui/SellSharesModal.tsx index e96fa9959..a6a36aa0d 100644 --- a/src/Corporation/ui/SellSharesModal.tsx +++ b/src/Corporation/ui/SellSharesModal.tsx @@ -10,6 +10,7 @@ import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; import { Money } from "../../ui/React/Money"; import { SellShares } from "../Actions"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; onClose: () => void; @@ -68,7 +69,7 @@ export function SellSharesModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) sell(); + if (event.key === KEY.ENTER) sell(); } return ( diff --git a/src/Corporation/ui/ThrowPartyModal.tsx b/src/Corporation/ui/ThrowPartyModal.tsx index 8b89c4506..c3e2958cb 100644 --- a/src/Corporation/ui/ThrowPartyModal.tsx +++ b/src/Corporation/ui/ThrowPartyModal.tsx @@ -10,6 +10,7 @@ import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; import Box from "@mui/material/Box"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; @@ -57,7 +58,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement { } function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) throwParty(); + if (event.key === KEY.ENTER) throwParty(); } return ( diff --git a/src/CotMG/FragmentType.ts b/src/CotMG/FragmentType.ts index 7928f0492..8c38b7285 100644 --- a/src/CotMG/FragmentType.ts +++ b/src/CotMG/FragmentType.ts @@ -29,67 +29,51 @@ export function Effect(tpe: FragmentType): string { switch (tpe) { case FragmentType.HackingChance: { return "+x% hack() success chance"; - break; } case FragmentType.HackingSpeed: { return "+x% faster hack(), grow(), and weaken()"; - break; } case FragmentType.HackingMoney: { return "+x% hack() power"; - break; } case FragmentType.HackingGrow: { return "+x% grow() power"; - break; } case FragmentType.Hacking: { return "+x% hacking skill"; - break; } case FragmentType.Strength: { return "+x% strength skill"; - break; } case FragmentType.Defense: { return "+x% defense skill"; - break; } case FragmentType.Dexterity: { return "+x% dexterity skill"; - break; } case FragmentType.Agility: { return "+x% agility skill"; - break; } case FragmentType.Charisma: { return "+x% charisma skill"; - break; } case FragmentType.HacknetMoney: { return "+x% hacknet production"; - break; } case FragmentType.HacknetCost: { return "x% cheaper hacknet cost"; - break; } case FragmentType.Rep: { return "+x% reputation from factions and companies"; - break; } case FragmentType.WorkMoney: { return "+x% work money"; - break; } case FragmentType.Crime: { return "+x% crime money"; - break; } case FragmentType.Bladeburner: { return "+x% all bladeburner stats"; - break; } } throw new Error("Calling effect for fragment type that doesn't have an effect " + tpe); diff --git a/src/Faction/ui/CreateGangModal.tsx b/src/Faction/ui/CreateGangModal.tsx index 5d023376c..60d788494 100644 --- a/src/Faction/ui/CreateGangModal.tsx +++ b/src/Faction/ui/CreateGangModal.tsx @@ -6,6 +6,7 @@ import { Modal } from "../../ui/React/Modal"; import { use } from "../../ui/Context"; import Typography from "@mui/material/Typography"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; import { FactionNames } from "../data/FactionNames"; interface IProps { @@ -38,7 +39,7 @@ export function CreateGangModal(props: IProps): React.ReactElement { } function onKeyUp(event: React.KeyboardEvent): void { - if (event.keyCode === 13) createGang(); + if (event.key === KEY.ENTER) createGang(); } return ( diff --git a/src/Gang/ui/GangMemberStats.tsx b/src/Gang/ui/GangMemberStats.tsx index 66fe16582..9ea5ae01a 100644 --- a/src/Gang/ui/GangMemberStats.tsx +++ b/src/Gang/ui/GangMemberStats.tsx @@ -7,12 +7,7 @@ import { useGang } from "./Context"; import Typography from "@mui/material/Typography"; import Tooltip from "@mui/material/Tooltip"; -import { - Table, - TableBody, - TableCell, - TableRow, -} from "@mui/material"; +import { Table, TableBody, TableCell, TableRow } from "@mui/material"; import { numeralWrapper } from "../../ui/numeralFormat"; import { GangMember } from "../GangMember"; @@ -37,8 +32,6 @@ export function GangMemberStats(props: IProps): React.ReactElement { cha: props.member.calculateAscensionMult(props.member.cha_asc_points), }; - - const gang = useGang(); const data = [ [`Money:`, ], @@ -78,14 +71,38 @@ export function GangMemberStats(props: IProps): React.ReactElement { } > - +
- - - - - - + + + + + +
diff --git a/src/Gang/ui/RecruitModal.tsx b/src/Gang/ui/RecruitModal.tsx index c5096ba74..4c81c36fc 100644 --- a/src/Gang/ui/RecruitModal.tsx +++ b/src/Gang/ui/RecruitModal.tsx @@ -8,6 +8,7 @@ import { useGang } from "./Context"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IRecruitPopupProps { open: boolean; @@ -34,7 +35,7 @@ export function RecruitModal(props: IRecruitPopupProps): React.ReactElement { } function onKeyUp(event: React.KeyboardEvent): void { - if (event.keyCode === 13) recruit(); + if (event.key === KEY.ENTER) recruit(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Gang/ui/TaskSelector.tsx b/src/Gang/ui/TaskSelector.tsx index ff19ff550..4c779dc31 100644 --- a/src/Gang/ui/TaskSelector.tsx +++ b/src/Gang/ui/TaskSelector.tsx @@ -21,6 +21,13 @@ export function TaskSelector(props: IProps): React.ReactElement { const gang = useGang(); const [currentTask, setCurrentTask] = useState(props.member.task); + const contextMember = gang.members.find(member => member.name == props.member.name) + if (contextMember && + contextMember.task != currentTask + ) { + setCurrentTask(contextMember.task) + } + function onChange(event: SelectChangeEvent): void { const task = event.target.value; props.member.assignToTask(task); diff --git a/src/Locations/ui/PurchaseServerModal.tsx b/src/Locations/ui/PurchaseServerModal.tsx index d0f8ccf44..ed54a7faa 100644 --- a/src/Locations/ui/PurchaseServerModal.tsx +++ b/src/Locations/ui/PurchaseServerModal.tsx @@ -10,6 +10,7 @@ import { use } from "../../ui/Context"; import Typography from "@mui/material/Typography"; import TextField from "@mui/material/TextField"; import Button from "@mui/material/Button"; +import { KEY } from "../../utils/helpers/keyCodes"; interface IProps { open: boolean; @@ -29,7 +30,7 @@ export function PurchaseServerModal(props: IProps): React.ReactElement { } function onKeyUp(event: React.KeyboardEvent): void { - if (event.keyCode === 13) tryToPurchaseServer(); + if (event.key === KEY.ENTER) tryToPurchaseServer(); } function onChange(event: React.ChangeEvent): void { diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 70a6c2b0f..af50c8c5c 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -232,6 +232,8 @@ export const RamCosts: IMap = { connect: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost), manualHack: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost), installBackdoor: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost), + getDarkwebProgramCost: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost / 4), + getDarkwebPrograms: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost / 4), getStats: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost / 4), getCharacterInformation: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost / 4), hospitalize: SF4Cost(RamCostConstants.ScriptSingularityFn1RamCost / 4), diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index ca35bf3fd..65a4b3d2f 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -144,7 +144,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { const safeGetServer = function (hostname: string, callingFnName: string): BaseServer { const server = GetServer(hostname); if (server == null) { - throw makeRuntimeErrorMsg(callingFnName, `Invalid hostname or IP: ${hostname}`); + throw makeRuntimeErrorMsg(callingFnName, `Invalid hostname: ${hostname}`); } return server; }; @@ -539,6 +539,10 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { const percentHacked = calculatePercentMoneyHacked(server, Player); + if (percentHacked === 0 || server.moneyAvailable === 0) { + return 0; // To prevent returning infinity below + } + return hackAmount / Math.floor(server.moneyAvailable * percentHacked); }, hackAnalyze: function (hostname: any): any { diff --git a/src/NetscriptFunctions/Corporation.ts b/src/NetscriptFunctions/Corporation.ts index e20fc21bb..b9ae2f0b5 100644 --- a/src/NetscriptFunctions/Corporation.ts +++ b/src/NetscriptFunctions/Corporation.ts @@ -291,7 +291,7 @@ export function NetscriptCorporation( lastCycleExpenses: division.lastCycleExpenses, thisCycleRevenue: division.thisCycleRevenue, thisCycleExpenses: division.thisCycleExpenses, - upgrades: division.upgrades, + upgrades: division.upgrades.slice(), cities: cities, products: division.products === undefined ? [] : Object.keys(division.products), }; @@ -846,5 +846,9 @@ export function NetscriptCorporation( const amountShares = helper.number("bribe", "amountShares", aamountShares); return bribe(factionName, amountCash, amountShares); }, + getBonusTime: function (): number { + checkAccess("getBonusTime"); + return Math.round(getCorporation().storedCycles / 5) * 1000; + } }; } diff --git a/src/NetscriptFunctions/Extra.ts b/src/NetscriptFunctions/Extra.ts index 61d4ba557..018c2b41f 100644 --- a/src/NetscriptFunctions/Extra.ts +++ b/src/NetscriptFunctions/Extra.ts @@ -3,7 +3,6 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { Exploit } from "../Exploits/Exploit"; import * as bcrypt from "bcryptjs"; import { INetscriptHelper } from "./INetscriptHelper"; -import { Augmentations } from "../Augmentation/Augmentations"; export interface INetscriptExtra { heart: { diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 92c89d12e..4ab802921 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -43,6 +43,7 @@ import { calculateHackingTime } from "../Hacking"; import { Server } from "../Server/Server"; import { netscriptCanHack } from "../Hacking/netscriptCanHack"; import { FactionNames } from "../Faction/data/FactionNames"; +import { FactionInfos } from "../Faction/FactionInfo"; export function NetscriptSingularity( player: IPlayer, @@ -370,7 +371,8 @@ export function NetscriptSingularity( if (player.city != CityName.Aevum) { workerScript.log( "gymWorkout", - () => `You cannot workout at '${LocationName.AevumCrushFitnessGym}' because you are not in '${CityName.Aevum}'.`, + () => + `You cannot workout at '${LocationName.AevumCrushFitnessGym}' because you are not in '${CityName.Aevum}'.`, ); return false; } @@ -382,7 +384,8 @@ export function NetscriptSingularity( if (player.city != CityName.Aevum) { workerScript.log( "gymWorkout", - () => `You cannot workout at '${LocationName.AevumSnapFitnessGym}' because you are not in '${CityName.Aevum}'.`, + () => + `You cannot workout at '${LocationName.AevumSnapFitnessGym}' because you are not in '${CityName.Aevum}'.`, ); return false; } @@ -394,7 +397,8 @@ export function NetscriptSingularity( if (player.city != CityName.Sector12) { workerScript.log( "gymWorkout", - () => `You cannot workout at '${LocationName.Sector12IronGym}' because you are not in '${CityName.Sector12}'.`, + () => + `You cannot workout at '${LocationName.Sector12IronGym}' because you are not in '${CityName.Sector12}'.`, ); return false; } @@ -406,7 +410,8 @@ export function NetscriptSingularity( if (player.city != CityName.Sector12) { workerScript.log( "gymWorkout", - () => `You cannot workout at '${LocationName.Sector12PowerhouseGym}' because you are not in '${CityName.Sector12}'.`, + () => + `You cannot workout at '${LocationName.Sector12PowerhouseGym}' because you are not in '${CityName.Sector12}'.`, ); return false; } @@ -418,7 +423,8 @@ export function NetscriptSingularity( if (player.city != CityName.Volhaven) { workerScript.log( "gymWorkout", - () => `You cannot workout at '${LocationName.VolhavenMilleniumFitnessGym}' because you are not in '${CityName.Volhaven}'.`, + () => + `You cannot workout at '${LocationName.VolhavenMilleniumFitnessGym}' because you are not in '${CityName.Volhaven}'.`, ); return false; } @@ -476,7 +482,7 @@ export function NetscriptSingularity( case CityName.Volhaven: if (player.money < CONSTANTS.TravelCost) { workerScript.log("travelToCity", () => "Not enough money to travel."); - return false + return false; } player.loseMoney(CONSTANTS.TravelCost, "other"); player.city = cityname; @@ -1033,93 +1039,12 @@ export function NetscriptSingularity( const fac = Factions[name]; // Arrays listing factions that allow each time of work - const hackAvailable = [ - FactionNames.Illuminati as string, - FactionNames.Daedalus as string, - FactionNames.TheCovenant as string, - FactionNames.ECorp as string, - FactionNames.MegaCorp as string, - FactionNames.BachmanAssociates as string, - FactionNames.Bladeburners as string, - FactionNames.NWO as string, - FactionNames.ClarkeIncorporated as string, - FactionNames.OmniTekIncorporated as string, - FactionNames.FourSigma as string, - FactionNames.KuaiGongInternational as string, - FactionNames.FulcrumSecretTechnologies as string, - FactionNames.BitRunners as string, - FactionNames.TheBlackHand as string, - FactionNames.NiteSec as string, - FactionNames.Chongqing as string, - FactionNames.Sector12 as string, - FactionNames.NewTokyo as string, - FactionNames.Aevum as string, - FactionNames.Ishima as string, - FactionNames.Volhaven as string, - FactionNames.SpeakersForTheDead as string, - FactionNames.TheDarkArmy as string, - FactionNames.TheSyndicate as string, - FactionNames.Silhouette as string, - FactionNames.Netburners as string, - FactionNames.TianDiHui as string, - FactionNames.CyberSec as string, - ]; - const fdWkAvailable = [ - FactionNames.Illuminati as string, - FactionNames.Daedalus as string, - FactionNames.TheCovenant as string, - FactionNames.ECorp as string, - FactionNames.MegaCorp as string, - FactionNames.BachmanAssociates as string, - FactionNames.Bladeburners as string, - FactionNames.NWO as string, - FactionNames.ClarkeIncorporated as string, - FactionNames.OmniTekIncorporated as string, - FactionNames.FourSigma as string, - FactionNames.KuaiGongInternational as string, - FactionNames.TheBlackHand as string, - FactionNames.Chongqing as string, - FactionNames.Sector12 as string, - FactionNames.NewTokyo as string, - FactionNames.Aevum as string, - FactionNames.Ishima as string, - FactionNames.Volhaven as string, - FactionNames.SpeakersForTheDead as string, - FactionNames.TheDarkArmy as string, - FactionNames.TheSyndicate as string, - FactionNames.Silhouette as string, - FactionNames.Tetrads as string, - FactionNames.SlumSnakes as string, - ]; - const scWkAvailable = [ - FactionNames.ECorp as string, - FactionNames.MegaCorp as string, - FactionNames.BachmanAssociates as string, - FactionNames.Bladeburners as string, - FactionNames.NWO as string, - FactionNames.ClarkeIncorporated as string, - FactionNames.OmniTekIncorporated as string, - FactionNames.FourSigma as string, - FactionNames.KuaiGongInternational as string, - FactionNames.FulcrumSecretTechnologies as string, - FactionNames.Chongqing as string, - FactionNames.Sector12 as string, - FactionNames.NewTokyo as string, - FactionNames.Aevum as string, - FactionNames.Ishima as string, - FactionNames.Volhaven as string, - FactionNames.SpeakersForTheDead as string, - FactionNames.TheSyndicate as string, - FactionNames.Tetrads as string, - FactionNames.SlumSnakes as string, - FactionNames.TianDiHui as string, - ]; switch (type.toLowerCase()) { case "hacking": case "hacking contracts": case "hackingcontracts": - if (!hackAvailable.includes(fac.name)) { + if (!FactionInfos[fac.name].offerHackingWork) { workerScript.log("workForFaction", () => `Faction '${fac.name}' do not need help with hacking contracts.`); return false; } @@ -1136,7 +1061,7 @@ export function NetscriptSingularity( case "field": case "fieldwork": case "field work": - if (!fdWkAvailable.includes(fac.name)) { + if (!FactionInfos[fac.name].offerFieldWork) { workerScript.log("workForFaction", () => `Faction '${fac.name}' do not need help with field missions.`); return false; } @@ -1153,7 +1078,7 @@ export function NetscriptSingularity( case "security": case "securitywork": case "security work": - if (!scWkAvailable.includes(fac.name)) { + if (!FactionInfos[fac.name].offerSecurityWork) { workerScript.log("workForFaction", () => `Faction '${fac.name}' do not need help with security work.`); return false; } @@ -1325,5 +1250,49 @@ export function NetscriptSingularity( return Object.assign({}, crime); }, + getDarkwebPrograms: function (): string[] { + helper.updateDynamicRam("getDarkwebPrograms", getRamCost(player, "getDarkwebPrograms")); + helper.checkSingularityAccess("getDarkwebPrograms"); + + // If we don't have Tor, log it and return [] (empty list) + if (!player.hasTorRouter()) { + workerScript.log("getDarkwebPrograms", () => "You do not have the TOR router."); + return []; + } + return Object.values(DarkWebItems).map((p) => p.program); + }, + getDarkwebProgramCost: function (programName: any): any { + helper.updateDynamicRam("getDarkwebProgramCost", getRamCost(player, "getDarkwebProgramCost")); + helper.checkSingularityAccess("getDarkwebProgramCost"); + + // If we don't have Tor, log it and return -1 + if (!player.hasTorRouter()) { + workerScript.log("getDarkwebProgramCost", () => "You do not have the TOR router."); + // returning -1 rather than throwing an error to be consistent with purchaseProgram + // which returns false if tor has + return -1; + } + + programName = programName.toLowerCase(); + const item = Object.values(DarkWebItems).find((i) => i.program.toLowerCase() === programName); + + // If the program doesn't exist, throw an error. The reasoning here is that the 99% case is that + // the player will be using this in automation scripts, and if they're asking for a program that + // doesn't exist, it's the first time they've run the script. So throw an error to let them know + // that they need to fix it. + if (item == null) { + throw helper.makeRuntimeErrorMsg( + "getDarkwebProgramCost", + `No such exploit ('${programName}') found on the darkweb! ` + + `\nThis function is not case-sensitive. Did you perhaps forget .exe at the end?`, + ); + } + + if (player.hasProgram(item.program)) { + workerScript.log("getDarkwebProgramCost", () => `You already have the '${item.program}' program`); + return 0; + } + return item.price; + }, }; } diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index 2246f61f7..4f7669119 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -30,6 +30,20 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel } }; + const getSleeveStats = function (sleeveNumber: any): any { + const sl = player.sleeves[sleeveNumber]; + return { + shock: 100 - sl.shock, + sync: sl.sync, + hacking: sl.hacking, + strength: sl.strength, + defense: sl.defense, + dexterity: sl.dexterity, + agility: sl.agility, + charisma: sl.charisma, + }; + } + return { getNumSleeves: function (): number { helper.updateDynamicRam("getNumSleeves", getRamCost(player, "sleeve", "getNumSleeves")); @@ -150,18 +164,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel helper.updateDynamicRam("getSleeveStats", getRamCost(player, "sleeve", "getSleeveStats")); checkSleeveAPIAccess("getSleeveStats"); checkSleeveNumber("getSleeveStats", sleeveNumber); - - const sl = player.sleeves[sleeveNumber]; - return { - shock: 100 - sl.shock, - sync: sl.sync, - hacking: sl.hacking, - strength: sl.strength, - defense: sl.defense, - dexterity: sl.dexterity, - agility: sl.agility, - charisma: sl.charisma, - }; + return getSleeveStats(sleeveNumber) }, getTask: function (asleeveNumber: any = 0): { task: string; @@ -289,7 +292,7 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel checkSleeveAPIAccess("purchaseSleeveAug"); checkSleeveNumber("purchaseSleeveAug", sleeveNumber); - if (player.sleeves[sleeveNumber].shock > 0){ + if (getSleeveStats(sleeveNumber).shock > 0) { throw helper.makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Sleeve shock too high: Sleeve ${sleeveNumber}`); } diff --git a/src/NetscriptFunctions/StockMarket.ts b/src/NetscriptFunctions/StockMarket.ts index 7e0e64f60..52fac413c 100644 --- a/src/NetscriptFunctions/StockMarket.ts +++ b/src/NetscriptFunctions/StockMarket.ts @@ -8,7 +8,12 @@ import { getBuyTransactionCost, getSellTransactionGain } from "../StockMarket/St import { OrderTypes } from "../StockMarket/data/OrderTypes"; import { PositionTypes } from "../StockMarket/data/PositionTypes"; import { StockSymbols } from "../StockMarket/data/StockSymbols"; -import { getStockMarket4SDataCost, getStockMarket4STixApiCost, getStockMarketWseCost, getStockMarketTixApiCost } from "../StockMarket/StockMarketCosts"; +import { + getStockMarket4SDataCost, + getStockMarket4STixApiCost, + getStockMarketWseCost, + getStockMarketTixApiCost, +} from "../StockMarket/StockMarketCosts"; import { Stock } from "../StockMarket/Stock"; import { TIX } from "../ScriptEditor/NetscriptDefinitions"; @@ -40,32 +45,32 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return Object.values(StockSymbols); }, getPrice: function (_symbol: unknown): number { - const symbol = helper.string("getPrice", "symbol", _symbol); helper.updateDynamicRam("getPrice", getRamCost(player, "stock", "getPrice")); + const symbol = helper.string("getPrice", "symbol", _symbol); checkTixApiAccess("getPrice"); const stock = getStockFromSymbol(symbol, "getPrice"); return stock.price; }, getAskPrice: function (_symbol: unknown): number { - const symbol = helper.string("getAskPrice", "symbol", _symbol); helper.updateDynamicRam("getAskPrice", getRamCost(player, "stock", "getAskPrice")); + const symbol = helper.string("getAskPrice", "symbol", _symbol); checkTixApiAccess("getAskPrice"); const stock = getStockFromSymbol(symbol, "getAskPrice"); return stock.getAskPrice(); }, getBidPrice: function (_symbol: unknown): number { - const symbol = helper.string("getBidPrice", "symbol", _symbol); helper.updateDynamicRam("getBidPrice", getRamCost(player, "stock", "getBidPrice")); + const symbol = helper.string("getBidPrice", "symbol", _symbol); checkTixApiAccess("getBidPrice"); const stock = getStockFromSymbol(symbol, "getBidPrice"); return stock.getBidPrice(); }, getPosition: function (_symbol: unknown): [number, number, number, number] { - const symbol = helper.string("getPosition", "symbol", _symbol); helper.updateDynamicRam("getPosition", getRamCost(player, "stock", "getPosition")); + const symbol = helper.string("getPosition", "symbol", _symbol); checkTixApiAccess("getPosition"); const stock = SymbolToStockMap[symbol]; if (stock == null) { @@ -74,18 +79,18 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx]; }, getMaxShares: function (_symbol: unknown): number { - const symbol = helper.string("getMaxShares", "symbol", _symbol); helper.updateDynamicRam("getMaxShares", getRamCost(player, "stock", "getMaxShares")); + const symbol = helper.string("getMaxShares", "symbol", _symbol); checkTixApiAccess("getMaxShares"); const stock = getStockFromSymbol(symbol, "getMaxShares"); return stock.maxShares; }, getPurchaseCost: function (_symbol: unknown, _shares: unknown, _posType: unknown): number { + helper.updateDynamicRam("getPurchaseCost", getRamCost(player, "stock", "getPurchaseCost")); const symbol = helper.string("getPurchaseCost", "symbol", _symbol); let shares = helper.number("getPurchaseCost", "shares", _shares); const posType = helper.string("getPurchaseCost", "posType", _posType); - helper.updateDynamicRam("getPurchaseCost", getRamCost(player, "stock", "getPurchaseCost")); checkTixApiAccess("getPurchaseCost"); const stock = getStockFromSymbol(symbol, "getPurchaseCost"); shares = Math.round(shares); @@ -108,10 +113,10 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return res; }, getSaleGain: function (_symbol: unknown, _shares: unknown, _posType: unknown): number { + helper.updateDynamicRam("getSaleGain", getRamCost(player, "stock", "getSaleGain")); const symbol = helper.string("getSaleGain", "symbol", _symbol); let shares = helper.number("getSaleGain", "shares", _shares); const posType = helper.string("getSaleGain", "posType", _posType); - helper.updateDynamicRam("getSaleGain", getRamCost(player, "stock", "getSaleGain")); checkTixApiAccess("getSaleGain"); const stock = getStockFromSymbol(symbol, "getSaleGain"); shares = Math.round(shares); @@ -134,18 +139,18 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return res; }, buy: function (_symbol: unknown, _shares: unknown): number { + helper.updateDynamicRam("buy", getRamCost(player, "stock", "buy")); const symbol = helper.string("buy", "symbol", _symbol); const shares = helper.number("buy", "shares", _shares); - helper.updateDynamicRam("buy", getRamCost(player, "stock", "buy")); checkTixApiAccess("buy"); const stock = getStockFromSymbol(symbol, "buy"); const res = buyStock(stock, shares, workerScript, {}); return res ? stock.getAskPrice() : 0; }, sell: function (_symbol: unknown, _shares: unknown): number { + helper.updateDynamicRam("sell", getRamCost(player, "stock", "sell")); const symbol = helper.string("sell", "symbol", _symbol); const shares = helper.number("sell", "shares", _shares); - helper.updateDynamicRam("sell", getRamCost(player, "stock", "sell")); checkTixApiAccess("sell"); const stock = getStockFromSymbol(symbol, "sell"); const res = sellStock(stock, shares, workerScript, {}); @@ -153,9 +158,9 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return res ? stock.getBidPrice() : 0; }, short: function (_symbol: unknown, _shares: unknown): number { + helper.updateDynamicRam("short", getRamCost(player, "stock", "short")); const symbol = helper.string("short", "symbol", _symbol); const shares = helper.number("short", "shares", _shares); - helper.updateDynamicRam("short", getRamCost(player, "stock", "short")); checkTixApiAccess("short"); if (player.bitNodeN !== 8) { if (player.sourceFileLvl(8) <= 1) { @@ -171,9 +176,9 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return res ? stock.getBidPrice() : 0; }, sellShort: function (_symbol: unknown, _shares: unknown): number { + helper.updateDynamicRam("sellShort", getRamCost(player, "stock", "sellShort")); const symbol = helper.string("sellShort", "symbol", _symbol); const shares = helper.number("sellShort", "shares", _shares); - helper.updateDynamicRam("sellShort", getRamCost(player, "stock", "sellShort")); checkTixApiAccess("sellShort"); if (player.bitNodeN !== 8) { if (player.sourceFileLvl(8) <= 1) { @@ -189,12 +194,12 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return res ? stock.getAskPrice() : 0; }, placeOrder: function (_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean { + helper.updateDynamicRam("placeOrder", getRamCost(player, "stock", "placeOrder")); const symbol = helper.string("placeOrder", "symbol", _symbol); const shares = helper.number("placeOrder", "shares", _shares); const price = helper.number("placeOrder", "price", _price); const type = helper.string("placeOrder", "type", _type); const pos = helper.string("placeOrder", "pos", _pos); - helper.updateDynamicRam("placeOrder", getRamCost(player, "stock", "placeOrder")); checkTixApiAccess("placeOrder"); if (player.bitNodeN !== 8) { if (player.sourceFileLvl(8) <= 2) { @@ -239,12 +244,12 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript _type: unknown, _pos: unknown, ): boolean { + helper.updateDynamicRam("cancelOrder", getRamCost(player, "stock", "cancelOrder")); const symbol = helper.string("cancelOrder", "symbol", _symbol); const shares = helper.number("cancelOrder", "shares", _shares); const price = helper.number("cancelOrder", "price", _price); const type = helper.string("cancelOrder", "type", _type); const pos = helper.string("cancelOrder", "pos", _pos); - helper.updateDynamicRam("cancelOrder", getRamCost(player, "stock", "cancelOrder")); checkTixApiAccess("cancelOrder"); if (player.bitNodeN !== 8) { if (player.sourceFileLvl(8) <= 2) { @@ -326,8 +331,8 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return orders; }, getVolatility: function (_symbol: unknown): number { - const symbol = helper.string("getVolatility", "symbol", _symbol); helper.updateDynamicRam("getVolatility", getRamCost(player, "stock", "getVolatility")); + const symbol = helper.string("getVolatility", "symbol", _symbol); if (!player.has4SDataTixApi) { throw helper.makeRuntimeErrorMsg("getVolatility", "You don't have 4S Market Data TIX API Access!"); } @@ -336,8 +341,8 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return stock.mv / 100; // Convert from percentage to decimal }, getForecast: function (_symbol: unknown): number { - const symbol = helper.string("getForecast", "symbol", _symbol); helper.updateDynamicRam("getForecast", getRamCost(player, "stock", "getForecast")); + const symbol = helper.string("getForecast", "symbol", _symbol); if (!player.has4SDataTixApi) { throw helper.makeRuntimeErrorMsg("getForecast", "You don't have 4S Market Data TIX API Access!"); } @@ -397,10 +402,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript } if (player.money < getStockMarketWseCost()) { - workerScript.log( - "stock.purchaseWseAccount", - () => "Not enough money to purchase WSE Account Access", - ); + workerScript.log("stock.purchaseWseAccount", () => "Not enough money to purchase WSE Account Access"); return false; } @@ -418,10 +420,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript } if (player.money < getStockMarketTixApiCost()) { - workerScript.log( - "stock.purchaseTixApi", - () => "Not enough money to purchase TIX API Access", - ); + workerScript.log("stock.purchaseTixApi", () => "Not enough money to purchase TIX API Access"); return false; } diff --git a/src/NetscriptFunctions/UserInterface.ts b/src/NetscriptFunctions/UserInterface.ts index 7b342a9a7..69e7de09b 100644 --- a/src/NetscriptFunctions/UserInterface.ts +++ b/src/NetscriptFunctions/UserInterface.ts @@ -28,7 +28,7 @@ export function NetscriptUserInterface( setTheme: function (newTheme: UserInterfaceTheme): void { helper.updateDynamicRam("setTheme", getRamCost(player, "ui", "setTheme")); - const hex = /^(#)((?:[A-Fa-f0-9]{3}){1,2})$/; + const hex = /^(#)((?:[A-Fa-f0-9]{2}){3,4}|(?:[A-Fa-f0-9]{3}))$/; const currentTheme = {...Settings.theme} const errors: string[] = []; for (const key of Object.keys(newTheme)) { diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index c561b5a9d..088b55b4d 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -178,7 +178,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise`; + msg += "
"; + msg += errorMsg; + dialogBoxCreate(msg); + workerScript.env.stopFlag = true; + workerScript.running = false; + killWorkerScript(workerScript); + return Promise.resolve(workerScript); + } }); }; int.setProperty(scope, name, int.createAsyncFunction(tempWrapper)); @@ -725,19 +739,17 @@ export function runScriptFromScript( `Cannot run script '${scriptname}' (t=${threads}) on '${server.hostname}' because there is not enough available RAM!`, ); return 0; - } else { - // Able to run script - workerScript.log( - caller, - () => `'${scriptname}' on '${server.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`, - ); - const runningScriptObj = new RunningScript(script, args); - runningScriptObj.threads = threads; - runningScriptObj.server = server.hostname; - - return startWorkerScript(player, runningScriptObj, server, workerScript); } - break; + // Able to run script + workerScript.log( + caller, + () => `'${scriptname}' on '${server.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`, + ); + const runningScriptObj = new RunningScript(script, args); + runningScriptObj.threads = threads; + runningScriptObj.server = server.hostname; + + return startWorkerScript(player, runningScriptObj, server, workerScript); } workerScript.log(caller, () => `Could not find script '${scriptname}' on '${server.hostname}'`); diff --git a/src/PersonObjects/formulas/reputation.ts b/src/PersonObjects/formulas/reputation.ts index 09d353817..e0db746c5 100644 --- a/src/PersonObjects/formulas/reputation.ts +++ b/src/PersonObjects/formulas/reputation.ts @@ -25,26 +25,19 @@ export function getHackingWorkRepGain(p: IPlayer, f: Faction): number { export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number { const t = (0.9 * - (p.hacking / CONSTANTS.MaxSkillLevel + - p.strength / CONSTANTS.MaxSkillLevel + - p.defense / CONSTANTS.MaxSkillLevel + - p.dexterity / CONSTANTS.MaxSkillLevel + - p.agility / CONSTANTS.MaxSkillLevel + - p.intelligence / CONSTANTS.MaxSkillLevel)) / - 4.5; + (p.strength + p.defense + p.dexterity + p.agility + + (p.hacking + p.intelligence) * CalculateShareMult() + ) + ) / CONSTANTS.MaxSkillLevel / 4.5; return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1); } export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number { const t = (0.9 * - (p.hacking / CONSTANTS.MaxSkillLevel + - p.strength / CONSTANTS.MaxSkillLevel + - p.defense / CONSTANTS.MaxSkillLevel + - p.dexterity / CONSTANTS.MaxSkillLevel + - p.agility / CONSTANTS.MaxSkillLevel + - p.charisma / CONSTANTS.MaxSkillLevel + - p.intelligence / CONSTANTS.MaxSkillLevel)) / - 5.5; + (p.strength + p.defense + p.dexterity + p.agility + p.charisma + + (p.hacking + p.intelligence) * CalculateShareMult() + ) + ) / CONSTANTS.MaxSkillLevel / 5.5; return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1); } diff --git a/src/Script/RamCalculations.ts b/src/Script/RamCalculations.ts index 7608f407b..0bb432581 100644 --- a/src/Script/RamCalculations.ts +++ b/src/Script/RamCalculations.ts @@ -427,6 +427,4 @@ export async function calculateRamUsage( console.error(e); return { cost: RamCalculationErrorCode.SyntaxError }; } - - return { cost: RamCalculationErrorCode.SyntaxError }; } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index ca5a20644..a062ddd29 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -2289,6 +2289,67 @@ export interface Singularity { * @returns True if the focus was changed. */ setFocus(focus: boolean): boolean; + + /** + * Get a list of programs offered on the dark web. + * @remarks + * RAM cost: 1 GB * 16/4/1 + * + * + * This function allows the player to get a list of programs available for purchase + * on the dark web. Players MUST have purchased Tor to get the list of programs + * available. If Tor has not been purchased yet, this function will return an + * empty list. + * + * @example + * ```ts + * // NS1 + * getDarkwebProgramsAvailable(); + * // returns ['BruteSSH.exe', 'FTPCrack.exe'...etc] + * ``` + * @example + * ```ts + * // NS2 + * ns.getDarkwebProgramsAvailable(); + * // returns ['BruteSSH.exe', 'FTPCrack.exe'...etc] + * ``` + * @returns - a list of programs available for purchase on the dark web, or [] if Tor has not + * been purchased + */ + getDarkwebPrograms(): string[]; + + /** + * Check the price of an exploit on the dark web + * @remarks + * RAM cost: 0.5 GB * 16/4/1 + * + * + * This function allows you to check the price of a darkweb exploit/program. + * You MUST have a TOR router in order to use this function. The price returned + * by this function is the same price you would see with buy -l from the terminal. + * Returns the cost of the program if it has not been purchased yet, 0 if it + * has already been purchased, or -1 if Tor has not been purchased (and thus + * the program/exploit is not available for purchase). + * + * If the program does not exist, an error is thrown. + * + * + * @example + * ```ts + * // NS1 + * getDarkwebProgramCost("brutessh.exe"); + * ``` + * @example + * ```ts + * // NS2 + * ns.getDarkwebProgramCost("brutessh.exe"); + * ``` + * @param programName - Name of program to check the price of + * @returns Price of the specified darkweb program + * (if not yet purchased), 0 if it has already been purchased, or -1 if Tor has not been + * purchased. Throws an error if the specified program/exploit does not exist + */ + getDarkwebProgramCost(programName: string): number; } /** @@ -6665,6 +6726,16 @@ export interface Corporation extends WarehouseAPI, OfficeAPI { * */ sellShares(amount: number): void; + /** + * Get bonus time. + * + * “Bonus time” is accumulated when the game is offline or if the game is inactive in the browser. + * + * “Bonus time” makes the game progress faster. + * + * @returns Bonus time for the Corporation mechanic in milliseconds. + */ + getBonusTime(): number; } /** diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index 42a8b6150..47355e557 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -42,8 +42,11 @@ function GetServerByHostname(hostname: string): BaseServer | null { //Get server by IP or hostname. Returns null if invalid export function GetServer(s: string): BaseServer | null { - const server = AllServers[s]; - if (server) return server; + if (AllServers.hasOwnProperty(s)) { + const server = AllServers[s]; + if (server) return server; + } + if (!isValidIPAddress(s)) { return GetServerByHostname(s); } diff --git a/src/Server/ServerHelpers.ts b/src/Server/ServerHelpers.ts index 7e8459e9d..fdd62bbaf 100644 --- a/src/Server/ServerHelpers.ts +++ b/src/Server/ServerHelpers.ts @@ -66,6 +66,199 @@ export function numCycleForGrowth(server: Server, growth: number, p: IPlayer, co return cycles; } +/** + * Replacement function for the above function that accounts for the +$1/thread functionality of grow + * with parameters that are the same (for compatibility), but functionality is slightly different. + * This function can ONLY be used to calculate the threads needed for a given server in its current state, + * and so wouldn't be appropriate to use for formulas.exe or ns.growthAnalyze (as those are meant to + * provide theoretical scenarios, or inverse hack respectively). Players COULD use this function with a + * custom server object with the correct moneyAvailable and moneyMax amounts, combined with a multiplier + * correctly calculated to bring the server to a new moneyAvailable (ie, passing in moneyAvailable 300 and x2 + * when you want the number of threads required to grow that particular server from 300 to 600), and this + * function would pass back the correct number of threads. But the key thing is that it doesn't just + * inverse/undo a hack (since the amount hacked from/to matters, not just the multiplier). + * The above is also a rather unnecessarily obtuse way of thinking about it for a formulas.exe type of + * application, so another function with different parameters is provided for that case below this one. + * Instead this function is meant to hand-off from the old numCycleForGrowth function to the new one + * where used internally for pro-rating or the like. Where you have applied a grow and want to determine + * how many threads were needed for THAT SPECIFIC grow case using a multiplier. + * Ideally, this function, and the original function above will be depreciated to use the methodology + * and inputs of the new function below this one. Even for internal cases (it's actually easier to do so). + * @param server - Server being grown + * @param growth - How much the server money is expected to be multiplied by (e.g. 1.5 for *1.5 / +50%) + * @param p - Reference to Player object + * @returns Number of "growth cycles" needed + */ +export function numCycleForGrowthTransition(server: Server, growth: number, p: IPlayer, cores = 1): number { + return numCycleForGrowthCorrected(server, server.moneyAvailable * growth, server.moneyAvailable, p, cores); +} + +/** + * This function calculates the number of threads needed to grow a server from one $amount to a higher $amount + * (ie, how many threads to grow this server from $200 to $600 for example). Used primarily for a formulas (or possibly growthAnalyze) + * type of application. It lets you "theorycraft" and easily ask what-if type questions. It's also the one that implements the + * main thread calculation algorithm, and so is the function all helper functions should call. + * It protects the inputs (so putting in INFINITY for targetMoney will use moneyMax, putting in a negative for start will use 0, etc.) + * @param server - Server being grown + * @param targetMoney - How much you want the server grown TO (not by), for instance, to grow from 200 to 600, input 600 + * @param startMoney - How much you are growing the server from, for instance, to grow from 200 to 600, input 200 + * @param p - Reference to Player object + * @returns Number of "growth cycles" needed + */ +export function numCycleForGrowthCorrected(server: Server, targetMoney: number, startMoney: number, p: IPlayer, cores = 1): number { + if (startMoney < 0) { startMoney = 0; } // servers "can't" have less than 0 dollars on them + if (targetMoney > server.moneyMax) { targetMoney = server.moneyMax; } // can't grow a server to more than its moneyMax + if (targetMoney <= startMoney) { return 0; } // no growth --> no threads + + // exponential base adjusted by security + const adjGrowthRate = (1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty); + const exponentialBase = Math.min(adjGrowthRate, CONSTANTS.ServerMaxGrowthRate); // cap growth rate + + // total of all grow thread multipliers + const serverGrowthPercentage = server.serverGrowth / 100.0; + const coreMultiplier = 1 + ((cores - 1) / 16); + const threadMultiplier = serverGrowthPercentage * p.hacking_grow_mult * coreMultiplier * BitNodeMultipliers.ServerGrowthRate; + + /* To understand what is done below we need to do some math. I hope the explanation is clear enough. + * First of, the names will be shortened for ease of manipulation: + * n:= targetMoney (n for new), o:= startMoney (o for old), b:= exponentialBase, t:= threadMultiplier, c:= cycles/threads + * c is what we are trying to compute. + * + * After growing, the money on a server is n = (o + c) * b^(c*t) + * c appears in an exponent and outside it, this is usually solved using the productLog/lambert's W special function + * this function will be noted W in the following + * The idea behind lambert's W function is W(x)*exp(W(x)) = x, or in other words, solving for y, y*exp(y) = x, as a function of x + * This function is provided in some advanced math library but we will compute it ourself here. + * + * Let's get back to solving the equation. It cannot be rewrote using W immediately because the base of the exponentiation is b + * b^(c*t) = exp(ln(b)*c*t) (this is how a^b is defined on reals, it matches the definition on integers) + * so n = (o + c) * exp(ln(b)*c*t) , W still cannot be used directly. We want to eliminate the other terms in 'o + c' and 'ln(b)*c*t'. + * + * A change of variable will do. The idea is to add an equation introducing a new variable (w here) in the form c = f(w) (for some f) + * With this equation we will eliminate all references to c, then solve for w and plug the result in the new equation to get c. + * The change of variable performed here should get rid of the unwanted terms mentionned above, c = w/(ln(b)*t) - o should help. + * This change of variable is allowed because whatever the value of c is, there is a value of w such that this equation holds: + * w = (c + o)*ln(b)*t (see how we used the terms we wanted to eliminate in order to build this variable change) + * + * We get n = (o + w/(ln(b)*t) - o) * exp(ln(b)*(w/(ln(b)*t) - o)*t) [ = w/(ln(b)*t) * exp(w - ln(b)*o*t) ] + * The change of variable exposed exp(w - o*ln(b)*t), we can rewrite that with exp(a - b) = exp(a)/exp(b) to isolate 'w*exp(w)' + * n = w/(ln(b)*t) * exp(w)/exp(ln(b)*o*t) [ = w*exp(w) / (ln(b) * t * b^(o*t)) ] + * Almost there, we just need to cancel the denominator on the right side of the equation: + * n * ln(b) * t * b^(o*t) = w*exp(w), Thus w = W(n * ln(b) * t * b^(o*t)) + * Finally we invert the variable change: c = W(n * ln(b) * t * b^(o*t))/(ln(b)*t) - o + * + * There is still an issue left: b^(o*t) doesn't fit inside a double precision float + * because the typical amount of money on servers is arround 10^6~10^9 + * We need to get an approximation of W without computing the power when o is huge + * Thankfully an approximation giving ~30% error uses log immediately so we will use + * W(n * ln(b) * t * b^(o*t)) ~= log(n * ln(b) * t * b^(o*t)) = log(n * ln(b) * t) + log(exp(ln(b) * o * t)) + * = log(n * ln(b) * t) + ln(b) * o * t + * (thanks to Drak for the grow formula, f4113nb34st and Wolfram Alpha for the rewrite, dwRchyngqxs for the explanation) + */ + const x = threadMultiplier * Math.log(exponentialBase); + const y = startMoney * x + Math.log(targetMoney * x); + /* Code for the approximation of lambert's W function is adapted from + * https://git.savannah.gnu.org/cgit/gsl.git/tree/specfunc/lambert.c + * using the articles [1] https://doi.org/10.1007/BF02124750 (algorithm above) + * and [2] https://doi.org/10.1145/361952.361970 (initial approximation when x < 2.5) + */ + let w; + if (y < Math.log(2.5)) { + /* exp(y) can be safely computed without overflow. + * The relative error on the result is better when exp(y) < 2.5 + * using Padé rational fraction approximation [2](5) + */ + const ey = Math.exp(y); + w = (ey + 4/3 * ey*ey) / (1 + 7/3 * ey + 5/6 * ey*ey); + } else { + /* obtain initial approximation from rough asymptotic [1](4.18) + * w = y [- log y when 0 <= y] + */ + w = y; + if (y > 0) w -= Math.log(y); + } + let cycles = w/x - startMoney; + + /* Iterative refinement, the goal is to correct c until |(o + c) * b^(c*t) - n| < 1 + * or the correction on the approximation is less than 1 + * The Newton-Raphson method will be used, this method is a classic to find roots of functions + * (given f, find c such that f(c) = 0). + * + * The idea of this method is to take the horizontal position at which the horizontal axis + * intersects with of the tangent of the function's curve as the next approximation. + * It is equivalent to treating the curve as a line (it is called a first order approximation) + * If the current approximation is c then the new approximated value is c - f(c)/f'(c) + * (where f' is the derivative of f). + * + * In our case f(c) = (o + c) * b^(c*t) - n, f'(c) = d((o + c) * b^(c*t) - n)/dc + * = (ln(b)*t * (c + o) + 1) * b^(c*t) + * And the update step is c[new] = c[old] - ((o + c) * b^(c*t) - n)/((ln(b)*t * (o + c) + 1) * b^(c*t)) + * + * The main question to ask when using this method is "does it converges?" + * (are the approximations getting better?), if it does then it does quickly. + * DOES IT CONVERGES? In the present case it does. The reason why doesn't help explaining the algorithm. + * If you are intrested then check out the wikipedia page. + */ + const bt = exponentialBase**threadMultiplier; + let corr = Infinity; + // Two sided error because we do not want to get stuck if the error stays on the wrong side + do { + // c should be above 0 so Halley's method can't be used, we have to stick to Newton-Raphson + const bct = bt**cycles; + const opc = startMoney + cycles; + const diff = opc * bct - targetMoney; + corr = diff / (opc * x + 1.0) / bct + cycles -= corr; + } while (Math.abs(corr) >= 1) + /* c is now within +/- 1 of the exact result. + * We want the ceiling of the exact result, so the floor if the approximation is above, + * the ceiling if the approximation is in the same unit as the exact result, + * and the ceiling + 1 if the approximation is below. + */ + const fca = Math.floor(cycles); + if (targetMoney <= (startMoney + fca)*Math.pow(exponentialBase, fca*threadMultiplier)) { + return fca; + } + const cca = Math.ceil(cycles); + if (targetMoney <= (startMoney + cca)*Math.pow(exponentialBase, cca*threadMultiplier)) { + return cca; + } + return cca + 1; +} + +/** + * This function calculates the number of threads needed to grow a server based on a pre-hack money and hackAmt + * (ie, if you're hacking a server with $1e6 moneyAvail for 60%, this function will tell you how many threads to regrow it + * A good replacement for the current ns.growthAnalyze if you want players to have more control/responsibility + * @param server - Server being grown + * @param hackProp - the proportion of money hacked (total, not per thread, like 0.60 for hacking 60% of available money) + * @param prehackMoney - how much money the server had before being hacked (like 200000 for hacking a server that had $200000 on it at time of hacking) + * @param p - Reference to Player object + * @returns Number of "growth cycles" needed to reverse the described hack + */ +export function numCycleForGrowthByHackAmt(server: Server, hackProp: number, prehackMoney: number, p: IPlayer, cores = 1): number{ + if (prehackMoney > server.moneyMax) prehackMoney = server.moneyMax; + const posthackMoney = Math.floor(prehackMoney * Math.min(1, Math.max(0, (1 - hackProp)))); + return numCycleForGrowthCorrected(server, prehackMoney, posthackMoney, p, cores); +} + +/** + * This function calculates the number of threads needed to grow a server based on an expected growth multiplier assuming it will max out + * (ie, if you expect to grow a server by 60% to reach maxMoney, this function will tell you how many threads to grow it) + * PROBABLY the best replacement for the current ns.growthAnalyze to maintain existing scripts + * @param server - Server being grown + * @param growth - How much the server is being grown by, as a multiple in DECIMAL form (e.g. 1.5 rather than 50). Infinity is acceptable. + * @param p - Reference to Player object + * @returns Number of "growth cycles" needed + */ +export function numCycleForGrowthByMultiplier(server: Server, growth: number, p: IPlayer, cores = 1): number{ + if (growth < 1.0) growth = 1.0; + const targetMoney = server.moneyMax; + const startingMoney = server.moneyMax / growth; + return numCycleForGrowthCorrected(server, targetMoney, startingMoney, p, cores); +} + + //Applied server growth for a single server. Returns the percentage growth export function processSingleServerGrowth(server: Server, threads: number, p: IPlayer, cores = 1): number { let serverGrowth = calculateServerGrowth(server, threads, p, cores); @@ -90,8 +283,8 @@ export function processSingleServerGrowth(server: Server, threads: number, p: IP // if there was any growth at all, increase security if (oldMoneyAvailable !== server.moneyAvailable) { - //Growing increases server security twice as much as hacking - let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable, p, cores); + let usedCycles = numCycleForGrowthCorrected(server, server.moneyAvailable, oldMoneyAvailable, p, cores); + // Growing increases server security twice as much as hacking usedCycles = Math.min(Math.max(0, Math.ceil(usedCycles)), threads); server.fortify(2 * CONSTANTS.ServerFortifyAmount * usedCycles); } diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx index bd1a1fbf8..81f724304 100644 --- a/src/Sidebar/ui/SidebarRoot.tsx +++ b/src/Sidebar/ui/SidebarRoot.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { KEY } from "../../utils/helpers/keyCodes"; import clsx from "clsx"; import { styled, Theme, CSSObject } from "@mui/material/styles"; import createStyles from "@mui/styles/createStyles"; @@ -53,7 +54,6 @@ import { Settings } from "../../Settings/Settings"; import { redPillFlag } from "../../RedPill"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; -import { KEY } from "../../utils/helpers/keyCodes"; import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot"; import { InvitationsSeen } from "../../Faction/ui/FactionsRoot"; import { hash } from "../../hash/hash"; @@ -276,54 +276,54 @@ export function SidebarRoot(props: IProps): React.ReactElement { function handleShortcuts(this: Document, event: KeyboardEvent): any { if (Settings.DisableHotkeys) return; if ((props.player.isWorking && props.player.focus) || redPillFlag) return; - if (event.keyCode == KEY.T && event.altKey) { + if (event.key === "t" && event.altKey) { event.preventDefault(); clickTerminal(); - } else if (event.keyCode === KEY.C && event.altKey) { + } else if (event.key === KEY.C && event.altKey) { event.preventDefault(); clickStats(); - } else if (event.keyCode === KEY.E && event.altKey) { + } else if (event.key === KEY.E && event.altKey) { event.preventDefault(); clickScriptEditor(); - } else if (event.keyCode === KEY.S && event.altKey) { + } else if (event.key === KEY.S && event.altKey) { event.preventDefault(); clickActiveScripts(); - } else if (event.keyCode === KEY.H && event.altKey) { + } else if (event.key === KEY.H && event.altKey) { event.preventDefault(); clickHacknet(); - } else if (event.keyCode === KEY.W && event.altKey) { + } else if (event.key === KEY.W && event.altKey) { event.preventDefault(); clickCity(); - } else if (event.keyCode === KEY.J && event.altKey && !event.ctrlKey && !event.metaKey && canJob) { + } else if (event.key === KEY.J && event.altKey && !event.ctrlKey && !event.metaKey && canJob) { // ctrl/cmd + alt + j is shortcut to open Chrome dev tools event.preventDefault(); clickJob(); - } else if (event.keyCode === KEY.R && event.altKey) { + } else if (event.key === KEY.R && event.altKey) { event.preventDefault(); clickTravel(); - } else if (event.keyCode === KEY.P && event.altKey) { + } else if (event.key === KEY.P && event.altKey) { event.preventDefault(); clickCreateProgram(); - } else if (event.keyCode === KEY.F && event.altKey) { + } else if (event.key === KEY.F && event.altKey) { if (props.page == Page.Terminal && Settings.EnableBashHotkeys) { return; } event.preventDefault(); clickFactions(); - } else if (event.keyCode === KEY.A && event.altKey) { + } else if (event.key === KEY.A && event.altKey) { event.preventDefault(); clickAugmentations(); - } else if (event.keyCode === KEY.U && event.altKey) { + } else if (event.key === KEY.U && event.altKey) { event.preventDefault(); clickTutorial(); - } else if (event.keyCode === KEY.B && event.altKey && props.player.bladeburner) { + } else if (event.key === KEY.B && event.altKey && props.player.bladeburner) { event.preventDefault(); clickBladeburner(); - } else if (event.keyCode === KEY.G && event.altKey && props.player.gang) { + } else if (event.key === KEY.G && event.altKey && props.player.gang) { event.preventDefault(); clickGang(); } - // if (event.keyCode === KEY.O && event.altKey) { + // if (event.key === KEY.O && event.altKey) { // event.preventDefault(); // gameOptionsBoxOpen(); // } diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts index 08d1fa4ed..33bd0232e 100644 --- a/src/Terminal/HelpText.ts +++ b/src/Terminal/HelpText.ts @@ -26,7 +26,7 @@ export const TerminalHelpText: string[] = [ " hostname Displays the hostname of the machine", " kill [script/pid] [args...] Stops the specified script on the current server ", " killall Stops all running scripts on the current machine", - " ls [dir] [| grep pattern] Displays all files on the machine", + " ls [dir] [--grep pattern] Displays all files on the machine", " lscpu Displays the number of CPU cores on the machine", " mem [script] [-t n] Displays the amount of RAM required to run the script", " mv [src] [dest] Move/rename a text or script file", @@ -295,28 +295,30 @@ export const HelpTexts: IMap = { " ", ], ls: [ - "Usage: ls [dir] [| grep pattern]", + "Usage: ls [dir] [-l] [--grep pattern]", " ", "The ls command, with no arguments, prints all files and directories on the current server's directory to the Terminal screen. ", "The files will be displayed in alphabetical order. ", " ", "The 'dir' optional parameter can be used to display files/directories in another directory.", " ", - "The '| grep pattern' optional parameter can be used to only display files whose filenames match the specified pattern.", + "The '-l' optional parameter allows you to force each item onto a single line.", + " ", + "The '--grep pattern' optional parameter can be used to only display files whose filenames match the specified pattern.", " ", "Examples:", " ", "List all files with the '.script' extension in the current directory:", " ", - " ls | grep .script", + " ls -l --grep .script", " ", "List all files with the '.js' extension in the root directory:", " ", - " ls / | grep .js", + " ls / -l --grep .js", " ", "List all files with the word 'purchase' in the filename, in the 'scripts' directory:", " ", - " ls scripts | grep purchase", + " ls scripts -l --grep purchase", " ", ], lscpu: ["Usage: lscpu", " ", "Prints the number of CPU Cores the current server has", " "], diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts index 043767686..2ebc4dcbf 100644 --- a/src/Terminal/Terminal.ts +++ b/src/Terminal/Terminal.ts @@ -620,7 +620,6 @@ export class Terminal implements ITerminal { const n00dlesServ = GetServer("n00dles"); if (n00dlesServ == null) { throw new Error("Could not get n00dles server"); - return; } switch (ITutorial.currStep) { case iTutorialSteps.TerminalHelp: diff --git a/src/Terminal/commands/ls.tsx b/src/Terminal/commands/ls.tsx index ff001a064..48e25a951 100644 --- a/src/Terminal/commands/ls.tsx +++ b/src/Terminal/commands/ls.tsx @@ -8,6 +8,7 @@ import { BaseServer } from "../../Server/BaseServer"; import { evaluateDirectoryPath, getFirstParentDirectory, isValidDirectoryPath } from "../DirectoryHelpers"; import { IRouter } from "../../ui/Router"; import { ITerminal } from "../ITerminal"; +import * as libarg from "arg" export function ls( terminal: ITerminal, @@ -16,44 +17,46 @@ export function ls( server: BaseServer, args: (string | number | boolean)[], ): void { + let flags; + try { + flags = libarg({ + '-l': Boolean, + '--grep': String, + '-g': '--grep', + }, + { argv: args } + ) + } catch (e) { + // catch passing only -g / --grep with no string to use as the search + incorrectUsage() + return; + } + const filter = flags['--grep'] + const numArgs = args.length; function incorrectUsage(): void { - terminal.error("Incorrect usage of ls command. Usage: ls [dir] [| grep pattern]"); + terminal.error("Incorrect usage of ls command. Usage: ls [dir] [-l] [-g, --grep pattern]"); } - if (numArgs > 4 || numArgs === 2) { + if (numArgs > 4) { return incorrectUsage(); } - // Grep - let filter = ""; // Grep - // Directory path let prefix = terminal.cwd(); if (!prefix.endsWith("/")) { prefix += "/"; } - // If there are 3+ arguments, then the last 3 must be for grep - if (numArgs >= 3) { - if (args[numArgs - 2] !== "grep" || args[numArgs - 3] !== "|") { - return incorrectUsage(); - } - filter = args[numArgs - 1] + ""; + // If first arg doesn't contain a - it must be the file/folder + const dir = (args[0] && typeof args[0] == "string" && !args[0].startsWith("-")) ? args[0] : "" + const newPath = evaluateDirectoryPath(dir + "", terminal.cwd()); + prefix = newPath || ""; + if (!prefix.endsWith("/")) { + prefix += "/"; } - - // If the second argument is not a pipe, then it must be for listing a directory - if (numArgs >= 1 && args[0] !== "|") { - const newPath = evaluateDirectoryPath(args[0] + "", terminal.cwd()); - prefix = newPath ? newPath : ""; - if (prefix != null) { - if (!prefix.endsWith("/")) { - prefix += "/"; - } - if (!isValidDirectoryPath(prefix)) { - return incorrectUsage(); - } - } + if (!isValidDirectoryPath(prefix)) { + return incorrectUsage(); } // Root directory, which is the same as no 'prefix' at all @@ -139,10 +142,9 @@ export function ls( }), )(); - const rowSplit = row - .split(" ") - .map((x) => x.trim()) - .filter((x) => !!x); + const rowSplit = row.split("~"); + let rowSplitArray = rowSplit.map((x) => [x.trim(), x.replace(x.trim(), "")]); + rowSplitArray = rowSplitArray.filter((x) => !!x[0]); function onScriptLinkClick(filename: string): void { if (player.getCurrentServer().hostname !== hostname) { @@ -156,34 +158,42 @@ export function ls( return ( - {rowSplit.map((rowItem) => ( - onScriptLinkClick(rowItem)}> - {rowItem} + {rowSplitArray.map((rowItem) => ( + + onScriptLinkClick(rowItem[0])}> + {rowItem[0]} + + + {rowItem[1]} + ))} ); } - function postSegments(segments: string[], style?: any, linked?: boolean): void { + function postSegments(segments: string[], flags: any, style?: any, linked?: boolean): void { const maxLength = Math.max(...segments.map((s) => s.length)) + 1; - const filesPerRow = Math.ceil(80 / maxLength); + const filesPerRow = flags["-l"] === true ? 1 : Math.ceil(80 / maxLength); for (let i = 0; i < segments.length; i++) { let row = ""; for (let col = 0; col < filesPerRow; col++) { if (!(i < segments.length)) break; row += segments[i]; row += " ".repeat(maxLength * (col + 1) - row.length); + if(linked) { + row += "~"; + } i++; } i--; if (!style) { terminal.print(row); } else if (linked) { - terminal.printRaw(); - } else { - terminal.printRaw({row}); - } + terminal.printRaw(); + } else { + terminal.printRaw({row}); + } } } @@ -196,6 +206,6 @@ export function ls( { segments: allScripts, style: { color: "yellow", fontStyle: "bold" }, linked: true }, ].filter((g) => g.segments.length > 0); for (let i = 0; i < groups.length; i++) { - postSegments(groups[i].segments, groups[i].style, groups[i].linked); + postSegments(groups[i].segments, flags, groups[i].style, groups[i].linked); } } diff --git a/src/Terminal/ui/TerminalInput.tsx b/src/Terminal/ui/TerminalInput.tsx index 318a77765..f84623535 100644 --- a/src/Terminal/ui/TerminalInput.tsx +++ b/src/Terminal/ui/TerminalInput.tsx @@ -48,7 +48,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React const terminalInput = useRef(null); const [value, setValue] = useState(command); - const [postUpdateValue, setPostUpdateValue] = useState<{postUpdate: () => void} | null>() + const [postUpdateValue, setPostUpdateValue] = useState<{ postUpdate: () => void } | null>(); const [possibilities, setPossibilities] = useState([]); const classes = useStyles(); @@ -65,14 +65,14 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React postUpdateValue.postUpdate(); setPostUpdateValue(null); } - }, [postUpdateValue]) + }, [postUpdateValue]); function saveValue(newValue: string, postUpdate?: () => void): void { command = newValue; setValue(newValue); if (postUpdate) { - setPostUpdateValue({postUpdate}); + setPostUpdateValue({ postUpdate }); } } @@ -102,7 +102,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React // Move cursor to correct location // foo bar |baz bum --> foo |baz bum const ref = terminalInput.current; - ref?.setSelectionRange(delStart+1, delStart+1); + ref?.setSelectionRange(delStart + 1, delStart + 1); }); return; } @@ -115,7 +115,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React // Move cursor to correct location // foo bar |baz bum --> foo bar |bum const ref = terminalInput.current; - ref?.setSelectionRange(start, start) + ref?.setSelectionRange(start, start); }); return; } @@ -180,13 +180,13 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React useEffect(() => { function keyDown(this: Document, event: KeyboardEvent): void { if (terminal.contractOpen) return; - if (terminal.action !== null && event.keyCode === KEY.C && event.ctrlKey) { + if (terminal.action !== null && event.key === KEY.C && event.ctrlKey) { terminal.finishAction(router, player, true); return; } const ref = terminalInput.current; if (event.ctrlKey || event.metaKey) return; - if (event.keyCode === KEY.C && (event.ctrlKey || event.metaKey)) return; // trying to copy + if (event.key === KEY.C && (event.ctrlKey || event.metaKey)) return; // trying to copy if (ref) ref.focus(); } @@ -196,7 +196,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React async function onKeyDown(event: React.KeyboardEvent): Promise { // Run command. - if (event.keyCode === KEY.ENTER && value !== "") { + if (event.key === KEY.ENTER && value !== "") { event.preventDefault(); terminal.print(`[${player.getCurrentServer().hostname} ~${terminal.cwd()}]> ${value}`); terminal.executeCommands(router, player, value); @@ -205,7 +205,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React } // Autocomplete - if (event.keyCode === KEY.TAB && value !== "") { + if (event.key === KEY.TAB && value !== "") { event.preventDefault(); let copy = value; @@ -256,13 +256,13 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React } // Clear screen. - if (event.keyCode === KEY.L && event.ctrlKey) { + if (event.key === KEY.L && event.ctrlKey) { event.preventDefault(); terminal.clear(); } // Select previous command. - if (event.keyCode === KEY.UPARROW || (Settings.EnableBashHotkeys && event.keyCode === KEY.P && event.ctrlKey)) { + if (event.key === KEY.UPARROW || (Settings.EnableBashHotkeys && event.key === "p" && event.ctrlKey)) { if (Settings.EnableBashHotkeys) { event.preventDefault(); } @@ -290,7 +290,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React } // Select next command - if (event.keyCode === KEY.DOWNARROW || (Settings.EnableBashHotkeys && event.keyCode === KEY.M && event.ctrlKey)) { + if (event.key === KEY.DOWNARROW || (Settings.EnableBashHotkeys && event.key === "m" && event.ctrlKey)) { if (Settings.EnableBashHotkeys) { event.preventDefault(); } @@ -317,57 +317,57 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React // Extra Bash Emulation Hotkeys, must be enabled through options if (Settings.EnableBashHotkeys) { - if (event.keyCode === KEY.A && event.ctrlKey) { + if (event.key === KEY.A && event.ctrlKey) { event.preventDefault(); moveTextCursor("home"); } - if (event.keyCode === KEY.E && event.ctrlKey) { + if (event.key === KEY.E && event.ctrlKey) { event.preventDefault(); moveTextCursor("end"); } - if (event.keyCode === KEY.B && event.ctrlKey) { + if (event.key === KEY.B && event.ctrlKey) { event.preventDefault(); moveTextCursor("prevchar"); } - if (event.keyCode === KEY.B && event.altKey) { + if (event.key === KEY.B && event.altKey) { event.preventDefault(); moveTextCursor("prevword"); } - if (event.keyCode === KEY.F && event.ctrlKey) { + if (event.key === KEY.F && event.ctrlKey) { event.preventDefault(); moveTextCursor("nextchar"); } - if (event.keyCode === KEY.F && event.altKey) { + if (event.key === KEY.F && event.altKey) { event.preventDefault(); moveTextCursor("nextword"); } - if ((event.keyCode === KEY.H || event.keyCode === KEY.D) && event.ctrlKey) { + if ((event.key === KEY.H || event.key === KEY.D) && event.ctrlKey) { modifyInput("backspace"); event.preventDefault(); } - if (event.keyCode === KEY.W && event.ctrlKey) { + if (event.key === KEY.W && event.ctrlKey) { event.preventDefault(); modifyInput("deletewordbefore"); } - if (event.keyCode === KEY.D && event.altKey) { + if (event.key === KEY.D && event.altKey) { event.preventDefault(); modifyInput("deletewordafter"); } - if (event.keyCode === KEY.U && event.ctrlKey) { + if (event.key === KEY.U && event.ctrlKey) { event.preventDefault(); modifyInput("clearbefore"); } - if (event.keyCode === KEY.K && event.ctrlKey) { + if (event.key === KEY.K && event.ctrlKey) { event.preventDefault(); modifyInput("clearafter"); } diff --git a/src/engine.tsx b/src/engine.tsx index 18689a08f..0f52b571c 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -48,6 +48,7 @@ import { calculateAchievements } from "./Achievements/Achievements"; import React from "react"; import { setupUncaughtPromiseHandler } from "./UncaughtPromiseHandler"; +import { Typography } from "@mui/material"; const Engine: { _lastUpdate: number; @@ -401,11 +402,21 @@ const Engine: { () => AlertEvents.emit( <> - Offline for {timeOfflineString}. While you were offline: + Offline for {timeOfflineString}. While you were offline:
    -
  • Your scripts generated{" "}
  • -
  • Your Hacknet Nodes generated {hacknetProdInfo}
  • -
  • You gained{" "} reputation divided amongst your factions
  • +
  • + + Your scripts generated + +
  • +
  • + Your Hacknet Nodes generated {hacknetProdInfo} +
  • +
  • + + You gained reputation divided amongst your factions + +
, ), diff --git a/src/ui/React/AlertManager.tsx b/src/ui/React/AlertManager.tsx index b1d7f5f0f..5ee31528d 100644 --- a/src/ui/React/AlertManager.tsx +++ b/src/ui/React/AlertManager.tsx @@ -3,7 +3,7 @@ import { EventEmitter } from "../../utils/EventEmitter"; import { Modal } from "./Modal"; import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; -import {sha256} from "js-sha256"; +import { sha256 } from "js-sha256"; export const AlertEvents = new EventEmitter<[string | JSX.Element]>(); @@ -23,8 +23,8 @@ export function AlertManager(): React.ReactElement { i++; setAlerts((old) => { const hash = getMessageHash(text); - if (old.some(a => a.hash === hash)) { - console.log('Duplicate message'); + if (old.some((a) => a.hash === hash)) { + console.log("Duplicate message"); return old; } return [ @@ -51,7 +51,7 @@ export function AlertManager(): React.ReactElement { }, []); function getMessageHash(text: string | JSX.Element): string { - if (typeof text === 'string') return sha256(text); + if (typeof text === "string") return sha256(text); return sha256(JSON.stringify(text.props)); } diff --git a/src/ui/React/CodingContractModal.tsx b/src/ui/React/CodingContractModal.tsx index 597e204e0..a16f3f61c 100644 --- a/src/ui/React/CodingContractModal.tsx +++ b/src/ui/React/CodingContractModal.tsx @@ -37,7 +37,7 @@ export function CodingContractModal(): React.ReactElement { // whatever ... const value = (event.target as any).value; - if (event.keyCode === KEY.ENTER && value !== "") { + if (event.key === KEY.ENTER && value !== "") { event.preventDefault(); props.onAttempt(answer); setAnswer(""); diff --git a/src/ui/React/PromptManager.tsx b/src/ui/React/PromptManager.tsx index 41f97e0ff..bd7145268 100644 --- a/src/ui/React/PromptManager.tsx +++ b/src/ui/React/PromptManager.tsx @@ -6,6 +6,7 @@ import Button from "@mui/material/Button"; import Select, { SelectChangeEvent } from "@mui/material/Select"; import TextField from "@mui/material/TextField"; import MenuItem from "@mui/material/MenuItem"; +import { KEY } from "../../utils/helpers/keyCodes"; export const PromptEvent = new EventEmitter<[Prompt]>(); @@ -93,7 +94,7 @@ function PromptMenuText({ resolve }: IContentProps): React.ReactElement { const onKeyDown = (event: React.KeyboardEvent): void => { event.stopPropagation(); - if (event.key === "Enter") { + if (event.key === KEY.ENTER) { event.preventDefault(); submit(); } diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx index 7a4c1802a..b1d2cf871 100644 --- a/src/ui/WorkInProgressRoot.tsx +++ b/src/ui/WorkInProgressRoot.tsx @@ -37,23 +37,12 @@ export function WorkInProgressRoot(): React.ReactElement { if (player.workType == CONSTANTS.WorkTypeFaction) { const faction = Factions[player.currentWorkFactionName]; - if (!faction) { - return ( - - - - Something has gone wrong, you cannot work for {player.currentWorkFactionName || "(Faction not found)"} at - this time. - - - - ); - } if (!faction) { return ( <> - You have not joined {faction} yet! + You have not joined {player.currentWorkFactionName || "(Faction not found)"} yet or cannot work at this time, + please try again if you think this should have worked diff --git a/src/ui/numeralFormat.ts b/src/ui/numeralFormat.ts index 1e005fd11..a2ffcbd57 100644 --- a/src/ui/numeralFormat.ts +++ b/src/ui/numeralFormat.ts @@ -52,24 +52,25 @@ class NumeralFormatter { } formatBigNumber(n: number): string { - return this.format(n, "0.000a"); + return this.format(n, "0.[000]a"); } // TODO: leverage numeral.js to do it. This function also implies you can // use this format in some text field but you can't. ( "1t" will parse but // "1s" will not) formatReallyBigNumber(n: number, decimalPlaces = 3): string { + const nAbs = Math.abs(n); if (n === Infinity) return "∞"; for (let i = 0; i < extraFormats.length; i++) { - if (extraFormats[i] < n && n <= extraFormats[i] * 1000) { + if (extraFormats[i] < nAbs && nAbs <= extraFormats[i] * 1000) { return this.format(n / extraFormats[i], "0." + "0".repeat(decimalPlaces)) + extraNotations[i]; } } - if (Math.abs(n) < 1000) { - return this.format(n, "0." + "0".repeat(decimalPlaces)); + if (nAbs < 1000) { + return this.format(n, "0.[" + "0".repeat(decimalPlaces) + "]"); } - const str = this.format(n, "0." + "0".repeat(decimalPlaces) + "a"); - if (str === "NaNt") return this.format(n, "0." + " ".repeat(decimalPlaces) + "e+0"); + const str = this.format(n, "0.[" + "0".repeat(decimalPlaces) + "]a"); + if (str === "NaNt") return this.format(n, "0.[" + " ".repeat(decimalPlaces) + "]e+0"); return str; } @@ -187,19 +188,56 @@ class NumeralFormatter { return this.format(n, "0.00"); } + parseCustomLargeNumber(str: string): number { + const numericRegExp = new RegExp('^(\-?\\d+\\.?\\d*)([' + extraNotations.join("") + ']?)$'); + const match = str.match(numericRegExp); + if (match == null) { + return NaN; + } + const [, number, notation] = match; + const notationIndex = extraNotations.indexOf(notation); + if (notationIndex === -1) { + return NaN; + } + return parseFloat(number) * extraFormats[notationIndex]; + } + + largestAbsoluteNumber(n1: number, n2 = 0, n3 = 0): number { + if(isNaN(n1)) n1=0; + if(isNaN(n2)) n2=0; + if(isNaN(n3)) n3=0; + const largestAbsolute = Math.max(Math.abs(n1), Math.abs(n2), Math.abs(n3)); + switch(largestAbsolute) { + case Math.abs(n1): return n1; + case Math.abs(n2): return n2; + case Math.abs(n3): return n3; + } + return 0; + } + parseMoney(s: string): number { - // numeral library does not handle formats like 1e10 well (returns 110), - // so if both return a valid number, return the biggest one + // numeral library does not handle formats like 1s (returns 1) and 1e10 (returns 110) well, + // so if more then 1 return a valid number, return the one farthest from 0 const numeralValue = numeral(s).value(); const parsed = parseFloat(s); - if (isNaN(parsed) && numeralValue === null) { + const selfParsed = this.parseCustomLargeNumber(s); + // Check for one or more NaN values + if (isNaN(parsed) && numeralValue === null && isNaN(selfParsed)) { // 3x NaN return NaN; - } else if (isNaN(parsed)) { + } else if (isNaN(parsed) && isNaN(selfParsed)) { // 2x NaN return numeralValue; - } else if (numeralValue === null) { + } else if (numeralValue === null && isNaN(selfParsed)) { // 2x NaN return parsed; - } else { - return Math.max(numeralValue, parsed); + } else if (isNaN(parsed) && numeralValue === null) { // 2x NaN + return selfParsed; + } else if (isNaN(parsed)) { // 1x NaN + return this.largestAbsoluteNumber(numeralValue, selfParsed); + } else if (numeralValue === null) { // 1x NaN + return this.largestAbsoluteNumber(parsed, selfParsed); + } else if (isNaN(selfParsed)) { // 1x NaN + return this.largestAbsoluteNumber(numeralValue, parsed); + } else { // no NaN + return this.largestAbsoluteNumber(numeralValue, parsed, selfParsed); } } } diff --git a/src/utils/helpers/keyCodes.ts b/src/utils/helpers/keyCodes.ts index 7fc89f380..a03290a26 100644 --- a/src/utils/helpers/keyCodes.ts +++ b/src/utils/helpers/keyCodes.ts @@ -1,51 +1,53 @@ -import { IMap } from "../../types"; - /** * Keyboard key codes */ -export const KEY: IMap = { - CTRL: 17, - DOWNARROW: 40, - ENTER: 13, - ESC: 27, - TAB: 9, - UPARROW: 38, +export enum KEY { + //SHIFT: 16, // Check by `&& event.shiftKey` + //CTRL: 17, // Check by `&& event.ctrlKey` + //ALT: 18, // Check by `&& event.altKey` + ENTER = "Enter", + ESC = "Escape", + TAB = "Tab", + UPARROW = "ArrowUp", + DOWNARROW = "ArrowDown", + LEFTARROW = "ArrowLeft", + RIGHTARROW = "ArrowRight", - "0": 48, - "1": 49, - "2": 50, - "3": 51, - "4": 52, - "5": 53, - "6": 54, - "7": 55, - "8": 56, - "9": 57, + k0 = "0", + k1 = "1", + k2 = "2", + k3 = "3", + k4 = "4", + k5 = "5", + k6 = "6", + k7 = "7", + k8 = "8", + k9 = "9", - A: 65, - B: 66, - C: 67, - D: 68, - E: 69, - F: 70, - G: 71, - H: 72, - I: 73, - J: 74, - K: 75, - L: 76, - M: 77, - N: 78, - O: 79, - P: 80, - Q: 81, - R: 82, - S: 83, - T: 84, - U: 85, - V: 86, - W: 87, - X: 88, - Y: 89, - Z: 90, -}; + A = "a", + B = "b", + C = "c", + D = "d", + E = "e", + F = "f", + G = "g", + H = "h", + I = "i", + J = "j", + K = "k", + L = "l", + M = "m", + N = "n", + O = "o", + P = "p", + Q = "q", + R = "r", + S = "s", + T = "t", + U = "u", + V = "v", + W = "w", + X = "x", + Y = "y", + Z = "z", +} diff --git a/test/jest/Netscript/DynamicRamCalculation.test.js b/test/jest/Netscript/DynamicRamCalculation.test.js index 55f69203d..4170f9ebd 100644 --- a/test/jest/Netscript/DynamicRamCalculation.test.js +++ b/test/jest/Netscript/DynamicRamCalculation.test.js @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { jest, describe, expect, test } from "@jest/globals"; +import { jest, describe, expect } from "@jest/globals"; import { Player } from "../../../src/Player"; import { NetscriptFunctions } from "../../../src/NetscriptFunctions"; @@ -704,6 +703,16 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () { await testNonzeroDynamicRamCost(f); }); + it("getDarkwebProgramCost()", async function () { + const f = ["getDarkwebProgramCost"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getDarkwebPrograms()", async function () { + const f = ["getDarkwebPrograms"]; + await testNonzeroDynamicRamCost(f); + }); + it("getCharacterInformation()", async function () { const f = ["getCharacterInformation"]; await testNonzeroDynamicRamCost(f); diff --git a/test/jest/Netscript/StaticRamCalculation.test.js b/test/jest/Netscript/StaticRamCalculation.test.js index dd323709b..55e56823f 100644 --- a/test/jest/Netscript/StaticRamCalculation.test.js +++ b/test/jest/Netscript/StaticRamCalculation.test.js @@ -656,6 +656,16 @@ describe("Netscript Static RAM Calculation/Generation Tests", function () { await expectNonZeroRamCost(f); }); + it("getDarkwebPrograms()", async function () { + const f = ["getDarkwebPrograms"]; + await expectNonZeroRamCost(f); + }); + + it("getDarkwebProgramCost()", async function () { + const f = ["getDarkwebProgramCost"]; + await expectNonZeroRamCost(f); + }); + it("upgradeHomeRam()", async function () { const f = ["upgradeHomeRam"]; await expectNonZeroRamCost(f); diff --git a/test/jest/ui/nFormat.test.js b/test/jest/ui/nFormat.test.js new file mode 100644 index 000000000..56e67a4a6 --- /dev/null +++ b/test/jest/ui/nFormat.test.js @@ -0,0 +1,247 @@ +import { describe, expect, test } from "@jest/globals"; +import { numeralWrapper } from "../../../src/ui/numeralFormat"; + +let decimalFormat = '0.[000000]'; + +describe('Numeral formatting for positive numbers', () => { + test('should not format too small numbers', () => { + expect(numeralWrapper.format(0.0000000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(0.000000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(0.00000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(0.0000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(0.000001, decimalFormat)).toEqual('0.000001'); + expect(numeralWrapper.format(0.00001, decimalFormat)).toEqual('0.00001'); + expect(numeralWrapper.format(0.0001, decimalFormat)).toEqual('0.0001'); + expect(numeralWrapper.format(0.001, decimalFormat)).toEqual('0.001'); + expect(numeralWrapper.format(0.01, decimalFormat)).toEqual('0.01'); + expect(numeralWrapper.format(0.1, decimalFormat)).toEqual('0.1'); + expect(numeralWrapper.format(1, decimalFormat)).toEqual('1'); + }); + test('should format big numbers in short format', () => { + expect(numeralWrapper.formatBigNumber(987654000000000000)).toEqual('987654t'); + expect(numeralWrapper.formatBigNumber(987654300000000000)).toEqual('987654.3t'); + expect(numeralWrapper.formatBigNumber(987654320000000000)).toEqual('987654.32t'); + expect(numeralWrapper.formatBigNumber(987654321000000000)).toEqual('987654.321t'); + expect(numeralWrapper.formatBigNumber(987654321900000000)).toEqual('987654.322t'); + }); + test('should format really big numbers in readable format', () => { + expect(numeralWrapper.formatReallyBigNumber(987)).toEqual('987'); + expect(numeralWrapper.formatReallyBigNumber(987654)).toEqual('987.654k'); + expect(numeralWrapper.formatReallyBigNumber(987654321)).toEqual('987.654m'); + expect(numeralWrapper.formatReallyBigNumber(987654321987)).toEqual('987.654b'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654)).toEqual('987.654t'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654321)).toEqual('987.654q'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654321987)).toEqual('987.654Q'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654)).toEqual('987.654s'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321)).toEqual('987.654S'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321987)).toEqual('987.654o'); + expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321987654)).toEqual('987.654n'); + }); + test('should format even bigger really big numbers in scientific format', () => { + expect(numeralWrapper.formatReallyBigNumber(987654321987654321987654321987654321)).toEqual('9.877e+35'); + expect(numeralWrapper.formatReallyBigNumber(9876543219876543219876543219876543219)).toEqual('9.877e+36'); + expect(numeralWrapper.formatReallyBigNumber(98765432198765432198765432198765432198)).toEqual('9.877e+37'); + }); + test('should format percentage', () => { + expect(numeralWrapper.formatPercentage(1234.56789)).toEqual('123456.79%'); + }); +}); + +describe('Numeral formatting for negative numbers', () => { + test('should not format too small numbers', () => { + expect(numeralWrapper.format(-0.0000000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(-0.000000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(-0.00000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(-0.0000001, decimalFormat)).toEqual('0'); + expect(numeralWrapper.format(-0.000001, decimalFormat)).toEqual('-0.000001'); + expect(numeralWrapper.format(-0.00001, decimalFormat)).toEqual('-0.00001'); + expect(numeralWrapper.format(-0.0001, decimalFormat)).toEqual('-0.0001'); + expect(numeralWrapper.format(-0.001, decimalFormat)).toEqual('-0.001'); + expect(numeralWrapper.format(-0.01, decimalFormat)).toEqual('-0.01'); + expect(numeralWrapper.format(-0.1, decimalFormat)).toEqual('-0.1'); + expect(numeralWrapper.format(-1, decimalFormat)).toEqual('-1'); + }); + test('should format big numbers in short format', () => { + expect(numeralWrapper.formatBigNumber(-987654000000000000)).toEqual('-987654t'); + expect(numeralWrapper.formatBigNumber(-987654300000000000)).toEqual('-987654.3t'); + expect(numeralWrapper.formatBigNumber(-987654320000000000)).toEqual('-987654.32t'); + expect(numeralWrapper.formatBigNumber(-987654321000000000)).toEqual('-987654.321t'); + expect(numeralWrapper.formatBigNumber(-987654321900000000)).toEqual('-987654.322t'); + }); + test('should format really big numbers in readable format', () => { + expect(numeralWrapper.formatReallyBigNumber(-987)).toEqual('-987'); + expect(numeralWrapper.formatReallyBigNumber(-987654)).toEqual('-987.654k'); + expect(numeralWrapper.formatReallyBigNumber(-987654321)).toEqual('-987.654m'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987)).toEqual('-987.654b'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654)).toEqual('-987.654t'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321)).toEqual('-987.654q'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987)).toEqual('-987.654Q'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654)).toEqual('-987.654s'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321)).toEqual('-987.654S'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321987)).toEqual('-987.654o'); + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321987654)).toEqual('-987.654n'); + }); + test('should format even bigger really big numbers in scientific format', () => { + expect(numeralWrapper.formatReallyBigNumber(-987654321987654321987654321987654321)).toEqual('-9.877e+35'); + expect(numeralWrapper.formatReallyBigNumber(-9876543219876543219876543219876543219)).toEqual('-9.877e+36'); + expect(numeralWrapper.formatReallyBigNumber(-98765432198765432198765432198765432198)).toEqual('-9.877e+37'); + }); + test('should format percentage', () => { + expect(numeralWrapper.formatPercentage(-1234.56789)).toEqual('-123456.79%'); + }); +}); + +describe('Numeral formatting of text', () => { + test('should filter non-numeric text', () => { + expect(numeralWrapper.format('abc')).toEqual('0'); + expect(numeralWrapper.format('123abc')).toEqual('123'); + expect(numeralWrapper.format('!3')).toEqual('3'); + expect(numeralWrapper.format('3!')).toEqual('3'); + expect(numeralWrapper.format('0.001', decimalFormat)).toEqual('0.001'); + }); + test('should not format too small numbers', () => { + expect(numeralWrapper.format('0.00000001', decimalFormat)).toEqual('0'); + expect(numeralWrapper.format('0.0000001', decimalFormat)).toEqual('0'); + expect(numeralWrapper.format('0.000001', decimalFormat)).toEqual('0.000001'); + expect(numeralWrapper.format('0.00001', decimalFormat)).toEqual('0.00001'); + expect(numeralWrapper.format('1', decimalFormat)).toEqual('1'); + expect(numeralWrapper.format('-0.00000001', decimalFormat)).toEqual('0'); + expect(numeralWrapper.format('-0.0000001', decimalFormat)).toEqual('0'); + expect(numeralWrapper.format('-0.000001', decimalFormat)).toEqual('-0.000001'); + expect(numeralWrapper.format('-0.00001', decimalFormat)).toEqual('-0.00001'); + expect(numeralWrapper.format('-1', decimalFormat)).toEqual('-1'); + }); + test('should format big numbers in short format', () => { + expect(numeralWrapper.formatBigNumber('987654000000000000')).toEqual('987654t'); + expect(numeralWrapper.formatBigNumber('987654300000000000')).toEqual('987654.3t'); + expect(numeralWrapper.formatBigNumber('987654320000000000')).toEqual('987654.32t'); + expect(numeralWrapper.formatBigNumber('987654321000000000')).toEqual('987654.321t'); + expect(numeralWrapper.formatBigNumber('987654321900000000')).toEqual('987654.322t'); + expect(numeralWrapper.formatBigNumber('-987654000000000000')).toEqual('-987654t'); + expect(numeralWrapper.formatBigNumber('-987654300000000000')).toEqual('-987654.3t'); + expect(numeralWrapper.formatBigNumber('-987654320000000000')).toEqual('-987654.32t'); + expect(numeralWrapper.formatBigNumber('-987654321000000000')).toEqual('-987654.321t'); + expect(numeralWrapper.formatBigNumber('-987654321900000000')).toEqual('-987654.322t'); + }); + test('should format really big numbers in readable format', () => { + expect(numeralWrapper.formatReallyBigNumber('987')).toEqual('987'); + expect(numeralWrapper.formatReallyBigNumber('987654')).toEqual('987.654k'); + expect(numeralWrapper.formatReallyBigNumber('987654321')).toEqual('987.654m'); + expect(numeralWrapper.formatReallyBigNumber('987654321987')).toEqual('987.654b'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654')).toEqual('987.654t'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654321')).toEqual('987.654q'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654321987')).toEqual('987.654Q'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654321987654')).toEqual('987.654s'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654321987654321')).toEqual('987.654S'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654321987654321987')).toEqual('987.654o'); + expect(numeralWrapper.formatReallyBigNumber('987654321987654321987654321987654')).toEqual('987.654n'); + expect(numeralWrapper.formatReallyBigNumber('-987')).toEqual('-987'); + expect(numeralWrapper.formatReallyBigNumber('-987654')).toEqual('-987.654k'); + expect(numeralWrapper.formatReallyBigNumber('-987654321')).toEqual('-987.654m'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987')).toEqual('-987.654b'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654')).toEqual('-987.654t'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321')).toEqual('-987.654q'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321987')).toEqual('-987.654Q'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321987654')).toEqual('-987.654s'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321987654321')).toEqual('-987.654S'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321987654321987')).toEqual('-987.654o'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321987654321987654')).toEqual('-987.654n'); + }); + test('should format even bigger really big numbers in scientific format', () => { + expect(numeralWrapper.formatReallyBigNumber('987654321987654321987654321987654321')).toEqual('9.877e+35'); + expect(numeralWrapper.formatReallyBigNumber('9876543219876543219876543219876543219')).toEqual('9.877e+36'); + expect(numeralWrapper.formatReallyBigNumber('98765432198765432198765432198765432198')).toEqual('9.877e+37'); + expect(numeralWrapper.formatReallyBigNumber('-987654321987654321987654321987654321')).toEqual('-9.877e+35'); + expect(numeralWrapper.formatReallyBigNumber('-9876543219876543219876543219876543219')).toEqual('-9.877e+36'); + expect(numeralWrapper.formatReallyBigNumber('-98765432198765432198765432198765432198')).toEqual('-9.877e+37'); + }); + test('should format percentage', () => { + expect(numeralWrapper.formatPercentage('1234.56789')).toEqual('123456.79%'); + expect(numeralWrapper.formatPercentage('-1234.56789')).toEqual('-123456.79%'); + }); +}); + +describe('Numeral formatting of scientific text', () => { + test('should format even bigger really big numbers in scientific format', () => { + // Accepted by numeral.js + expect(numeralWrapper.parseMoney('123')).toEqual(123); + expect(numeralWrapper.parseMoney('123.456')).toEqual(123.456); + expect(numeralWrapper.parseMoney('123k')).toEqual(123000); + expect(numeralWrapper.parseMoney('123.456k')).toEqual(123456); + expect(numeralWrapper.parseMoney('123m')).toEqual(123000000); + expect(numeralWrapper.parseMoney('123.456m')).toEqual(123456000); + expect(numeralWrapper.parseMoney('123b')).toEqual(123000000000); + expect(numeralWrapper.parseMoney('123.456b')).toEqual(123456000000); + expect(numeralWrapper.parseMoney('123t')).toEqual(123000000000000); + expect(numeralWrapper.parseMoney('123.456t')).toEqual(123456000000000); + // Custom formats, parseFloat has some rounding issues + expect(numeralWrapper.parseMoney('123q')).toBeCloseTo(123000000000000000); + expect(numeralWrapper.parseMoney('123.456q')).toBeCloseTo(123456000000000000); + expect(numeralWrapper.parseMoney('123Q')).toBeCloseTo(123000000000000000000); + expect(numeralWrapper.parseMoney('123.456Q')).toBeCloseTo(123456000000000000000); + expect(numeralWrapper.parseMoney('123s')).toBeCloseTo(123000000000000000000000); + expect(numeralWrapper.parseMoney('123.456s')).toBeCloseTo(123456000000000000000000); + expect(numeralWrapper.parseMoney('123S')).toBeCloseTo(123000000000000000000000000); + expect(numeralWrapper.parseMoney('123.456S')).toBeCloseTo(123456000000000000000000000); + // Larger numbers fail the test due to rounding issues + //expect(numeralWrapper.parseMoney('123o')).toBeCloseTo(123000000000000000000000000000); + //expect(numeralWrapper.parseMoney('123.456o')).toBeCloseTo(123456000000000000000000000000); + //expect(numeralWrapper.parseMoney('123n')).toBeCloseTo(123000000000000000000000000000000); + //expect(numeralWrapper.parseMoney('123.456n')).toBeCloseTo(123456000000000000000000000000000); + }); + test('should format even bigger really big negative numbers in scientific format', () => { + // Accepted by numeral.js + expect(numeralWrapper.parseMoney('-123')).toEqual(-123); + expect(numeralWrapper.parseMoney('-123.456')).toEqual(-123.456); + expect(numeralWrapper.parseMoney('-123k')).toEqual(-123000); + expect(numeralWrapper.parseMoney('-123.456k')).toEqual(-123456); + expect(numeralWrapper.parseMoney('-123m')).toEqual(-123000000); + expect(numeralWrapper.parseMoney('-123.456m')).toEqual(-123456000); + expect(numeralWrapper.parseMoney('-123b')).toEqual(-123000000000); + expect(numeralWrapper.parseMoney('-123.456b')).toEqual(-123456000000); + expect(numeralWrapper.parseMoney('-123t')).toEqual(-123000000000000); + expect(numeralWrapper.parseMoney('-123.456t')).toEqual(-123456000000000); + // Custom formats, parseFloat has some rounding issues + expect(numeralWrapper.parseMoney('-123q')).toBeCloseTo(-123000000000000000); + expect(numeralWrapper.parseMoney('-123.456q')).toBeCloseTo(-123456000000000000); + expect(numeralWrapper.parseMoney('-123Q')).toBeCloseTo(-123000000000000000000); + expect(numeralWrapper.parseMoney('-123.456Q')).toBeCloseTo(-123456000000000000000); + expect(numeralWrapper.parseMoney('-123s')).toBeCloseTo(-123000000000000000000000); + expect(numeralWrapper.parseMoney('-123.456s')).toBeCloseTo(-123456000000000000000000); + expect(numeralWrapper.parseMoney('-123S')).toBeCloseTo(-123000000000000000000000000); + expect(numeralWrapper.parseMoney('-123.456S')).toBeCloseTo(-123456000000000000000000000); + // Larger numbers fail the test due to rounding issues + //expect(numeralWrapper.parseMoney('-123o')).toBeCloseTo(-123000000000000000000000000000); + //expect(numeralWrapper.parseMoney('-123.456o')).toBeCloseTo(-123456000000000000000000000000); + //expect(numeralWrapper.parseMoney('-123n')).toBeCloseTo(-123000000000000000000000000000000); + //expect(numeralWrapper.parseMoney('-123.456n')).toBeCloseTo(-123456000000000000000000000000000); + }); +}); + +describe('Finding the number furthest away from 0', () => { + test('should work if all numbers are equal', () => { + expect(numeralWrapper.largestAbsoluteNumber(0, 0, 0)).toEqual(0); + expect(numeralWrapper.largestAbsoluteNumber(1, 1, 1)).toEqual(1); + expect(numeralWrapper.largestAbsoluteNumber(123, 123, 123)).toEqual(123); + expect(numeralWrapper.largestAbsoluteNumber(-1, -1, -1)).toEqual(-1); + expect(numeralWrapper.largestAbsoluteNumber(-123, -123, -123)).toEqual(-123); + }); + test('should work for different positive numbers, and for the largest number in each spot', () => { + expect(numeralWrapper.largestAbsoluteNumber(1, 2, 3)).toEqual(3); + expect(numeralWrapper.largestAbsoluteNumber(456, 789, 123)).toEqual(789); + expect(numeralWrapper.largestAbsoluteNumber(789123, 123456, 456789)).toEqual(789123); + }); + test('should work for different negative numbers, and for the smallest number in each spot', () => { + expect(numeralWrapper.largestAbsoluteNumber(-1, -2, -3)).toEqual(-3); + expect(numeralWrapper.largestAbsoluteNumber(-456, -789, -123)).toEqual(-789); + expect(numeralWrapper.largestAbsoluteNumber(-789123, -123456, -456789)).toEqual(-789123); + }); + test('should work for combined positive and negative numbers', () => { + expect(numeralWrapper.largestAbsoluteNumber(1, -2, 3)).toEqual(3); + expect(numeralWrapper.largestAbsoluteNumber(-456, 789, -123)).toEqual(789); + expect(numeralWrapper.largestAbsoluteNumber(789123, -123456, -456789)).toEqual(789123); + }); + test('Should return 0 for invalid input', () => { + expect(numeralWrapper.largestAbsoluteNumber('abc', undefined, null)).toEqual(0); + }); +});