mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-21 23:23:51 +01:00
commit
01a2530863
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
34
dist/vendor.bundle.js
vendored
34
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/vendor.bundle.js.map
vendored
2
dist/vendor.bundle.js.map
vendored
File diff suppressed because one or more lines are too long
@ -338,7 +338,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.
|
||||
|
||||
@ -349,19 +349,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
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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",
|
||||
|
@ -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<Achievement> = {
|
||||
...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"],
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -210,64 +210,62 @@ export function BitverseRoot(props: IProps): React.ReactElement {
|
||||
]} />
|
||||
</>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
// prettier-ignore
|
||||
<>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | O | | O__/ | / \__ | | O | | | O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<CinematicText lines={[
|
||||
"> 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
|
||||
<>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | O | | O__/ | / \__ | | O | | | O </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<CinematicText lines={[
|
||||
"> 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)",
|
||||
]} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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":
|
||||
|
@ -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<HTMLInputElement>): 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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) buy();
|
||||
if (event.key === KEY.ENTER) buy();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) newIndustry();
|
||||
if (event.key === KEY.ENTER) newIndustry();
|
||||
}
|
||||
|
||||
function onIndustryChange(event: SelectChangeEvent<string>): void {
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) goPublic();
|
||||
if (event.key === KEY.ENTER) goPublic();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) issueDividends();
|
||||
if (event.key === KEY.ENTER) issueDividends();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) issueNewShares();
|
||||
if (event.key === KEY.ENTER) issueNewShares();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) limitProductProduction();
|
||||
if (event.key === KEY.ENTER) limitProductProduction();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) makeProduct();
|
||||
if (event.key === KEY.ENTER) makeProduct();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) bulkPurchase();
|
||||
if (event.key === KEY.ENTER) bulkPurchase();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
@ -123,7 +124,7 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement {
|
||||
}
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) purchaseMaterial();
|
||||
if (event.key === KEY.ENTER) purchaseMaterial();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) sellMaterial();
|
||||
if (event.key === KEY.ENTER) sellMaterial();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) sellProduct();
|
||||
if (event.key === KEY.ENTER) sellProduct();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) sell();
|
||||
if (event.key === KEY.ENTER) sell();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) throwParty();
|
||||
if (event.key === KEY.ENTER) throwParty();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
|
@ -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:`, <MoneyRate money={5 * props.member.calculateMoneyGain(gang)} />],
|
||||
@ -78,14 +71,38 @@ export function GangMemberStats(props: IProps): React.ReactElement {
|
||||
</Typography>
|
||||
}
|
||||
>
|
||||
<Table sx={{ display: 'table', mb: 1, width: '100%' }}>
|
||||
<Table sx={{ display: "table", mb: 1, width: "100%" }}>
|
||||
<TableBody>
|
||||
<StatsRow name="Hacking" color={Settings.theme.hack} data={{ level: props.member.hack, exp: props.member.hack_exp }} />
|
||||
<StatsRow name="Strength" color={Settings.theme.combat} data={{ level: props.member.str, exp: props.member.str_exp }} />
|
||||
<StatsRow name="Defense" color={Settings.theme.combat} data={{ level: props.member.def, exp: props.member.def_exp }} />
|
||||
<StatsRow name="Dexterity" color={Settings.theme.combat} data={{ level: props.member.dex, exp: props.member.dex_exp }} />
|
||||
<StatsRow name="Agility" color={Settings.theme.combat} data={{ level: props.member.agi, exp: props.member.agi_exp }} />
|
||||
<StatsRow name="Charisma" color={Settings.theme.cha} data={{ level: props.member.cha, exp: props.member.cha_exp }} />
|
||||
<StatsRow
|
||||
name="Hacking"
|
||||
color={Settings.theme.hack}
|
||||
data={{ level: props.member.hack, exp: props.member.hack_exp }}
|
||||
/>
|
||||
<StatsRow
|
||||
name="Strength"
|
||||
color={Settings.theme.combat}
|
||||
data={{ level: props.member.str, exp: props.member.str_exp }}
|
||||
/>
|
||||
<StatsRow
|
||||
name="Defense"
|
||||
color={Settings.theme.combat}
|
||||
data={{ level: props.member.def, exp: props.member.def_exp }}
|
||||
/>
|
||||
<StatsRow
|
||||
name="Dexterity"
|
||||
color={Settings.theme.combat}
|
||||
data={{ level: props.member.dex, exp: props.member.dex_exp }}
|
||||
/>
|
||||
<StatsRow
|
||||
name="Agility"
|
||||
color={Settings.theme.combat}
|
||||
data={{ level: props.member.agi, exp: props.member.agi_exp }}
|
||||
/>
|
||||
<StatsRow
|
||||
name="Charisma"
|
||||
color={Settings.theme.cha}
|
||||
data={{ level: props.member.cha, exp: props.member.cha_exp }}
|
||||
/>
|
||||
<TableRow>
|
||||
<TableCell classes={{ root: classes.cellNone }}>
|
||||
<br />
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) recruit();
|
||||
if (event.key === KEY.ENTER) recruit();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -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<string>): void {
|
||||
const task = event.target.value;
|
||||
props.member.assignToTask(task);
|
||||
|
@ -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<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) tryToPurchaseServer();
|
||||
if (event.key === KEY.ENTER) tryToPurchaseServer();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
|
@ -232,6 +232,8 @@ export const RamCosts: IMap<any> = {
|
||||
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),
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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}`);
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -178,7 +178,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
|
||||
const entry = ns[name];
|
||||
if (typeof entry === "function") {
|
||||
//Async functions need to be wrapped. See JS-Interpreter documentation
|
||||
if (["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "scp", "write", "share"].includes(name)) {
|
||||
if (["hack", "grow", "weaken", "sleep", "prompt", "manualHack", "scp", "write", "share", "wget"].includes(name)) {
|
||||
const tempWrapper = function (...args: any[]): void {
|
||||
const fnArgs = [];
|
||||
|
||||
@ -199,7 +199,21 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
|
||||
})
|
||||
.catch(function (err: any) {
|
||||
// workerscript is when you cancel a delay
|
||||
if (!(err instanceof WorkerScript)) console.error(err);
|
||||
if (!(err instanceof WorkerScript)) {
|
||||
console.error(err);
|
||||
const errorTextArray = err.split("|DELIMITER|");
|
||||
const hostname = errorTextArray[1];
|
||||
const scriptName = errorTextArray[2];
|
||||
const errorMsg = errorTextArray[3];
|
||||
let msg = `${scriptName}@${hostname}<br>`;
|
||||
msg += "<br>";
|
||||
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}'`);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -427,6 +427,4 @@ export async function calculateRamUsage(
|
||||
console.error(e);
|
||||
return { cost: RamCalculationErrorCode.SyntaxError };
|
||||
}
|
||||
|
||||
return { cost: RamCalculationErrorCode.SyntaxError };
|
||||
}
|
||||
|
71
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
71
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
// }
|
||||
|
@ -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<string[]> = {
|
||||
" ",
|
||||
],
|
||||
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", " "],
|
||||
|
@ -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:
|
||||
|
@ -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 (
|
||||
<span className={classes.scriptLinksWrap}>
|
||||
{rowSplit.map((rowItem) => (
|
||||
<span key={rowItem} className={classes.scriptLink} onClick={() => onScriptLinkClick(rowItem)}>
|
||||
{rowItem}
|
||||
{rowSplitArray.map((rowItem) => (
|
||||
<span>
|
||||
<span key={rowItem[0]} className={classes.scriptLink} onClick={() => onScriptLinkClick(rowItem[0])}>
|
||||
{rowItem[0]}
|
||||
</span>
|
||||
<span key={'s'+rowItem[0]}>
|
||||
{rowItem[1]}
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
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(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
|
||||
} else {
|
||||
terminal.printRaw(<span style={style}>{row}</span>);
|
||||
}
|
||||
terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
|
||||
} else {
|
||||
terminal.printRaw(<span style={style}>{row}</span>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export function TerminalInput({ terminal, router, player }: IProps): React.React
|
||||
const terminalInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [value, setValue] = useState(command);
|
||||
const [postUpdateValue, setPostUpdateValue] = useState<{postUpdate: () => void} | null>()
|
||||
const [postUpdateValue, setPostUpdateValue] = useState<{ postUpdate: () => void } | null>();
|
||||
const [possibilities, setPossibilities] = useState<string[]>([]);
|
||||
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<HTMLInputElement>): Promise<void> {
|
||||
// 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");
|
||||
}
|
||||
|
@ -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:
|
||||
<Typography>Offline for {timeOfflineString}. While you were offline:</Typography>
|
||||
<ul>
|
||||
<li>Your scripts generated{" "} <Money money={offlineHackingIncome} /></li>
|
||||
<li>Your Hacknet Nodes generated {hacknetProdInfo}</li>
|
||||
<li>You gained{" "} <Reputation reputation={offlineReputation} /> reputation divided amongst your factions</li>
|
||||
<li>
|
||||
<Typography>
|
||||
Your scripts generated <Money money={offlineHackingIncome} />
|
||||
</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography>Your Hacknet Nodes generated {hacknetProdInfo}</Typography>
|
||||
</li>
|
||||
<li>
|
||||
<Typography>
|
||||
You gained <Reputation reputation={offlineReputation} /> reputation divided amongst your factions
|
||||
</Typography>
|
||||
</li>
|
||||
</ul>
|
||||
</>,
|
||||
),
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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("");
|
||||
|
@ -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<HTMLInputElement>): void => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.key === "Enter") {
|
||||
if (event.key === KEY.ENTER) {
|
||||
event.preventDefault();
|
||||
submit();
|
||||
}
|
||||
|
@ -37,23 +37,12 @@ export function WorkInProgressRoot(): React.ReactElement {
|
||||
|
||||
if (player.workType == CONSTANTS.WorkTypeFaction) {
|
||||
const faction = Factions[player.currentWorkFactionName];
|
||||
if (!faction) {
|
||||
return (
|
||||
<Grid container direction="column" justifyContent="center" alignItems="center" style={{ minHeight: "100vh" }}>
|
||||
<Grid item>
|
||||
<Typography>
|
||||
Something has gone wrong, you cannot work for {player.currentWorkFactionName || "(Faction not found)"} at
|
||||
this time.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
if (!faction) {
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h4" color="primary">
|
||||
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
|
||||
</Typography>
|
||||
<Button onClick={() => router.toFactions()}>Back to Factions</Button>
|
||||
</>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +1,53 @@
|
||||
import { IMap } from "../../types";
|
||||
|
||||
/**
|
||||
* Keyboard key codes
|
||||
*/
|
||||
export const KEY: IMap<number> = {
|
||||
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",
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
247
test/jest/ui/nFormat.test.js
Normal file
247
test/jest/ui/nFormat.test.js
Normal file
@ -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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user