Merge pull request #3162 from danielyxie/dev

Few fixes
This commit is contained in:
hydroflame 2022-03-17 16:48:47 -04:00 committed by GitHub
commit 7afc7d5c78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 913 additions and 526 deletions

@ -137,7 +137,7 @@ module.exports = {
"no-ex-assign": ["off"], "no-ex-assign": ["off"],
"no-extra-boolean-cast": ["error"], "no-extra-boolean-cast": ["error"],
"no-extra-parens": ["off"], "no-extra-parens": ["off"],
"no-extra-semi": ["off"], "no-extra-semi": ["error"],
"no-eval": ["off"], "no-eval": ["off"],
"no-extend-native": ["off"], "no-extend-native": ["off"],
"no-extra-bind": ["error"], "no-extra-bind": ["error"],
@ -166,12 +166,12 @@ module.exports = {
"no-label-var": ["error"], "no-label-var": ["error"],
"no-labels": ["off"], "no-labels": ["off"],
"no-lone-blocks": ["error"], "no-lone-blocks": ["error"],
"no-lonely-if": ["off"], "no-lonely-if": ["error"],
"no-loop-func": ["off"], "no-loop-func": ["off"],
"no-magic-numbers": ["off"], "no-magic-numbers": ["off"],
"no-mixed-operators": ["off"], "no-mixed-operators": ["off"],
"no-mixed-requires": ["error"], "no-mixed-requires": ["error"],
"no-mixed-spaces-and-tabs": ["off"], "no-mixed-spaces-and-tabs": ["error"],
"no-multi-assign": ["off"], "no-multi-assign": ["off"],
"no-multi-spaces": ["off"], "no-multi-spaces": ["off"],
"no-multi-str": ["error"], "no-multi-str": ["error"],
@ -253,7 +253,7 @@ module.exports = {
"no-use-before-define": ["off"], "no-use-before-define": ["off"],
"no-useless-call": ["off"], "no-useless-call": ["off"],
"no-useless-computed-key": ["error"], "no-useless-computed-key": ["error"],
"no-useless-concat": ["off"], "no-useless-concat": ["error"],
"no-useless-constructor": ["error"], "no-useless-constructor": ["error"],
"no-useless-escape": ["off"], "no-useless-escape": ["off"],
"no-useless-rename": [ "no-useless-rename": [

89
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -6,10 +6,8 @@ Intelligence is a :ref:`stat <gameplay_stats>` that is unlocked by having
:ref:`Source-File 5 <gameplay_sourcefiles>` (i.e. Destroying BitNode-5). :ref:`Source-File 5 <gameplay_sourcefiles>` (i.e. Destroying BitNode-5).
Intelligence is unique because it is permanent and persistent. It never gets reset Intelligence is unique because it is permanent and persistent. It never gets reset
back to 1. However, gaining Intelligence experience is extremely slow. The methods back to 1. However, gaining Intelligence experience is extremely slow. It is a stat
of gaining Intelligence exp is also hidden. You won't know when you gain that gradually builds up as you continue to play the game.
experience and how much. It is a stat that gradually builds up as you continue
to play the game.
Intelligence will boost your production for many actions in the game, including: Intelligence will boost your production for many actions in the game, including:

@ -21,23 +21,19 @@ can be used to check how much RAM a server has.
Identifying Servers Identifying Servers
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
A server is identified by two properties: its IP address and its hostname. A server is identified by its hostname.
An IP address is a 32-bit number represented in dot-decimal notation. A hostname is a label assigned to a server.
For example, "56.1.5.0" and "86.5.1.0" might be two IP addresses
you see in the game. A hostname is a label assigned to a server.
A hostname will usually give you a general idea of what the server A hostname will usually give you a general idea of what the server
is. For example, the company Nova Medical might have a server with is. For example, the company Nova Medical might have a server with
the hostname "nova-med". the hostname "nova-med".
Hostnames and IP addresses are unique. This means that if one Hostnames are unique. This means that if one
server has the IP address "1.1.1.1" and the hostname server has the the hostname "some-server", then no other server
"some-server", then no other server in the game can have that in the game can have that that hostname.
IP address or that hostname.
There are many :ref:`Netscript Functions <netscriptfunctions>` There are many :ref:`Netscript Functions <netscriptfunctions>`
and :ref:`terminal` commands in the game and :ref:`terminal` commands in the game
that will require you to target a specific server. This is done using that will require you to target a specific server by hostname.
either the IP address or the hostname of the server.
Player-owned Servers Player-owned Servers
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^

@ -230,7 +230,7 @@ connect
$ connect [hostname/ip] $ connect [hostname/ip]
Connect to a remote server. The hostname or IP address of the remote server must Connect to a remote server. The hostname of the remote server must
be given as the argument to this command. Note that only servers that are immediately be given as the argument to this command. Note that only servers that are immediately
adjacent to the current server in the network can be connected to. To see which adjacent to the current server in the network can be connected to. To see which
servers can be connected to, use the 'scan' command. servers can be connected to, use the 'scan' command.
@ -326,7 +326,7 @@ Then to kill this script the same arguments would have to be used::
$ kill foo.script 50e3 sigma-cosmetics $ kill foo.script 50e3 sigma-cosmetics
If you are killing the script using its PID, then the PID argument must be numeric. If you are killing the script using its PID, then the PID argument must be numeric.
killall killall
^^^^^^^ ^^^^^^^
@ -533,28 +533,6 @@ Then in order to check its logs with 'tail' the same arguments must be used::
$ tail foo.script 10 50000 $ tail foo.script 10 50000
theme
^^^^^
$ theme [preset] | [#background #text #highlight]
Change the color of the game's user interface
This command can be called with a preset theme. Currently, the supported presets are:
* default
* muted
* solarized
However, you can also specify your own color scheme using hex values.
To do so, you must specify three hex color values for the background
color, the text color, and the highlight color. These hex values must
be preceded by a pound sign (#) and must be either 3 or 6 digits. Example::
$ theme #ffffff #385 #235012
A color picker such as Google's can be used to get your desired hex color values
top top
^^^ ^^^

@ -337,23 +337,21 @@ async function restoreIfNewerExists(window) {
let bestMatch; let bestMatch;
if (!steam.data && !disk.data) { if (!steam.data && !disk.data) {
log.info("No data to import"); log.info("No data to import");
} else { } else if (!steam.data) {
// We'll just compare using the lastSave field for now. // We'll just compare using the lastSave field for now.
if (!steam.data) { log.debug('Best potential save match: Disk');
log.debug('Best potential save match: Disk'); bestMatch = disk;
bestMatch = disk; } else if (!disk.data) {
} else if (!disk.data) { log.debug('Best potential save match: Steam Cloud');
log.debug('Best potential save match: Steam Cloud'); bestMatch = steam;
bestMatch = steam; } else if ((steam.data.lastSave >= disk.data.lastSave)
} else if ((steam.data.lastSave >= disk.data.lastSave) || (steam.data.playtime + lowPlaytime > disk.data.playtime)) {
|| (steam.data.playtime + lowPlaytime > disk.data.playtime)) { // We want to prioritze steam data if the playtime is very close
// We want to prioritze steam data if the playtime is very close log.debug('Best potential save match: Steam Cloud');
log.debug('Best potential save match: Steam Cloud'); bestMatch = steam;
bestMatch = steam; } else {
} else { log.debug('Best potential save match: disk');
log.debug('Best potential save match: disk'); bestMatch = disk;
bestMatch = disk;
}
} }
if (bestMatch) { if (bestMatch) {
if (bestMatch.data.lastSave > currentData.lastSave + 5000) { if (bestMatch.data.lastSave > currentData.lastSave + 5000) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

49
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "bitburner", "name": "bitburner",
"version": "1.4.0", "version": "1.5.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bitburner", "name": "bitburner",
"version": "1.4.0", "version": "1.5.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "SEE LICENSE IN license.txt", "license": "SEE LICENSE IN license.txt",
"dependencies": { "dependencies": {
@ -17,9 +17,12 @@
"@mui/icons-material": "^5.0.3", "@mui/icons-material": "^5.0.3",
"@mui/material": "^5.0.3", "@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1", "@mui/styles": "^5.0.1",
"@types/bcrypt": "^5.0.0",
"@types/bcryptjs": "^2.4.2",
"acorn": "^8.4.1", "acorn": "^8.4.1",
"acorn-walk": "^8.1.1", "acorn-walk": "^8.1.1",
"arg": "^5.0.0", "arg": "^5.0.0",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^1.0.3", "better-react-mathjax": "^1.0.3",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",
@ -3977,6 +3980,19 @@
"@babel/types": "^7.3.0" "@babel/types": "^7.3.0"
} }
}, },
"node_modules/@types/bcrypt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz",
"integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
"integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ=="
},
"node_modules/@types/escodegen": { "node_modules/@types/escodegen": {
"version": "0.0.7", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
@ -4068,8 +4084,7 @@
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "16.10.4", "version": "16.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.4.tgz",
"integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA==", "integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA=="
"dev": true
}, },
"node_modules/@types/numeral": { "node_modules/@types/numeral": {
"version": "0.0.25", "version": "0.0.25",
@ -5448,6 +5463,11 @@
"tweetnacl": "^0.14.3" "tweetnacl": "^0.14.3"
} }
}, },
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
},
"node_modules/better-react-mathjax": { "node_modules/better-react-mathjax": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-1.0.3.tgz", "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-1.0.3.tgz",
@ -25344,6 +25364,19 @@
"@babel/types": "^7.3.0" "@babel/types": "^7.3.0"
} }
}, },
"@types/bcrypt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz",
"integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==",
"requires": {
"@types/node": "*"
}
},
"@types/bcryptjs": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz",
"integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ=="
},
"@types/escodegen": { "@types/escodegen": {
"version": "0.0.7", "version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
@ -25435,8 +25468,7 @@
"@types/node": { "@types/node": {
"version": "16.10.4", "version": "16.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.4.tgz",
"integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA==", "integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA=="
"dev": true
}, },
"@types/numeral": { "@types/numeral": {
"version": "0.0.25", "version": "0.0.25",
@ -26527,6 +26559,11 @@
"tweetnacl": "^0.14.3" "tweetnacl": "^0.14.3"
} }
}, },
"bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms="
},
"better-react-mathjax": { "better-react-mathjax": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-1.0.3.tgz", "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-1.0.3.tgz",

@ -17,9 +17,12 @@
"@mui/icons-material": "^5.0.3", "@mui/icons-material": "^5.0.3",
"@mui/material": "^5.0.3", "@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1", "@mui/styles": "^5.0.1",
"@types/bcrypt": "^5.0.0",
"@types/bcryptjs": "^2.4.2",
"acorn": "^8.4.1", "acorn": "^8.4.1",
"acorn-walk": "^8.1.1", "acorn-walk": "^8.1.1",
"arg": "^5.0.0", "arg": "^5.0.0",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^1.0.3", "better-react-mathjax": "^1.0.3",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"date-fns": "^2.25.0", "date-fns": "^2.25.0",

@ -481,7 +481,16 @@
"ID": "DEVMENU", "ID": "DEVMENU",
"Name": "Exploit: you're not meant to access this", "Name": "Exploit: you're not meant to access this",
"Description": "Open the dev menu." "Description": "Open the dev menu."
},
"RAINBOW": {
"ID": "RAINBOW",
"Name": "Exploit: rainbow",
"Description": "Make good use of the rainbow."
},
"TRUE_RECURSION": {
"ID": "TRUE_RECURSION",
"Name": "Exploit: true recursion",
"Description": "Beat BN1 in megabyteburner 2000."
} }
} }
} }

@ -553,7 +553,8 @@ export const achievements: IMap<Achievement> = {
...achievementData["MAX_CACHE"], ...achievementData["MAX_CACHE"],
Icon: "HASHNETCAP", Icon: "HASHNETCAP",
Visible: () => hasAccessToSF(Player, 9), Visible: () => hasAccessToSF(Player, 9),
Condition: () => hasHacknetServers(Player) && Condition: () =>
hasHacknetServers(Player) &&
Player.hashManager.hashes === Player.hashManager.capacity && Player.hashManager.hashes === Player.hashManager.capacity &&
Player.hashManager.capacity > 0, Player.hashManager.capacity > 0,
}, },
@ -729,6 +730,18 @@ export const achievements: IMap<Achievement> = {
Secret: true, Secret: true,
Condition: () => Player.exploits.includes(Exploit.YoureNotMeantToAccessThis), Condition: () => Player.exploits.includes(Exploit.YoureNotMeantToAccessThis),
}, },
RAINBOW: {
...achievementData["RAINBOW"],
Icon: "SF-1",
Secret: true,
Condition: () => Player.exploits.includes(Exploit.INeedARainbow),
},
TRUE_RECURSION: {
...achievementData["TRUE_RECURSION"],
Icon: "SF-1",
Secret: true,
Condition: () => Player.exploits.includes(Exploit.TrueRecursion),
},
}; };
// Steam has a limit of 100 achievement. So these were planned but commented for now. // Steam has a limit of 100 achievement. So these were planned but commented for now.

@ -1,5 +1,7 @@
import React from "react"; import React, { useEffect } from "react";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { use } from "../../ui/Context";
import { Exploit } from "../../Exploits/Exploit";
const metaBB = "https://bitburner-official.github.io/bitburner-legacy/"; const metaBB = "https://bitburner-official.github.io/bitburner-legacy/";
@ -10,6 +12,14 @@ const style = {
} as any; } as any;
export function BBCabinetRoot(): React.ReactElement { export function BBCabinetRoot(): React.ReactElement {
const player = use.Player();
useEffect(() => {
window.addEventListener("message", function (this: Window, ev: MessageEvent<boolean>) {
if (ev.isTrusted && ev.origin == "https://bitburner-official.github.io" && ev.data) {
player.giveExploit(Exploit.TrueRecursion);
}
});
});
// prettier-ignore // prettier-ignore
const joystick = const joystick =
<> <>

@ -148,7 +148,7 @@ function initAugmentations(): void {
name: AugmentationNames.HemoRecirculator, name: AugmentationNames.HemoRecirculator,
moneyCost: 4.5e7, moneyCost: 4.5e7,
repCost: 1e4, repCost: 1e4,
info: "A heart implant that greatly increases the body's ability to effectively use and pump " + "blood.", info: "A heart implant that greatly increases the body's ability to effectively use and pump blood.",
strength_mult: 1.08, strength_mult: 1.08,
defense_mult: 1.08, defense_mult: 1.08,
agility_mult: 1.08, agility_mult: 1.08,
@ -430,7 +430,7 @@ function initAugmentations(): void {
repCost: 1.125e6, repCost: 1.125e6,
moneyCost: 4.25e9, moneyCost: 4.25e9,
info: info:
"Graphene is grafted and fused into the skeletal structure, " + "enhancing bone density and tensile strength.", "Graphene is grafted and fused into the skeletal structure, enhancing bone density and tensile strength.",
strength_mult: 1.7, strength_mult: 1.7,
defense_mult: 1.7, defense_mult: 1.7,
}); });
@ -1085,7 +1085,7 @@ function initAugmentations(): void {
name: AugmentationNames.FocusWire, name: AugmentationNames.FocusWire,
repCost: 7.5e4, repCost: 7.5e4,
moneyCost: 9e8, moneyCost: 9e8,
info: "A cranial implant that stops procrastination by blocking specific neural pathways " + "in the brain.", info: "A cranial implant that stops procrastination by blocking specific neural pathways in the brain.",
hacking_exp_mult: 1.05, hacking_exp_mult: 1.05,
strength_exp_mult: 1.05, strength_exp_mult: 1.05,
defense_exp_mult: 1.05, defense_exp_mult: 1.05,
@ -1486,7 +1486,7 @@ function initAugmentations(): void {
name: AugmentationNames.SmartSonar, name: AugmentationNames.SmartSonar,
repCost: 2.25e4, repCost: 2.25e4,
moneyCost: 7.5e7, moneyCost: 7.5e7,
info: "A cochlear implant that helps the player detect and locate enemies " + "using sound propagation.", info: "A cochlear implant that helps the player detect and locate enemies using sound propagation.",
dexterity_mult: 1.1, dexterity_mult: 1.1,
dexterity_exp_mult: 1.15, dexterity_exp_mult: 1.15,
crime_money_mult: 1.25, crime_money_mult: 1.25,
@ -1703,7 +1703,7 @@ function initAugmentations(): void {
"The left arm of a legendary BitRunner who ascended beyond this world. " + "The left arm of a legendary BitRunner who ascended beyond this world. " +
"It projects a light blue energy shield that protects the exposed inner parts. " + "It projects a light blue energy shield that protects the exposed inner parts. " +
"Even though it contains no weapons, the advanced tungsten titanium " + "Even though it contains no weapons, the advanced tungsten titanium " +
"alloy increases the users strength to unbelievable levels. The augmentation " + "alloy increases the user's strength to unbelievable levels. The augmentation " +
"gets more powerful over time for seemingly no reason.", "gets more powerful over time for seemingly no reason.",
strength_mult: 2.7, strength_mult: 2.7,
}); });
@ -2015,7 +2015,7 @@ function initAugmentations(): void {
repCost: 6.25e4, repCost: 6.25e4,
moneyCost: 2.75e8, moneyCost: 2.75e8,
info: info:
"Cybernetic arms created from plasteel and carbon fibers that completely replace " + "the user's organic arms.", "Cybernetic arms created from plasteel and carbon fibers that completely replace the user's organic arms.",
strength_mult: 1.3, strength_mult: 1.3,
dexterity_mult: 1.3, dexterity_mult: 1.3,
}); });

@ -202,8 +202,8 @@ BitNodes["BitNode5"] = new BitNode(
Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will upgrade its Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will upgrade its
level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. Intelligence is level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. Intelligence is
unique because it is permanent and persistent (it never gets reset back to 1). However gaining Intelligence unique because it is permanent and persistent (it never gets reset back to 1). However gaining Intelligence
experience is much slower than other stats, and it is also hidden (you won't know when you gain experience and how experience is much slower than other stats. Higher Intelligence levels will boost your production for many actions
much). Higher Intelligence levels will boost your production for many actions in the game. <br /> in the game. <br />
<br /> <br />
In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with In addition, this Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with
Formulas.exe, and will also raise all of your hacking-related multipliers by: Formulas.exe, and will also raise all of your hacking-related multipliers by:

@ -135,7 +135,7 @@ export class Action implements IAction {
if (this.decays.hasOwnProperty(decay)) { if (this.decays.hasOwnProperty(decay)) {
if (this.decays[decay] > 1) { if (this.decays[decay] > 1) {
throw new Error( throw new Error(
"Invalid decays when constructing " + "Action " + this.name + ". " + "Decay value cannot be greater than 1", `Invalid decays when constructing Action ${this.name}. Decay value cannot be greater than 1`,
); );
} }
} }

@ -8,7 +8,7 @@ export const Skills: IMap<Skill> = {};
Skills[SkillNames.BladesIntuition] = new Skill({ Skills[SkillNames.BladesIntuition] = new Skill({
name: SkillNames.BladesIntuition, name: SkillNames.BladesIntuition,
desc: desc:
"Each level of this skill increases your success chance " + "for all Contracts, Operations, and BlackOps by 3%", "Each level of this skill increases your success chance for all Contracts, Operations, and BlackOps by 3%",
baseCost: 3, baseCost: 3,
costInc: 2.1, costInc: 2.1,
successChanceAll: 3, successChanceAll: 3,
@ -33,14 +33,14 @@ export const Skills: IMap<Skill> = {};
}); });
Skills[SkillNames.DigitalObserver] = new Skill({ Skills[SkillNames.DigitalObserver] = new Skill({
name: SkillNames.DigitalObserver, name: SkillNames.DigitalObserver,
desc: "Each level of this skill increases your success chance in " + "all Operations and BlackOps by 4%", desc: "Each level of this skill increases your success chance in all Operations and BlackOps by 4%",
baseCost: 2, baseCost: 2,
costInc: 2.1, costInc: 2.1,
successChanceOperation: 4, successChanceOperation: 4,
}); });
Skills[SkillNames.Tracer] = new Skill({ Skills[SkillNames.Tracer] = new Skill({
name: SkillNames.Tracer, name: SkillNames.Tracer,
desc: "Each level of this skill increases your success chance in " + "all Contracts by 4%", desc: "Each level of this skill increases your success chance in all Contracts by 4%",
baseCost: 2, baseCost: 2,
costInc: 2.1, costInc: 2.1,
successChanceContract: 4, successChanceContract: 4,
@ -67,7 +67,7 @@ export const Skills: IMap<Skill> = {};
}); });
Skills[SkillNames.EvasiveSystem] = new Skill({ Skills[SkillNames.EvasiveSystem] = new Skill({
name: SkillNames.EvasiveSystem, name: SkillNames.EvasiveSystem,
desc: "Each level of this skill increases your effective " + "dexterity and agility for Bladeburner actions by 4%", desc: "Each level of this skill increases your effective dexterity and agility for Bladeburner actions by 4%",
baseCost: 2, baseCost: 2,
costInc: 2.1, costInc: 2.1,
effDex: 4, effDex: 4,

@ -119,17 +119,17 @@ export function SellMaterial(mat: Material, amt: string, price: string): void {
try { try {
tempQty = eval(tempQty); tempQty = eval(tempQty);
} catch (e) { } catch (e) {
throw new Error("Invalid value or expression for sell price field: " + e); throw new Error("Invalid value or expression for sell quantity field: " + e);
} }
if (tempQty == null || isNaN(parseFloat(tempQty)) || parseFloat(tempQty) < 0) { if (tempQty == null || isNaN(parseFloat(tempQty)) || parseFloat(tempQty) < 0) {
throw new Error("Invalid value or expression for sell price field"); throw new Error("Invalid value or expression for sell quantity field");
} }
mat.sllman[0] = true; mat.sllman[0] = true;
mat.sllman[1] = q; //Use sanitized input mat.sllman[1] = q; //Use sanitized input
} else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) { } else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) {
throw new Error("Invalid value for sell quantity field! Must be numeric or 'MAX'"); throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'");
} else { } else {
let q = parseFloat(amt); let q = parseFloat(amt);
if (isNaN(q)) { if (isNaN(q)) {
@ -156,10 +156,10 @@ export function SellProduct(product: Product, city: string, amt: string, price:
try { try {
temp = eval(temp); temp = eval(temp);
} catch (e) { } catch (e) {
throw new Error("Invalid value or expression for sell quantity field: " + e); throw new Error("Invalid value or expression for sell price field: " + e);
} }
if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) { if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) {
throw new Error("Invalid value or expression for sell quantity field."); throw new Error("Invalid value or expression for sell price field.");
} }
product.sCost = price; //Use sanitized price product.sCost = price; //Use sanitized price
} else { } else {
@ -184,11 +184,11 @@ export function SellProduct(product: Product, city: string, amt: string, price:
try { try {
temp = eval(temp); temp = eval(temp);
} catch (e) { } catch (e) {
throw new Error("Invalid value or expression for sell price field: " + e); throw new Error("Invalid value or expression for sell quantity field: " + e);
} }
if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) { if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) {
throw new Error("Invalid value or expression for sell price field"); throw new Error("Invalid value or expression for sell quantity field");
} }
if (all) { if (all) {
for (let i = 0; i < cities.length; ++i) { for (let i = 0; i < cities.length; ++i) {
@ -201,7 +201,7 @@ export function SellProduct(product: Product, city: string, amt: string, price:
product.sllman[city][1] = qty; //Use sanitized input product.sllman[city][1] = qty; //Use sanitized input
} }
} else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) { } else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) {
throw new Error("Invalid value for sell quantity field! Must be numeric"); throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'");
} else { } else {
let qty = parseFloat(amt); let qty = parseFloat(amt);
if (isNaN(qty)) { if (isNaN(qty)) {
@ -218,8 +218,7 @@ export function SellProduct(product: Product, city: string, amt: string, price:
product.sllman[city][0] = false; product.sllman[city][0] = false;
product.sllman[city][1] = ""; product.sllman[city][1] = "";
} }
} else { } else if (all) {
if (all) {
for (let i = 0; i < cities.length; ++i) { for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i]; const tempCity = cities[i];
product.sllman[tempCity][0] = true; product.sllman[tempCity][0] = true;
@ -229,7 +228,6 @@ export function SellProduct(product: Product, city: string, amt: string, price:
product.sllman[city][0] = true; product.sllman[city][0] = true;
product.sllman[city][1] = qty; product.sllman[city][1] = qty;
} }
}
} }
} }

@ -227,13 +227,13 @@ export function resetIndustryResearchTrees(): void {
IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy(); IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy();
IndustryResearchTrees.Fishing = getBaseResearchTreeCopy(); IndustryResearchTrees.Fishing = getBaseResearchTreeCopy();
IndustryResearchTrees.Mining = getBaseResearchTreeCopy(); IndustryResearchTrees.Mining = getBaseResearchTreeCopy();
IndustryResearchTrees.Food = getBaseResearchTreeCopy(); IndustryResearchTrees.Food = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy(); IndustryResearchTrees.Tobacco = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.Chemical = getBaseResearchTreeCopy(); IndustryResearchTrees.Chemical = getBaseResearchTreeCopy();
IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy(); IndustryResearchTrees.Pharmaceutical = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.Computer = getBaseResearchTreeCopy(); IndustryResearchTrees.Computer = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.Robotics = getBaseResearchTreeCopy(); IndustryResearchTrees.Robotics = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.Software = getBaseResearchTreeCopy(); IndustryResearchTrees.Software = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy(); IndustryResearchTrees.Healthcare = getProductIndustryResearchTreeCopy();
IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy(); IndustryResearchTrees.RealEstate = getProductIndustryResearchTreeCopy();
} }

@ -106,7 +106,7 @@ export const researchMetadata: IConstructorParams[] = [
{ {
name: "JoyWire", name: "JoyWire",
cost: 20e3, cost: 20e3,
desc: "A brain implant which is installed in employees, increasing their " + "maximum happiness by 10.", desc: "A brain implant which is installed in employees, increasing their maximum happiness by 10.",
}, },
{ {
name: "Market-TA.I", name: "Market-TA.I",
@ -160,7 +160,7 @@ export const researchMetadata: IConstructorParams[] = [
{ {
name: "sudo.Assist", name: "sudo.Assist",
cost: 15e3, cost: 15e3,
desc: "Develop a virtual assistant AI to handle and manage administrative " + "issues for your corporation.", desc: "Develop a virtual assistant AI to handle and manage administrative issues for your corporation.",
}, },
{ {
name: "uPgrade: Capacity.I", name: "uPgrade: Capacity.I",

@ -33,8 +33,7 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
function throwParty(): void { function throwParty(): void {
if (cost === null || isNaN(cost) || cost < 0) { if (cost === null || isNaN(cost) || cost < 0) {
dialogBoxCreate("Invalid value entered"); dialogBoxCreate("Invalid value entered");
} else { } else if (!canParty) {
if (!canParty) {
dialogBoxCreate("You don't have enough company funds to throw a party!"); dialogBoxCreate("You don't have enough company funds to throw a party!");
} else { } else {
const mult = ThrowParty(corp, props.office, cost); const mult = ThrowParty(corp, props.office, cost);
@ -46,7 +45,6 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
props.rerender(); props.rerender();
props.onClose(); props.onClose();
} }
}
} }
function EffectText(): React.ReactElement { function EffectText(): React.ReactElement {

@ -19,6 +19,8 @@ export enum Exploit {
RealityAlteration = "RealityAlteration", RealityAlteration = "RealityAlteration",
N00dles = "N00dles", N00dles = "N00dles",
YoureNotMeantToAccessThis = "YoureNotMeantToAccessThis", YoureNotMeantToAccessThis = "YoureNotMeantToAccessThis",
TrueRecursion = "TrueRecursion",
INeedARainbow = "INeedARainbow",
// To the players reading this. Yes you're supposed to add EditSaveFile by // To the players reading this. Yes you're supposed to add EditSaveFile by
// editing your save file, yes you could add them all, no we don't care // editing your save file, yes you could add them all, no we don't care
// that's not the point. // that's not the point.
@ -37,6 +39,8 @@ const names: {
RealityAlteration: "by altering reality to suit your whims.", RealityAlteration: "by altering reality to suit your whims.",
N00dles: "by harnessing the power of the n00dles.", N00dles: "by harnessing the power of the n00dles.",
YoureNotMeantToAccessThis: "by accessing the dev menu.", YoureNotMeantToAccessThis: "by accessing the dev menu.",
TrueRecursion: "by truly recursing.",
INeedARainbow: "by using the power of the rainbow.",
}; };
export function ExploitName(exploit: string): string { export function ExploitName(exploit: string): string {

@ -117,8 +117,7 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
const factionInfo = fac.getInfo(); const factionInfo = fac.getInfo();
const hasPrereqs = hasAugmentationPrereqs(aug); const hasPrereqs = hasAugmentationPrereqs(aug);
if (!hasPrereqs) { if (!hasPrereqs) {
const txt = const txt = `You must first purchase or install ${aug.prereqs.join(",")} before you can purchase this one.`;
"You must first purchase or install " + aug.prereqs.join(",") + " before you can " + "purchase this one.";
if (sing) { if (sing) {
return txt; return txt;
} else { } else {
@ -166,8 +165,7 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
if (sing) { if (sing) {
return "You purchased " + aug.name; return "You purchased " + aug.name;
} else { } else if (!Settings.SuppressBuyAugmentationConfirmation) {
if (!Settings.SuppressBuyAugmentationConfirmation) {
dialogBoxCreate( dialogBoxCreate(
"You purchased " + "You purchased " +
aug.name + aug.name +
@ -177,7 +175,6 @@ export function purchaseAugmentation(aug: Augmentation, fac: Faction, sing = fal
"augmentations will now be more expensive.", "augmentations will now be more expensive.",
); );
} }
}
} else { } else {
dialogBoxCreate( dialogBoxCreate(
"Hmm, something went wrong when trying to purchase an Augmentation. " + "Hmm, something went wrong when trying to purchase an Augmentation. " +

@ -28,7 +28,7 @@ type IProps = {
}; };
// Info text for all options on the UI // Info text for all options on the UI
const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and " + "faction reputation"; const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and faction reputation";
const hackingContractsInfo = const hackingContractsInfo =
"Complete hacking contracts for your faction. " + "Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " + "Your effectiveness, which determines how much " +

@ -1,14 +1,21 @@
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Container from "@mui/material/Container";
import Paper from "@mui/material/Paper";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import {
Box,
Button,
Container,
Paper,
TableBody,
TableRow,
Typography
} from "@mui/material";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Table, TableCell } from "../../ui/React/Table"; import { Table, TableCell } from "../../ui/React/Table";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { Faction } from "../Faction"; import { Faction } from "../Faction";
import { joinFaction } from "../FactionHelpers"; import { joinFaction } from "../FactionHelpers";
import { Factions } from "../Factions"; import { Factions } from "../Factions";
@ -51,6 +58,28 @@ export function FactionsRoot(props: IProps): React.ReactElement {
setRerender((x) => !x); setRerender((x) => !x);
} }
const getAugsLeft = (faction: Faction, player: IPlayer): number => {
const isPlayersGang = player.inGang() && player.getGangName() === faction.name;
let augs: string[] = [];
if (isPlayersGang) {
for (const augName of Object.keys(Augmentations)) {
if (
augName === AugmentationNames.NeuroFluxGovernor ||
augName === AugmentationNames.TheRedPill && player.bitNodeN !== 2 ||
Augmentations[augName].isSpecial
) continue;
augs.push(augName)
}
} else {
augs = faction.augmentations.slice();
}
return augs.filter(
(augmentation: string) => !player.hasAugmentation(augmentation)
).length;
}
return ( return (
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}> <Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Factions</Typography> <Typography variant="h4">Factions</Typography>
@ -82,11 +111,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
<TableCell align="right"> <TableCell align="right">
<Box ml={1} mb={1}> <Box ml={1} mb={1}>
<Button sx={{ width: '100%' }} onClick={() => openFactionAugPage(Factions[faction])}> <Button sx={{ width: '100%' }} onClick={() => openFactionAugPage(Factions[faction])}>
Augmentations Left: {Factions[faction] Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
.augmentations
.filter((augmentation: string) =>
!props.player.hasAugmentation(augmentation))
.length}
</Button> </Button>
</Box> </Box>
</TableCell> </TableCell>

@ -52,21 +52,19 @@ export function SpecialLocation(props: IProps): React.ReactElement {
if (p.inBladeburner()) { if (p.inBladeburner()) {
// Enter Bladeburner division // Enter Bladeburner division
router.toBladeburner(); router.toBladeburner();
} else { } else if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {
// Apply for Bladeburner division // Apply for Bladeburner division
if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) { p.startBladeburner({new: true});
p.startBladeburner({ new: true }); dialogBoxCreate("You have been accepted into the Bladeburner division!");
dialogBoxCreate("You have been accepted into the Bladeburner division!"); setRerender((old) => !old);
setRerender((old) => !old);
const worldHeader = document.getElementById("world-menu-header"); const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) { if (worldHeader instanceof HTMLElement) {
worldHeader.click(); worldHeader.click();
worldHeader.click(); worldHeader.click();
}
} else {
dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)");
} }
} else {
dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)");
} }
} }

@ -108,6 +108,7 @@ export const RamCosts: IMap<any> = {
hackAnalyzeSecurity: RamCostConstants.ScriptHackAnalyzeRamCost, hackAnalyzeSecurity: RamCostConstants.ScriptHackAnalyzeRamCost,
hackAnalyzeChance: RamCostConstants.ScriptHackAnalyzeRamCost, hackAnalyzeChance: RamCostConstants.ScriptHackAnalyzeRamCost,
sleep: 0, sleep: 0,
asleep: 0,
share: 2.4, share: 2.4,
getSharePower: 0.2, getSharePower: 0.2,
grow: RamCostConstants.ScriptGrowRamCost, grow: RamCostConstants.ScriptGrowRamCost,

@ -102,7 +102,7 @@ export class WorkerScript {
scriptRef: RunningScript; scriptRef: RunningScript;
/** /**
* IP Address on which this script is running * hostname on which this script is running
*/ */
hostname: string; hostname: string;

@ -174,7 +174,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
throw makeRuntimeRejectMsg( throw makeRuntimeRejectMsg(
workerScript, workerScript,
`Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` + `Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` +
`This is probably a bug. Please report to game developer`, `This is probably a bug. Please report to game developer`,
); );
} }
@ -425,19 +425,22 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const helper = { const helper = {
updateDynamicRam: updateDynamicRam, updateDynamicRam: updateDynamicRam,
makeRuntimeErrorMsg: makeRuntimeErrorMsg, makeRuntimeErrorMsg: makeRuntimeErrorMsg,
string: (funcName: string, argName: string, v: any): string => { string: (funcName: string, argName: string, v: unknown): string => {
if (typeof v === "string") return v; if (typeof v === "string") return v;
if (typeof v === "number") return v + ""; // cast to string; if (typeof v === "number") return v + ""; // cast to string;
throw makeRuntimeErrorMsg(funcName, `${argName} should be a string`); throw makeRuntimeErrorMsg(funcName, `${argName} should be a string`);
}, },
number: (funcName: string, argName: string, v: any): number => { number: (funcName: string, argName: string, v: unknown): number => {
if (!isNaN(v)) { if (typeof v === "string") {
if (typeof v === "number") return v; const x = parseFloat(v);
if (!isNaN(parseFloat(v))) return parseFloat(v); if (!isNaN(x)) return x; // otherwise it wasn't even a string representing a number.
} else if (typeof v === "number") {
if (isNaN(v)) throw makeRuntimeErrorMsg(funcName, `${argName} is NaN`);
return v;
} }
throw makeRuntimeErrorMsg(funcName, `${argName} should be a number`); throw makeRuntimeErrorMsg(funcName, `${argName} should be a number`);
}, },
boolean: (v: any): boolean => { boolean: (v: unknown): boolean => {
return !!v; // Just convert it to boolean. return !!v; // Just convert it to boolean.
}, },
getServer: safeGetServer, getServer: safeGetServer,
@ -467,7 +470,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const gang = NetscriptGang(Player, workerScript, helper); const gang = NetscriptGang(Player, workerScript, helper);
const sleeve = NetscriptSleeve(Player, workerScript, helper); const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript); const extra = NetscriptExtra(Player, workerScript, helper);
const hacknet = NetscriptHacknet(Player, workerScript, helper); const hacknet = NetscriptHacknet(Player, workerScript, helper);
const stanek = NetscriptStanek(Player, workerScript, helper); const stanek = NetscriptStanek(Player, workerScript, helper);
const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
@ -550,6 +553,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculatePercentMoneyHacked(server, Player); return calculatePercentMoneyHacked(server, Player);
}, },
hackAnalyzeSecurity: function (threads: any): number { hackAnalyzeSecurity: function (threads: any): number {
updateDynamicRam("hackAnalyzeSecurity", getRamCost(Player, "hackAnalyzeSecurity"));
return CONSTANTS.ServerFortifyAmount * threads; return CONSTANTS.ServerFortifyAmount * threads;
}, },
hackAnalyzeChance: function (hostname: any): any { hackAnalyzeChance: function (hostname: any): any {
@ -564,6 +568,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculateHackingChance(server, Player); return calculateHackingChance(server, Player);
}, },
sleep: function (time: any): any { sleep: function (time: any): any {
updateDynamicRam("sleep", getRamCost(Player, "sleep"));
if (time === undefined) { if (time === undefined) {
throw makeRuntimeErrorMsg("sleep", "Takes 1 argument."); throw makeRuntimeErrorMsg("sleep", "Takes 1 argument.");
} }
@ -573,6 +578,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}); });
}, },
asleep: function (time: any): any { asleep: function (time: any): any {
updateDynamicRam("asleep", getRamCost(Player, "asleep"));
if (time === undefined) { if (time === undefined) {
throw makeRuntimeErrorMsg("asleep", "Takes 1 argument."); throw makeRuntimeErrorMsg("asleep", "Takes 1 argument.");
} }
@ -650,6 +656,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return numCycleForGrowth(server, Number(growth), Player, cores); return numCycleForGrowth(server, Number(growth), Player, cores);
}, },
growthAnalyzeSecurity: function (threads: any): number { growthAnalyzeSecurity: function (threads: any): number {
updateDynamicRam("growthAnalyzeSecurity", getRamCost(Player, "growthAnalyzeSecurity"));
return 2 * CONSTANTS.ServerFortifyAmount * threads; return 2 * CONSTANTS.ServerFortifyAmount * threads;
}, },
weaken: function (hostname: any, { threads: requestedThreads }: any = {}): any { weaken: function (hostname: any, { threads: requestedThreads }: any = {}): any {
@ -692,7 +699,8 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
workerScript.log( workerScript.log(
"weaken", "weaken",
() => () =>
`'${server.hostname}' security level weakened to ${server.hackDifficulty `'${server.hostname}' security level weakened to ${
server.hackDifficulty
}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)})`, }. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)})`,
); );
workerScript.scriptRef.onlineExpGained += expGain; workerScript.scriptRef.onlineExpGained += expGain;
@ -701,10 +709,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}); });
}, },
weakenAnalyze: function (threads: any, cores: any = 1): number { weakenAnalyze: function (threads: any, cores: any = 1): number {
updateDynamicRam("weakenAnalyze", getRamCost(Player, "weakenAnalyze"));
const coreBonus = 1 + (cores - 1) / 16; const coreBonus = 1 + (cores - 1) / 16;
return CONSTANTS.ServerWeakenAmount * threads * coreBonus * BitNodeMultipliers.ServerWeakenRate; return CONSTANTS.ServerWeakenAmount * threads * coreBonus * BitNodeMultipliers.ServerWeakenRate;
}, },
share: function (): Promise<void> { share: function (): Promise<void> {
updateDynamicRam("share", getRamCost(Player, "share"));
workerScript.log("share", () => "Sharing this computer."); workerScript.log("share", () => "Sharing this computer.");
const end = StartSharing(workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.intelligence, 2)); const end = StartSharing(workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.intelligence, 2));
return netscriptDelay(10000, workerScript).finally(function () { return netscriptDelay(10000, workerScript).finally(function () {
@ -713,21 +723,25 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}); });
}, },
getSharePower: function (): number { getSharePower: function (): number {
updateDynamicRam("getSharePower", getRamCost(Player, "getSharePower"));
return CalculateShareMult(); return CalculateShareMult();
}, },
print: function (...args: any[]): void { print: function (...args: any[]): void {
updateDynamicRam("print", getRamCost(Player, "print"));
if (args.length === 0) { if (args.length === 0) {
throw makeRuntimeErrorMsg("print", "Takes at least 1 argument."); throw makeRuntimeErrorMsg("print", "Takes at least 1 argument.");
} }
workerScript.print(argsToString(args)); workerScript.print(argsToString(args));
}, },
printf: function (format: string, ...args: any[]): void { printf: function (format: string, ...args: any[]): void {
updateDynamicRam("printf", getRamCost(Player, "printf"));
if (typeof format !== "string") { if (typeof format !== "string") {
throw makeRuntimeErrorMsg("printf", "First argument must be string for the format."); throw makeRuntimeErrorMsg("printf", "First argument must be string for the format.");
} }
workerScript.print(vsprintf(format, args)); workerScript.print(vsprintf(format, args));
}, },
tprint: function (...args: any[]): void { tprint: function (...args: any[]): void {
updateDynamicRam("tprint", getRamCost(Player, "tprint"));
if (args.length === 0) { if (args.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument."); throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
} }
@ -751,6 +765,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
Terminal.print(`${workerScript.scriptRef.filename}: ${str}`); Terminal.print(`${workerScript.scriptRef.filename}: ${str}`);
}, },
tprintf: function (format: any, ...args: any): any { tprintf: function (format: any, ...args: any): any {
updateDynamicRam("tprintf", getRamCost(Player, "tprintf"));
if (typeof format !== "string") { if (typeof format !== "string") {
throw makeRuntimeErrorMsg("tprintf", "First argument must be string for the format."); throw makeRuntimeErrorMsg("tprintf", "First argument must be string for the format.");
} }
@ -775,9 +790,11 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
Terminal.print(`${str}`); Terminal.print(`${str}`);
}, },
clearLog: function (): any { clearLog: function (): any {
updateDynamicRam("clearLog", getRamCost(Player, "clearLog"));
workerScript.scriptRef.clearLog(); workerScript.scriptRef.clearLog();
}, },
disableLog: function (fn: any): any { disableLog: function (fn: any): any {
updateDynamicRam("disableLog", getRamCost(Player, "disableLog"));
if (fn === "ALL") { if (fn === "ALL") {
for (fn of Object.keys(possibleLogs)) { for (fn of Object.keys(possibleLogs)) {
workerScript.disableLogs[fn] = true; workerScript.disableLogs[fn] = true;
@ -791,6 +808,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}, },
enableLog: function (fn: any): any { enableLog: function (fn: any): any {
updateDynamicRam("enableLog", getRamCost(Player, "enableLog"));
if (fn === "ALL") { if (fn === "ALL") {
for (fn of Object.keys(possibleLogs)) { for (fn of Object.keys(possibleLogs)) {
delete workerScript.disableLogs[fn]; delete workerScript.disableLogs[fn];
@ -803,12 +821,14 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
workerScript.log("enableLog", () => `Enabled logging for ${fn}`); workerScript.log("enableLog", () => `Enabled logging for ${fn}`);
}, },
isLogEnabled: function (fn: any): any { isLogEnabled: function (fn: any): any {
updateDynamicRam("isLogEnabled", getRamCost(Player, "isLogEnabled"));
if (possibleLogs[fn] === undefined) { if (possibleLogs[fn] === undefined) {
throw makeRuntimeErrorMsg("isLogEnabled", `Invalid argument: ${fn}.`); throw makeRuntimeErrorMsg("isLogEnabled", `Invalid argument: ${fn}.`);
} }
return !workerScript.disableLogs[fn]; return !workerScript.disableLogs[fn];
}, },
getScriptLogs: function (fn: any, hostname: any, ...scriptArgs: any): any { getScriptLogs: function (fn: any, hostname: any, ...scriptArgs: any): any {
updateDynamicRam("getScriptLogs", getRamCost(Player, "getScriptLogs"));
const runningScriptObj = getRunningScript(fn, hostname, "getScriptLogs", scriptArgs); const runningScriptObj = getRunningScript(fn, hostname, "getScriptLogs", scriptArgs);
if (runningScriptObj == null) { if (runningScriptObj == null) {
workerScript.log("getScriptLogs", () => getCannotFindRunningScriptErrorMessage(fn, hostname, scriptArgs)); workerScript.log("getScriptLogs", () => getCannotFindRunningScriptErrorMessage(fn, hostname, scriptArgs));
@ -818,6 +838,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return runningScriptObj.logs.slice(); return runningScriptObj.logs.slice();
}, },
tail: function (fn: any, hostname: any = workerScript.hostname, ...scriptArgs: any): any { tail: function (fn: any, hostname: any = workerScript.hostname, ...scriptArgs: any): any {
updateDynamicRam("tail", getRamCost(Player, "tail"));
let runningScriptObj; let runningScriptObj;
if (arguments.length === 0) { if (arguments.length === 0) {
runningScriptObj = workerScript.scriptRef; runningScriptObj = workerScript.scriptRef;
@ -1084,6 +1105,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return scriptsRunning; return scriptsRunning;
}, },
exit: function (): any { exit: function (): any {
updateDynamicRam("exit", getRamCost(Player, "exit"));
workerScript.running = false; // Prevent workerScript from "finishing execution naturally" workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(workerScript)) { if (killWorkerScript(workerScript)) {
workerScript.log("exit", () => "Exiting..."); workerScript.log("exit", () => "Exiting...");
@ -1647,7 +1669,10 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const cost = getPurchaseServerCost(ram); const cost = getPurchaseServerCost(ram);
if (cost === Infinity) { if (cost === Infinity) {
if (ram > getPurchaseServerMaxRam()) { if (ram > getPurchaseServerMaxRam()) {
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must not be greater than getPurchaseServerMaxRam`); workerScript.log(
"purchaseServer",
() => `Invalid argument: ram='${ram}' must not be greater than getPurchaseServerMaxRam`,
);
} else { } else {
workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must be a positive power of 2`); workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must be a positive power of 2`);
} }
@ -1769,6 +1794,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return res; return res;
}, },
writePort: function (port: any, data: any = ""): any { writePort: function (port: any, data: any = ""): any {
updateDynamicRam("writePort", getRamCost(Player, "writePort"));
if (typeof data !== "string" && typeof data !== "number") { if (typeof data !== "string" && typeof data !== "number") {
throw makeRuntimeErrorMsg( throw makeRuntimeErrorMsg(
"writePort", "writePort",
@ -1857,6 +1883,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}, },
readPort: function (port: any): any { readPort: function (port: any): any {
updateDynamicRam("readPort", getRamCost(Player, "readPort"));
// Read from port // Read from port
const iport = helper.getValidPort("readPort", port); const iport = helper.getValidPort("readPort", port);
const x = iport.read(); const x = iport.read();
@ -1916,6 +1943,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return 0; return 0;
}, },
clearPort: function (port: any): any { clearPort: function (port: any): any {
updateDynamicRam("clearPort", getRamCost(Player, "clearPort"));
// Clear port // Clear port
const iport = helper.getValidPort("clearPort", port); const iport = helper.getValidPort("clearPort", port);
return iport.clear(); return iport.clear();
@ -1964,6 +1992,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return suc; return suc;
}, },
getScriptName: function (): any { getScriptName: function (): any {
updateDynamicRam("getScriptName", getRamCost(Player, "getScriptName"));
return workerScript.name; return workerScript.name;
}, },
getScriptRam: function (scriptname: any, hostname: any = workerScript.hostname): any { getScriptRam: function (scriptname: any, hostname: any = workerScript.hostname): any {
@ -2095,6 +2124,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
} }
}, },
nFormat: function (n: any, format: any): any { nFormat: function (n: any, format: any): any {
updateDynamicRam("nFormat", getRamCost(Player, "nFormat"));
if (isNaN(n) || isNaN(parseFloat(n)) || typeof format !== "string") { if (isNaN(n) || isNaN(parseFloat(n)) || typeof format !== "string") {
return ""; return "";
} }
@ -2102,6 +2132,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return numeralWrapper.format(parseFloat(n), format); return numeralWrapper.format(parseFloat(n), format);
}, },
tFormat: function (milliseconds: any, milliPrecision: any = false): any { tFormat: function (milliseconds: any, milliPrecision: any = false): any {
updateDynamicRam("tFormat", getRamCost(Player, "tFormat"));
return convertTimeMsToTimeElapsedString(milliseconds, milliPrecision); return convertTimeMsToTimeElapsedString(milliseconds, milliPrecision);
}, },
getTimeSinceLastAug: function (): any { getTimeSinceLastAug: function (): any {
@ -2109,17 +2140,20 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return Player.playtimeSinceLastAug; return Player.playtimeSinceLastAug;
}, },
alert: function (message: any): void { alert: function (message: any): void {
updateDynamicRam("alert", getRamCost(Player, "alert"));
message = argsToString([message]); message = argsToString([message]);
dialogBoxCreate(message); dialogBoxCreate(message);
}, },
toast: function (message: any, variant: any = "success", duration: any = 2000): void { toast: function (message: any, variant: any = "success", duration: any = 2000): void {
updateDynamicRam("toast", getRamCost(Player, "toast"));
if (!["success", "info", "warning", "error"].includes(variant)) if (!["success", "info", "warning", "error"].includes(variant))
throw new Error(`variant must be one of "success", "info", "warning", or "error"`); throw new Error(`variant must be one of "success", "info", "warning", or "error"`);
message = argsToString([message]); message = argsToString([message]);
SnackbarEvents.emit(message, variant, duration); SnackbarEvents.emit(message, variant, duration);
}, },
prompt: function (txt: any): any { prompt: function (txt: any, options?: { type?: string; options?: string[] }): any {
updateDynamicRam("prompt", getRamCost(Player, "prompt"));
if (!isString(txt)) { if (!isString(txt)) {
txt = JSON.stringify(txt); txt = JSON.stringify(txt);
} }
@ -2127,11 +2161,13 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return new Promise(function (resolve) { return new Promise(function (resolve) {
PromptEvent.emit({ PromptEvent.emit({
txt: txt, txt: txt,
options,
resolve: resolve, resolve: resolve,
}); });
}); });
}, },
wget: async function (url: any, target: any, hostname: any = workerScript.hostname): Promise<boolean> { wget: async function (url: any, target: any, hostname: any = workerScript.hostname): Promise<boolean> {
updateDynamicRam("wget", getRamCost(Player, "wget"));
if (!isScriptFilename(target) && !target.endsWith(".txt")) { if (!isScriptFilename(target) && !target.endsWith(".txt")) {
workerScript.log("wget", () => `Invalid target file: '${target}'. Must be a script or text file.`); workerScript.log("wget", () => `Invalid target file: '${target}'. Must be a script or text file.`);
return Promise.resolve(false); return Promise.resolve(false);
@ -2173,7 +2209,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction); return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
}, },
getOwnedSourceFiles: function (): SourceFileLvl[] { getOwnedSourceFiles: function (): SourceFileLvl[] {
helper.updateDynamicRam("getOwnedSourceFiles", getRamCost(Player, "getOwnedSourceFiles")); updateDynamicRam("getOwnedSourceFiles", getRamCost(Player, "getOwnedSourceFiles"));
const res: SourceFileLvl[] = []; const res: SourceFileLvl[] = [];
for (let i = 0; i < Player.sourceFiles.length; ++i) { for (let i = 0; i < Player.sourceFiles.length; ++i) {
res.push({ res.push({
@ -2184,7 +2220,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return res; return res;
}, },
getPlayer: function (): INetscriptPlayer { getPlayer: function (): INetscriptPlayer {
helper.updateDynamicRam("getPlayer", getRamCost(Player, "getPlayer")); updateDynamicRam("getPlayer", getRamCost(Player, "getPlayer"));
const data = { const data = {
hacking: Player.hacking, hacking: Player.hacking,
@ -2273,16 +2309,20 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
jobs: {}, jobs: {},
factions: Player.factions.slice(), factions: Player.factions.slice(),
tor: Player.hasTorRouter(), tor: Player.hasTorRouter(),
inBladeburner: Player.inBladeburner(),
hasCorporation: Player.hasCorporation(), hasCorporation: Player.hasCorporation(),
}; };
Object.assign(data.jobs, Player.jobs); Object.assign(data.jobs, Player.jobs);
return data; return data;
}, },
atExit: function (f: any): void { atExit: function (f: any): void {
updateDynamicRam("atExit", getRamCost(Player, "atExit"));
if (typeof f !== "function") { if (typeof f !== "function") {
throw makeRuntimeErrorMsg("atExit", "argument should be function"); throw makeRuntimeErrorMsg("atExit", "argument should be function");
} }
workerScript.atExit = () => { f(); }; // Wrap the user function to prevent WorkerScript leaking as 'this' workerScript.atExit = () => {
f();
}; // Wrap the user function to prevent WorkerScript leaking as 'this'
}, },
mv: function (host: string, source: string, destination: string): void { mv: function (host: string, source: string, destination: string): void {
updateDynamicRam("mv", getRamCost(Player, "mv")); updateDynamicRam("mv", getRamCost(Player, "mv"));

@ -1,6 +1,8 @@
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { Exploit } from "../Exploits/Exploit"; import { Exploit } from "../Exploits/Exploit";
import * as bcrypt from "bcryptjs";
import { INetscriptHelper } from "./INetscriptHelper";
export interface INetscriptExtra { export interface INetscriptExtra {
heart: { heart: {
@ -9,9 +11,10 @@ export interface INetscriptExtra {
exploit(): void; exploit(): void;
bypass(doc: Document): void; bypass(doc: Document): void;
alterReality(): void; alterReality(): void;
rainbow(guess: string): void;
} }
export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INetscriptExtra { export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): INetscriptExtra {
return { return {
heart: { heart: {
// Easter egg function // Easter egg function
@ -22,17 +25,18 @@ export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INe
exploit: function (): void { exploit: function (): void {
player.giveExploit(Exploit.UndocumentedFunctionCall); player.giveExploit(Exploit.UndocumentedFunctionCall);
}, },
bypass: function (doc: any): void { bypass: function (doc: unknown): void {
// reset both fields first // reset both fields first
doc.completely_unused_field = undefined; const d = doc as any;
d.completely_unused_field = undefined;
const real_document: any = document; const real_document: any = document;
real_document.completely_unused_field = undefined; real_document.completely_unused_field = undefined;
// set one to true and check that it affected the other. // set one to true and check that it affected the other.
real_document.completely_unused_field = true; real_document.completely_unused_field = true;
if (doc.completely_unused_field && workerScript.ramUsage === 1.6) { if (d.completely_unused_field && workerScript.ramUsage === 1.6) {
player.giveExploit(Exploit.Bypass); player.giveExploit(Exploit.Bypass);
} }
doc.completely_unused_field = undefined; d.completely_unused_field = undefined;
real_document.completely_unused_field = undefined; real_document.completely_unused_field = undefined;
}, },
alterReality: function (): void { alterReality: function (): void {
@ -50,5 +54,17 @@ export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INe
player.giveExploit(Exploit.RealityAlteration); player.giveExploit(Exploit.RealityAlteration);
} }
}, },
rainbow: function (guess: unknown): void {
async function tryGuess(): Promise<void> {
const verified = await bcrypt.compare(
helper.string("rainbow", "guess", guess),
"$2a$10$aertxDEkgor8baVtQDZsLuMwwGYmkRM/ohcA6FjmmzIHQeTCsrCcO",
);
if (verified) {
player.giveExploit(Exploit.INeedARainbow);
}
}
tryGuess();
},
}; };
} }

@ -38,40 +38,6 @@ import {
calculateAscensionPointsGain, calculateAscensionPointsGain,
} from "../Gang/formulas/formulas"; } from "../Gang/formulas/formulas";
export interface INetscriptFormulas {
skills: {
calculateSkill(exp: any, mult?: any): any;
calculateExp(skill: any, mult?: any): any;
};
hacking: {
hackChance(server: any, player: any): any;
hackExp(server: any, player: any): any;
hackPercent(server: any, player: any): any;
growPercent(server: any, threads: any, player: any, cores?: any): any;
hackTime(server: any, player: any): any;
growTime(server: any, player: any): any;
weakenTime(server: any, player: any): any;
};
hacknetNodes: {
moneyGainRate(level: any, ram: any, cores: any, mult?: any): any;
levelUpgradeCost(startingLevel: any, extraLevels?: any, costMult?: any): any;
ramUpgradeCost(startingRam: any, extraLevels?: any, costMult?: any): any;
coreUpgradeCost(startingCore: any, extraCores?: any, costMult?: any): any;
hacknetNodeCost(n: any, mult: any): any;
constants(): any;
};
hacknetServers: {
hashGainRate(level: any, ramUsed: any, maxRam: any, cores: any, mult?: any): any;
levelUpgradeCost(startingLevel: any, extraLevels?: any, costMult?: any): any;
ramUpgradeCost(startingRam: any, extraLevels?: any, costMult?: any): any;
coreUpgradeCost(startingCore: any, extraCores?: any, costMult?: any): any;
cacheUpgradeCost(startingCache: any, extraCache?: any): any;
hashUpgradeCost(upgName: any, level: any): any;
hacknetServerCost(n: any, mult: any): any;
constants(): any;
};
}
export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IFormulas { export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IFormulas {
const checkFormulasAccess = function (func: string): void { const checkFormulasAccess = function (func: string): void {
if (!player.hasProgram(Programs.Formulas.name)) { if (!player.hasProgram(Programs.Formulas.name)) {
@ -80,63 +46,84 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
}; };
return { return {
skills: { skills: {
calculateSkill: function (exp: any, mult: any = 1): any { calculateSkill: function (_exp: unknown, _mult: unknown = 1): number {
const exp = helper.number("calculateSkill", "exp", _exp);
const mult = helper.number("calculateSkill", "mult", _mult);
checkFormulasAccess("skills.calculateSkill"); checkFormulasAccess("skills.calculateSkill");
return calculateSkill(exp, mult); return calculateSkill(exp, mult);
}, },
calculateExp: function (skill: any, mult: any = 1): any { calculateExp: function (_skill: unknown, _mult: unknown = 1): number {
const skill = helper.number("calculateExp", "skill", _skill);
const mult = helper.number("calculateExp", "mult", _mult);
checkFormulasAccess("skills.calculateExp"); checkFormulasAccess("skills.calculateExp");
return calculateExp(skill, mult); return calculateExp(skill, mult);
}, },
}, },
hacking: { hacking: {
hackChance: function (server: any, player: any): any { hackChance: function (server: any, player: any): number {
checkFormulasAccess("hacking.hackChance"); checkFormulasAccess("hacking.hackChance");
return calculateHackingChance(server, player); return calculateHackingChance(server, player);
}, },
hackExp: function (server: any, player: any): any { hackExp: function (server: any, player: any): number {
checkFormulasAccess("hacking.hackExp"); checkFormulasAccess("hacking.hackExp");
return calculateHackingExpGain(server, player); return calculateHackingExpGain(server, player);
}, },
hackPercent: function (server: any, player: any): any { hackPercent: function (server: any, player: any): number {
checkFormulasAccess("hacking.hackPercent"); checkFormulasAccess("hacking.hackPercent");
return calculatePercentMoneyHacked(server, player); return calculatePercentMoneyHacked(server, player);
}, },
growPercent: function (server: any, threads: any, player: any, cores: any = 1): any { growPercent: function (server: any, _threads: unknown, player: any, _cores: unknown = 1): number {
const threads = helper.number("growPercent", "threads", _threads);
const cores = helper.number("growPercent", "cores", _cores);
checkFormulasAccess("hacking.growPercent"); checkFormulasAccess("hacking.growPercent");
return calculateServerGrowth(server, threads, player, cores); return calculateServerGrowth(server, threads, player, cores);
}, },
hackTime: function (server: any, player: any): any { hackTime: function (server: any, player: any): number {
checkFormulasAccess("hacking.hackTime"); checkFormulasAccess("hacking.hackTime");
return calculateHackingTime(server, player) * 1000; return calculateHackingTime(server, player) * 1000;
}, },
growTime: function (server: any, player: any): any { growTime: function (server: any, player: any): number {
checkFormulasAccess("hacking.growTime"); checkFormulasAccess("hacking.growTime");
return calculateGrowTime(server, player) * 1000; return calculateGrowTime(server, player) * 1000;
}, },
weakenTime: function (server: any, player: any): any { weakenTime: function (server: any, player: any): number {
checkFormulasAccess("hacking.weakenTime"); checkFormulasAccess("hacking.weakenTime");
return calculateWeakenTime(server, player) * 1000; return calculateWeakenTime(server, player) * 1000;
}, },
}, },
hacknetNodes: { hacknetNodes: {
moneyGainRate: function (level: any, ram: any, cores: any, mult: any = 1): any { moneyGainRate: function (_level: unknown, _ram: unknown, _cores: unknown, _mult: unknown = 1): number {
const level = helper.number("moneyGainRate", "level", _level);
const ram = helper.number("moneyGainRate", "ram", _ram);
const cores = helper.number("moneyGainRate", "cores", _cores);
const mult = helper.number("moneyGainRate", "mult", _mult);
checkFormulasAccess("hacknetNodes.moneyGainRate"); checkFormulasAccess("hacknetNodes.moneyGainRate");
return calculateMoneyGainRate(level, ram, cores, mult); return calculateMoneyGainRate(level, ram, cores, mult);
}, },
levelUpgradeCost: function (startingLevel: any, extraLevels: any = 1, costMult: any = 1): any { levelUpgradeCost: function (_startingLevel: unknown, _extraLevels: unknown = 1, _costMult: unknown = 1): number {
const startingLevel = helper.number("levelUpgradeCost", "startingLevel", _startingLevel);
const extraLevels = helper.number("levelUpgradeCost", "extraLevels", _extraLevels);
const costMult = helper.number("levelUpgradeCost", "costMult", _costMult);
checkFormulasAccess("hacknetNodes.levelUpgradeCost"); checkFormulasAccess("hacknetNodes.levelUpgradeCost");
return calculateLevelUpgradeCost(startingLevel, extraLevels, costMult); return calculateLevelUpgradeCost(startingLevel, extraLevels, costMult);
}, },
ramUpgradeCost: function (startingRam: any, extraLevels: any = 1, costMult: any = 1): any { ramUpgradeCost: function (_startingRam: unknown, _extraLevels: unknown = 1, _costMult: unknown = 1): number {
const startingRam = helper.number("ramUpgradeCost", "startingRam", _startingRam);
const extraLevels = helper.number("ramUpgradeCost", "extraLevels", _extraLevels);
const costMult = helper.number("ramUpgradeCost", "costMult", _costMult);
checkFormulasAccess("hacknetNodes.ramUpgradeCost"); checkFormulasAccess("hacknetNodes.ramUpgradeCost");
return calculateRamUpgradeCost(startingRam, extraLevels, costMult); return calculateRamUpgradeCost(startingRam, extraLevels, costMult);
}, },
coreUpgradeCost: function (startingCore: any, extraCores: any = 1, costMult: any = 1): any { coreUpgradeCost: function (_startingCore: unknown, _extraCores: unknown = 1, _costMult: unknown = 1): number {
const startingCore = helper.number("coreUpgradeCost", "startingCore", _startingCore);
const extraCores = helper.number("coreUpgradeCost", "extraCores", _extraCores);
const costMult = helper.number("coreUpgradeCost", "costMult", _costMult);
checkFormulasAccess("hacknetNodes.coreUpgradeCost"); checkFormulasAccess("hacknetNodes.coreUpgradeCost");
return calculateCoreUpgradeCost(startingCore, extraCores, costMult); return calculateCoreUpgradeCost(startingCore, extraCores, costMult);
}, },
hacknetNodeCost: function (n: any, mult: any): any { hacknetNodeCost: function (_n: unknown, _mult: unknown): number {
const n = helper.number("hacknetNodeCost", "n", _n);
const mult = helper.number("hacknetNodeCost", "mult", _mult);
checkFormulasAccess("hacknetNodes.hacknetNodeCost"); checkFormulasAccess("hacknetNodes.hacknetNodeCost");
return calculateNodeCost(n, mult); return calculateNodeCost(n, mult);
}, },
@ -146,27 +133,51 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
}, },
}, },
hacknetServers: { hacknetServers: {
hashGainRate: function (level: any, ramUsed: any, maxRam: any, cores: any, mult: any = 1): any { hashGainRate: function (
_level: unknown,
_ramUsed: unknown,
_maxRam: unknown,
_cores: unknown,
_mult: unknown = 1,
): number {
const level = helper.number("hashGainRate", "level", _level);
const ramUsed = helper.number("hashGainRate", "ramUsed", _ramUsed);
const maxRam = helper.number("hashGainRate", "maxRam", _maxRam);
const cores = helper.number("hashGainRate", "cores", _cores);
const mult = helper.number("hashGainRate", "mult", _mult);
checkFormulasAccess("hacknetServers.hashGainRate"); checkFormulasAccess("hacknetServers.hashGainRate");
return HScalculateHashGainRate(level, ramUsed, maxRam, cores, mult); return HScalculateHashGainRate(level, ramUsed, maxRam, cores, mult);
}, },
levelUpgradeCost: function (startingLevel: any, extraLevels: any = 1, costMult: any = 1): any { levelUpgradeCost: function (_startingLevel: unknown, _extraLevels: unknown = 1, _costMult: unknown = 1): number {
const startingLevel = helper.number("levelUpgradeCost", "startingLevel", _startingLevel);
const extraLevels = helper.number("levelUpgradeCost", "extraLevels", _extraLevels);
const costMult = helper.number("levelUpgradeCost", "costMult", _costMult);
checkFormulasAccess("hacknetServers.levelUpgradeCost"); checkFormulasAccess("hacknetServers.levelUpgradeCost");
return HScalculateLevelUpgradeCost(startingLevel, extraLevels, costMult); return HScalculateLevelUpgradeCost(startingLevel, extraLevels, costMult);
}, },
ramUpgradeCost: function (startingRam: any, extraLevels: any = 1, costMult: any = 1): any { ramUpgradeCost: function (_startingRam: unknown, _extraLevels: unknown = 1, _costMult: unknown = 1): number {
const startingRam = helper.number("ramUpgradeCost", "startingRam", _startingRam);
const extraLevels = helper.number("ramUpgradeCost", "extraLevels", _extraLevels);
const costMult = helper.number("ramUpgradeCost", "costMult", _costMult);
checkFormulasAccess("hacknetServers.ramUpgradeCost"); checkFormulasAccess("hacknetServers.ramUpgradeCost");
return HScalculateRamUpgradeCost(startingRam, extraLevels, costMult); return HScalculateRamUpgradeCost(startingRam, extraLevels, costMult);
}, },
coreUpgradeCost: function (startingCore: any, extraCores: any = 1, costMult: any = 1): any { coreUpgradeCost: function (_startingCore: unknown, _extraCores: unknown = 1, _costMult: unknown = 1): number {
const startingCore = helper.number("coreUpgradeCost", "startingCore", _startingCore);
const extraCores = helper.number("coreUpgradeCost", "extraCores", _extraCores);
const costMult = helper.number("coreUpgradeCost", "costMult", _costMult);
checkFormulasAccess("hacknetServers.coreUpgradeCost"); checkFormulasAccess("hacknetServers.coreUpgradeCost");
return HScalculateCoreUpgradeCost(startingCore, extraCores, costMult); return HScalculateCoreUpgradeCost(startingCore, extraCores, costMult);
}, },
cacheUpgradeCost: function (startingCache: any, extraCache: any = 1): any { cacheUpgradeCost: function (_startingCache: unknown, _extraCache: unknown = 1): number {
const startingCache = helper.number("cacheUpgradeCost", "startingCache", _startingCache);
const extraCache = helper.number("cacheUpgradeCost", "extraCache", _extraCache);
checkFormulasAccess("hacknetServers.cacheUpgradeCost"); checkFormulasAccess("hacknetServers.cacheUpgradeCost");
return HScalculateCacheUpgradeCost(startingCache, extraCache); return HScalculateCacheUpgradeCost(startingCache, extraCache);
}, },
hashUpgradeCost: function (upgName: any, level: any): any { hashUpgradeCost: function (_upgName: unknown, _level: unknown): number {
const upgName = helper.string("hashUpgradeCost", "upgName", _upgName);
const level = helper.number("hashUpgradeCost", "level", _level);
checkFormulasAccess("hacknetServers.hashUpgradeCost"); checkFormulasAccess("hacknetServers.hashUpgradeCost");
const upg = player.hashManager.getUpgrade(upgName); const upg = player.hashManager.getUpgrade(upgName);
if (!upg) { if (!upg) {
@ -177,7 +188,9 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
} }
return upg.getCost(level); return upg.getCost(level);
}, },
hacknetServerCost: function (n: any, mult: any = 1): any { hacknetServerCost: function (_n: unknown, _mult: unknown = 1): number {
const n = helper.number("hacknetServerCost", "n", _n);
const mult = helper.number("hacknetServerCost", "mult", _mult);
checkFormulasAccess("hacknetServers.hacknetServerCost"); checkFormulasAccess("hacknetServers.hacknetServerCost");
return HScalculateServerCost(n, mult); return HScalculateServerCost(n, mult);
}, },
@ -203,11 +216,13 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h
checkFormulasAccess("gang.moneyGain"); checkFormulasAccess("gang.moneyGain");
return calculateMoneyGain(gang, member, task); return calculateMoneyGain(gang, member, task);
}, },
ascensionPointsGain: function (exp: any): number { ascensionPointsGain: function (_exp: unknown): number {
const exp = helper.number("ascensionPointsGain", "exp", _exp);
checkFormulasAccess("gang.ascensionPointsGain"); checkFormulasAccess("gang.ascensionPointsGain");
return calculateAscensionPointsGain(exp); return calculateAscensionPointsGain(exp);
}, },
ascensionMultiplier: function (points: any): number { ascensionMultiplier: function (_points: unknown): number {
const points = helper.number("ascensionMultiplier", "points", _points);
checkFormulasAccess("gang.ascensionMultiplier"); checkFormulasAccess("gang.ascensionMultiplier");
return calculateAscensionMult(points); return calculateAscensionMult(points);
}, },

@ -24,10 +24,7 @@ import { Hacknet as IHacknet, NodeStats } from "../ScriptEditor/NetscriptDefinit
export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IHacknet { export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IHacknet {
// Utility function to get Hacknet Node object // Utility function to get Hacknet Node object
const getHacknetNode = function (i: any, callingFn = ""): HacknetNode | HacknetServer { const getHacknetNode = function (i: number, callingFn = ""): HacknetNode | HacknetServer {
if (isNaN(i)) {
throw helper.makeRuntimeErrorMsg(callingFn, "Invalid index specified for Hacknet Node: " + i);
}
if (i < 0 || i >= player.hacknetNodes.length) { if (i < 0 || i >= player.hacknetNodes.length) {
throw helper.makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i); throw helper.makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i);
} }
@ -72,7 +69,8 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
return getCostOfNextHacknetNode(player); return getCostOfNextHacknetNode(player);
} }
}, },
getNodeStats: function (i: any): NodeStats { getNodeStats: function (_i: unknown): NodeStats {
const i = helper.number("getNodeStats", "i", _i);
const node = getHacknetNode(i, "getNodeStats"); const node = getHacknetNode(i, "getNodeStats");
const hasUpgraded = hasHacknetServers(player); const hasUpgraded = hasHacknetServers(player);
const res: any = { const res: any = {
@ -93,19 +91,27 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
return res; return res;
}, },
upgradeLevel: function (i: any, n: any): boolean { upgradeLevel: function (_i: unknown, _n: unknown): boolean {
const i = helper.number("upgradeLevel", "i", _i);
const n = helper.number("upgradeLevel", "n", _n);
const node = getHacknetNode(i, "upgradeLevel"); const node = getHacknetNode(i, "upgradeLevel");
return purchaseLevelUpgrade(player, node, n); return purchaseLevelUpgrade(player, node, n);
}, },
upgradeRam: function (i: any, n: any): boolean { upgradeRam: function (_i: unknown, _n: unknown): boolean {
const i = helper.number("upgradeRam", "i", _i);
const n = helper.number("upgradeRam", "n", _n);
const node = getHacknetNode(i, "upgradeRam"); const node = getHacknetNode(i, "upgradeRam");
return purchaseRamUpgrade(player, node, n); return purchaseRamUpgrade(player, node, n);
}, },
upgradeCore: function (i: any, n: any): boolean { upgradeCore: function (_i: unknown, _n: unknown): boolean {
const i = helper.number("upgradeCore", "i", _i);
const n = helper.number("upgradeCore", "n", _n);
const node = getHacknetNode(i, "upgradeCore"); const node = getHacknetNode(i, "upgradeCore");
return purchaseCoreUpgrade(player, node, n); return purchaseCoreUpgrade(player, node, n);
}, },
upgradeCache: function (i: any, n: any): boolean { upgradeCache: function (_i: unknown, _n: unknown): boolean {
const i = helper.number("upgradeCache", "i", _i);
const n = helper.number("upgradeCache", "n", _n);
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return false; return false;
} }
@ -120,19 +126,27 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
} }
return res; return res;
}, },
getLevelUpgradeCost: function (i: any, n: any): number { getLevelUpgradeCost: function (_i: unknown, _n: unknown): number {
const i = helper.number("getLevelUpgradeCost", "i", _i);
const n = helper.number("getLevelUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeLevel"); const node = getHacknetNode(i, "upgradeLevel");
return node.calculateLevelUpgradeCost(n, player.hacknet_node_level_cost_mult); return node.calculateLevelUpgradeCost(n, player.hacknet_node_level_cost_mult);
}, },
getRamUpgradeCost: function (i: any, n: any): number { getRamUpgradeCost: function (_i: unknown, _n: unknown): number {
const i = helper.number("getRamUpgradeCost", "i", _i);
const n = helper.number("getRamUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeRam"); const node = getHacknetNode(i, "upgradeRam");
return node.calculateRamUpgradeCost(n, player.hacknet_node_ram_cost_mult); return node.calculateRamUpgradeCost(n, player.hacknet_node_ram_cost_mult);
}, },
getCoreUpgradeCost: function (i: any, n: any): number { getCoreUpgradeCost: function (_i: unknown, _n: unknown): number {
const i = helper.number("getCoreUpgradeCost", "i", _i);
const n = helper.number("getCoreUpgradeCost", "n", _n);
const node = getHacknetNode(i, "upgradeCore"); const node = getHacknetNode(i, "upgradeCore");
return node.calculateCoreUpgradeCost(n, player.hacknet_node_core_cost_mult); return node.calculateCoreUpgradeCost(n, player.hacknet_node_core_cost_mult);
}, },
getCacheUpgradeCost: function (i: any, n: any): number { getCacheUpgradeCost: function (_i: unknown, _n: unknown): number {
const i = helper.number("getCacheUpgradeCost", "i", _i);
const n = helper.number("getCacheUpgradeCost", "n", _n);
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return Infinity; return Infinity;
} }
@ -155,26 +169,30 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
} }
return player.hashManager.capacity; return player.hashManager.capacity;
}, },
hashCost: function (upgName: any): number { hashCost: function (_upgName: unknown): number {
const upgName = helper.string("hashCost", "upgName", _upgName);
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return Infinity; return Infinity;
} }
return player.hashManager.getUpgradeCost(upgName); return player.hashManager.getUpgradeCost(upgName);
}, },
spendHashes: function (upgName: any, upgTarget: any): boolean { spendHashes: function (_upgName: unknown, _upgTarget: unknown): boolean {
const upgName = helper.string("spendHashes", "upgName", _upgName);
const upgTarget = helper.string("spendHashes", "upgTarget", _upgTarget);
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return false; return false;
} }
return purchaseHashUpgrade(player, upgName, upgTarget); return purchaseHashUpgrade(player, upgName, upgTarget);
}, },
getHashUpgrades: function(): string[] { getHashUpgrades: function (): string[] {
if (!hasHacknetServers(player)) { if (!hasHacknetServers(player)) {
return []; return [];
} }
return Object.values(HashUpgrades).map((upgrade: HashUpgrade) => upgrade.name); return Object.values(HashUpgrades).map((upgrade: HashUpgrade) => upgrade.name);
}, },
getHashUpgradeLevel: function (upgName: any): number { getHashUpgradeLevel: function (_upgName: unknown): number {
const upgName = helper.string("getHashUpgradeLevel", "upgName", _upgName);
const level = player.hashManager.upgrades[upgName]; const level = player.hashManager.upgrades[upgName];
if (level === undefined) { if (level === undefined) {
throw helper.makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`); throw helper.makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`);

@ -3,9 +3,9 @@ import { BaseServer } from "../Server/BaseServer";
export interface INetscriptHelper { export interface INetscriptHelper {
updateDynamicRam(functionName: string, ram: number): void; updateDynamicRam(functionName: string, ram: number): void;
makeRuntimeErrorMsg(functionName: string, message: string): void; makeRuntimeErrorMsg(functionName: string, message: string): void;
string(funcName: string, argName: string, v: any): string; string(funcName: string, argName: string, v: unknown): string;
number(funcName: string, argName: string, v: any): number; number(funcName: string, argName: string, v: unknown): number;
boolean(v: any): boolean; boolean(v: unknown): boolean;
getServer(ip: any, fn: any): BaseServer; getServer(ip: any, fn: any): BaseServer;
checkSingularityAccess(func: string): void; checkSingularityAccess(func: string): void;
hack(hostname: string, manual: boolean): Promise<number>; hack(hostname: string, manual: boolean): Promise<number>;

@ -32,9 +32,9 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
checkStanekAPIAccess("height"); checkStanekAPIAccess("height");
return staneksGift.height(); return staneksGift.height();
}, },
charge: function (arootX: unknown, arootY: unknown): Promise<void> { charge: function (_rootX: unknown, _rootY: unknown): Promise<void> {
const rootX = helper.number("stanek.charge", "rootX", arootX); const rootX = helper.number("stanek.charge", "rootX", _rootX);
const rootY = helper.number("stanek.charge", "rootY", arootY); const rootY = helper.number("stanek.charge", "rootY", _rootY);
helper.updateDynamicRam("charge", getRamCost(player, "stanek", "charge")); helper.updateDynamicRam("charge", getRamCost(player, "stanek", "charge"));
checkStanekAPIAccess("charge"); checkStanekAPIAccess("charge");
@ -67,11 +67,11 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`); workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`);
staneksGift.clear(); staneksGift.clear();
}, },
canPlace: function (arootX: unknown, arootY: unknown, arotation: unknown, afragmentId: unknown): boolean { canPlace: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.canPlace", "rootX", arootX); const rootX = helper.number("stanek.canPlace", "rootX", _rootX);
const rootY = helper.number("stanek.canPlace", "rootY", arootY); const rootY = helper.number("stanek.canPlace", "rootY", _rootY);
const rotation = helper.number("stanek.canPlace", "rotation", arotation); const rotation = helper.number("stanek.canPlace", "rotation", _rotation);
const fragmentId = helper.number("stanek.canPlace", "fragmentId", afragmentId); const fragmentId = helper.number("stanek.canPlace", "fragmentId", _fragmentId);
helper.updateDynamicRam("canPlace", getRamCost(player, "stanek", "canPlace")); helper.updateDynamicRam("canPlace", getRamCost(player, "stanek", "canPlace"));
checkStanekAPIAccess("canPlace"); checkStanekAPIAccess("canPlace");
const fragment = FragmentById(fragmentId); const fragment = FragmentById(fragmentId);
@ -79,29 +79,29 @@ export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, hel
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment); const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
return can; return can;
}, },
place: function (arootX: unknown, arootY: unknown, arotation: unknown, afragmentId: unknown): boolean { place: function (_rootX: unknown, _rootY: unknown, _rotation: unknown, _fragmentId: unknown): boolean {
const rootX = helper.number("stanek.place", "rootX", arootX); const rootX = helper.number("stanek.place", "rootX", _rootX);
const rootY = helper.number("stanek.place", "rootY", arootY); const rootY = helper.number("stanek.place", "rootY", _rootY);
const rotation = helper.number("stanek.place", "rotation", arotation); const rotation = helper.number("stanek.place", "rotation", _rotation);
const fragmentId = helper.number("stanek.place", "fragmentId", afragmentId); const fragmentId = helper.number("stanek.place", "fragmentId", _fragmentId);
helper.updateDynamicRam("place", getRamCost(player, "stanek", "place")); helper.updateDynamicRam("place", getRamCost(player, "stanek", "place"));
checkStanekAPIAccess("place"); checkStanekAPIAccess("place");
const fragment = FragmentById(fragmentId); const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`); if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
return staneksGift.place(rootX, rootY, rotation, fragment); return staneksGift.place(rootX, rootY, rotation, fragment);
}, },
get: function (arootX: unknown, arootY: unknown): IActiveFragment | undefined { get: function (_rootX: unknown, _rootY: unknown): IActiveFragment | undefined {
const rootX = helper.number("stanek.get", "rootX", arootX); const rootX = helper.number("stanek.get", "rootX", _rootX);
const rootY = helper.number("stanek.get", "rootY", arootY); const rootY = helper.number("stanek.get", "rootY", _rootY);
helper.updateDynamicRam("get", getRamCost(player, "stanek", "get")); helper.updateDynamicRam("get", getRamCost(player, "stanek", "get"));
checkStanekAPIAccess("get"); checkStanekAPIAccess("get");
const fragment = staneksGift.findFragment(rootX, rootY); const fragment = staneksGift.findFragment(rootX, rootY);
if (fragment !== undefined) return fragment.copy(); if (fragment !== undefined) return fragment.copy();
return undefined; return undefined;
}, },
remove: function (arootX: unknown, arootY: unknown): boolean { remove: function (_rootX: unknown, _rootY: unknown): boolean {
const rootX = helper.number("stanek.remove", "rootX", arootX); const rootX = helper.number("stanek.remove", "rootX", _rootX);
const rootY = helper.number("stanek.remove", "rootY", arootY); const rootY = helper.number("stanek.remove", "rootY", _rootY);
helper.updateDynamicRam("remove", getRamCost(player, "stanek", "remove")); helper.updateDynamicRam("remove", getRamCost(player, "stanek", "remove"));
checkStanekAPIAccess("remove"); checkStanekAPIAccess("remove");
return staneksGift.delete(rootX, rootY); return staneksGift.delete(rootX, rootY);

@ -34,33 +34,37 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return stock; return stock;
}; };
return { return {
getSymbols: function (): any { getSymbols: function (): string[] {
helper.updateDynamicRam("getSymbols", getRamCost(player, "stock", "getSymbols")); helper.updateDynamicRam("getSymbols", getRamCost(player, "stock", "getSymbols"));
checkTixApiAccess("getSymbols"); checkTixApiAccess("getSymbols");
return Object.values(StockSymbols); return Object.values(StockSymbols);
}, },
getPrice: function (symbol: any): any { getPrice: function (_symbol: unknown): number {
const symbol = helper.string("getPrice", "symbol", _symbol);
helper.updateDynamicRam("getPrice", getRamCost(player, "stock", "getPrice")); helper.updateDynamicRam("getPrice", getRamCost(player, "stock", "getPrice"));
checkTixApiAccess("getPrice"); checkTixApiAccess("getPrice");
const stock = getStockFromSymbol(symbol, "getPrice"); const stock = getStockFromSymbol(symbol, "getPrice");
return stock.price; return stock.price;
}, },
getAskPrice: function (symbol: any): any { getAskPrice: function (_symbol: unknown): number {
const symbol = helper.string("getAskPrice", "symbol", _symbol);
helper.updateDynamicRam("getAskPrice", getRamCost(player, "stock", "getAskPrice")); helper.updateDynamicRam("getAskPrice", getRamCost(player, "stock", "getAskPrice"));
checkTixApiAccess("getAskPrice"); checkTixApiAccess("getAskPrice");
const stock = getStockFromSymbol(symbol, "getAskPrice"); const stock = getStockFromSymbol(symbol, "getAskPrice");
return stock.getAskPrice(); return stock.getAskPrice();
}, },
getBidPrice: function (symbol: any): any { getBidPrice: function (_symbol: unknown): number {
const symbol = helper.string("getBidPrice", "symbol", _symbol);
helper.updateDynamicRam("getBidPrice", getRamCost(player, "stock", "getBidPrice")); helper.updateDynamicRam("getBidPrice", getRamCost(player, "stock", "getBidPrice"));
checkTixApiAccess("getBidPrice"); checkTixApiAccess("getBidPrice");
const stock = getStockFromSymbol(symbol, "getBidPrice"); const stock = getStockFromSymbol(symbol, "getBidPrice");
return stock.getBidPrice(); return stock.getBidPrice();
}, },
getPosition: function (symbol: any): any { getPosition: function (_symbol: unknown): [number, number, number, number] {
const symbol = helper.string("getPosition", "symbol", _symbol);
helper.updateDynamicRam("getPosition", getRamCost(player, "stock", "getPosition")); helper.updateDynamicRam("getPosition", getRamCost(player, "stock", "getPosition"));
checkTixApiAccess("getPosition"); checkTixApiAccess("getPosition");
const stock = SymbolToStockMap[symbol]; const stock = SymbolToStockMap[symbol];
@ -69,14 +73,18 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
} }
return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx]; return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx];
}, },
getMaxShares: function (symbol: any): any { getMaxShares: function (_symbol: unknown): number {
const symbol = helper.string("getMaxShares", "symbol", _symbol);
helper.updateDynamicRam("getMaxShares", getRamCost(player, "stock", "getMaxShares")); helper.updateDynamicRam("getMaxShares", getRamCost(player, "stock", "getMaxShares"));
checkTixApiAccess("getMaxShares"); checkTixApiAccess("getMaxShares");
const stock = getStockFromSymbol(symbol, "getMaxShares"); const stock = getStockFromSymbol(symbol, "getMaxShares");
return stock.maxShares; return stock.maxShares;
}, },
getPurchaseCost: function (symbol: any, shares: any, posType: any): any { getPurchaseCost: function (_symbol: unknown, _shares: unknown, _posType: unknown): number {
const symbol = helper.string("getPurchaseCost", "symbol", _symbol);
let shares = helper.number("getPurchaseCost", "shares", _shares);
const posType = helper.string("getPurchaseCost", "posType", _posType);
helper.updateDynamicRam("getPurchaseCost", getRamCost(player, "stock", "getPurchaseCost")); helper.updateDynamicRam("getPurchaseCost", getRamCost(player, "stock", "getPurchaseCost"));
checkTixApiAccess("getPurchaseCost"); checkTixApiAccess("getPurchaseCost");
const stock = getStockFromSymbol(symbol, "getPurchaseCost"); const stock = getStockFromSymbol(symbol, "getPurchaseCost");
@ -99,7 +107,10 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res; return res;
}, },
getSaleGain: function (symbol: any, shares: any, posType: any): any { getSaleGain: function (_symbol: unknown, _shares: unknown, _posType: unknown): number {
const symbol = helper.string("getSaleGain", "symbol", _symbol);
let shares = helper.number("getSaleGain", "shares", _shares);
const posType = helper.string("getSaleGain", "posType", _posType);
helper.updateDynamicRam("getSaleGain", getRamCost(player, "stock", "getSaleGain")); helper.updateDynamicRam("getSaleGain", getRamCost(player, "stock", "getSaleGain"));
checkTixApiAccess("getSaleGain"); checkTixApiAccess("getSaleGain");
const stock = getStockFromSymbol(symbol, "getSaleGain"); const stock = getStockFromSymbol(symbol, "getSaleGain");
@ -122,14 +133,18 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res; return res;
}, },
buy: function (symbol: any, shares: any): any { buy: function (_symbol: unknown, _shares: unknown): number {
const symbol = helper.string("buy", "symbol", _symbol);
const shares = helper.number("buy", "shares", _shares);
helper.updateDynamicRam("buy", getRamCost(player, "stock", "buy")); helper.updateDynamicRam("buy", getRamCost(player, "stock", "buy"));
checkTixApiAccess("buy"); checkTixApiAccess("buy");
const stock = getStockFromSymbol(symbol, "buy"); const stock = getStockFromSymbol(symbol, "buy");
const res = buyStock(stock, shares, workerScript, {}); const res = buyStock(stock, shares, workerScript, {});
return res ? stock.getAskPrice() : 0; return res ? stock.getAskPrice() : 0;
}, },
sell: function (symbol: any, shares: any): any { sell: function (_symbol: unknown, _shares: unknown): number {
const symbol = helper.string("sell", "symbol", _symbol);
const shares = helper.number("sell", "shares", _shares);
helper.updateDynamicRam("sell", getRamCost(player, "stock", "sell")); helper.updateDynamicRam("sell", getRamCost(player, "stock", "sell"));
checkTixApiAccess("sell"); checkTixApiAccess("sell");
const stock = getStockFromSymbol(symbol, "sell"); const stock = getStockFromSymbol(symbol, "sell");
@ -137,7 +152,9 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res ? stock.getBidPrice() : 0; return res ? stock.getBidPrice() : 0;
}, },
short: function (symbol: any, shares: any): any { short: function (_symbol: unknown, _shares: unknown): number {
const symbol = helper.string("short", "symbol", _symbol);
const shares = helper.number("short", "shares", _shares);
helper.updateDynamicRam("short", getRamCost(player, "stock", "short")); helper.updateDynamicRam("short", getRamCost(player, "stock", "short"));
checkTixApiAccess("short"); checkTixApiAccess("short");
if (player.bitNodeN !== 8) { if (player.bitNodeN !== 8) {
@ -153,7 +170,9 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res ? stock.getBidPrice() : 0; return res ? stock.getBidPrice() : 0;
}, },
sellShort: function (symbol: any, shares: any): any { sellShort: function (_symbol: unknown, _shares: unknown): number {
const symbol = helper.string("sellShort", "symbol", _symbol);
const shares = helper.number("sellShort", "shares", _shares);
helper.updateDynamicRam("sellShort", getRamCost(player, "stock", "sellShort")); helper.updateDynamicRam("sellShort", getRamCost(player, "stock", "sellShort"));
checkTixApiAccess("sellShort"); checkTixApiAccess("sellShort");
if (player.bitNodeN !== 8) { if (player.bitNodeN !== 8) {
@ -169,7 +188,12 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res ? stock.getAskPrice() : 0; return res ? stock.getAskPrice() : 0;
}, },
placeOrder: function (symbol: any, shares: any, price: any, type: any, pos: any): any { placeOrder: function (_symbol: unknown, _shares: unknown, _price: unknown, _type: unknown, _pos: unknown): boolean {
const symbol = helper.string("placeOrder", "symbol", _symbol);
const shares = helper.number("placeOrder", "shares", _shares);
const price = helper.number("placeOrder", "price", _price);
const type = helper.string("placeOrder", "type", _type);
const pos = helper.string("placeOrder", "pos", _pos);
helper.updateDynamicRam("placeOrder", getRamCost(player, "stock", "placeOrder")); helper.updateDynamicRam("placeOrder", getRamCost(player, "stock", "placeOrder"));
checkTixApiAccess("placeOrder"); checkTixApiAccess("placeOrder");
if (player.bitNodeN !== 8) { if (player.bitNodeN !== 8) {
@ -208,7 +232,18 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return placeOrder(stock, shares, price, orderType, orderPos, workerScript); return placeOrder(stock, shares, price, orderType, orderPos, workerScript);
}, },
cancelOrder: function (symbol: any, shares: any, price: any, type: any, pos: any): any { cancelOrder: function (
_symbol: unknown,
_shares: unknown,
_price: unknown,
_type: unknown,
_pos: unknown,
): boolean {
const symbol = helper.string("cancelOrder", "symbol", _symbol);
const shares = helper.number("cancelOrder", "shares", _shares);
const price = helper.number("cancelOrder", "price", _price);
const type = helper.string("cancelOrder", "type", _type);
const pos = helper.string("cancelOrder", "pos", _pos);
helper.updateDynamicRam("cancelOrder", getRamCost(player, "stock", "cancelOrder")); helper.updateDynamicRam("cancelOrder", getRamCost(player, "stock", "cancelOrder"));
checkTixApiAccess("cancelOrder"); checkTixApiAccess("cancelOrder");
if (player.bitNodeN !== 8) { if (player.bitNodeN !== 8) {
@ -290,7 +325,8 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return orders; return orders;
}, },
getVolatility: function (symbol: any): any { getVolatility: function (_symbol: unknown): number {
const symbol = helper.string("getVolatility", "symbol", _symbol);
helper.updateDynamicRam("getVolatility", getRamCost(player, "stock", "getVolatility")); helper.updateDynamicRam("getVolatility", getRamCost(player, "stock", "getVolatility"));
if (!player.has4SDataTixApi) { if (!player.has4SDataTixApi) {
throw helper.makeRuntimeErrorMsg("getVolatility", "You don't have 4S Market Data TIX API Access!"); throw helper.makeRuntimeErrorMsg("getVolatility", "You don't have 4S Market Data TIX API Access!");
@ -299,7 +335,8 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return stock.mv / 100; // Convert from percentage to decimal return stock.mv / 100; // Convert from percentage to decimal
}, },
getForecast: function (symbol: any): any { getForecast: function (_symbol: unknown): number {
const symbol = helper.string("getForecast", "symbol", _symbol);
helper.updateDynamicRam("getForecast", getRamCost(player, "stock", "getForecast")); helper.updateDynamicRam("getForecast", getRamCost(player, "stock", "getForecast"));
if (!player.has4SDataTixApi) { if (!player.has4SDataTixApi) {
throw helper.makeRuntimeErrorMsg("getForecast", "You don't have 4S Market Data TIX API Access!"); throw helper.makeRuntimeErrorMsg("getForecast", "You don't have 4S Market Data TIX API Access!");
@ -310,7 +347,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag); stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag);
return forecast / 100; // Convert from percentage to decimal return forecast / 100; // Convert from percentage to decimal
}, },
purchase4SMarketData: function () { purchase4SMarketData: function (): boolean {
helper.updateDynamicRam("purchase4SMarketData", getRamCost(player, "stock", "purchase4SMarketData")); helper.updateDynamicRam("purchase4SMarketData", getRamCost(player, "stock", "purchase4SMarketData"));
checkTixApiAccess("purchase4SMarketData"); checkTixApiAccess("purchase4SMarketData");
@ -329,7 +366,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
workerScript.log("stock.purchase4SMarketData", () => "Purchased 4S Market Data"); workerScript.log("stock.purchase4SMarketData", () => "Purchased 4S Market Data");
return true; return true;
}, },
purchase4SMarketDataTixApi: function () { purchase4SMarketDataTixApi: function (): boolean {
helper.updateDynamicRam("purchase4SMarketDataTixApi", getRamCost(player, "stock", "purchase4SMarketDataTixApi")); helper.updateDynamicRam("purchase4SMarketDataTixApi", getRamCost(player, "stock", "purchase4SMarketDataTixApi"));
checkTixApiAccess("purchase4SMarketDataTixApi"); checkTixApiAccess("purchase4SMarketDataTixApi");

@ -375,7 +375,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): any
}); });
//Now we have to generate the code that would create the namespace //Now we have to generate the code that would create the namespace
generatedCode += "var " + namespace + ";\n" + "(function (namespace) {\n"; generatedCode += `var ${namespace};\n(function (namespace) {\n`;
//Add the function declarations //Add the function declarations
fnDeclarations.forEach((fn: any) => { fnDeclarations.forEach((fn: any) => {
@ -390,7 +390,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): any
}); });
//Finish //Finish
generatedCode += "})(" + namespace + " || " + "(" + namespace + " = {}));\n"; generatedCode += `})(${namespace} || (" + namespace + " = {}));\n`;
} else { } else {
//import {...} from script //import {...} from script

@ -363,11 +363,11 @@ export class PlayerObject implements IPlayer {
this.companyName = ""; // Name of Company. Must match a key value in Companies ma; this.companyName = ""; // Name of Company. Must match a key value in Companies ma;
// Servers // Servers
this.currentServer = ""; //IP address of Server currently being accessed through termina; this.currentServer = ""; //hostname of Server currently being accessed through termina;
this.purchasedServers = []; //IP Addresses of purchased server; this.purchasedServers = []; //hostnames of purchased server;
// Hacknet Nodes/Servers // Hacknet Nodes/Servers
this.hacknetNodes = []; // Note= For Hacknet Servers, this array holds the IP addresses of the server; this.hacknetNodes = []; // Note= For Hacknet Servers, this array holds the hostnames of the server;
this.hashManager = new HashManager(); this.hashManager = new HashManager();
//Factions //Factions
@ -483,11 +483,11 @@ export class PlayerObject implements IPlayer {
// Let's get a hash of some semi-random stuff so we have something unique. // Let's get a hash of some semi-random stuff so we have something unique.
this.identifier = cyrb53( this.identifier = cyrb53(
"I-" + "I-" +
new Date().getTime() + new Date().getTime() +
navigator.userAgent + navigator.userAgent +
window.innerWidth + window.innerWidth +
window.innerHeight + window.innerHeight +
getRandomInt(100, 999), getRandomInt(100, 999),
); );
this.init = generalMethods.init; this.init = generalMethods.init;

@ -608,11 +608,9 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
if (this.workPartTime(numCycles)) { if (this.workPartTime(numCycles)) {
router.toCity(); router.toCity();
} }
} else { } else if (this.work(numCycles)) {
if (this.work(numCycles)) {
router.toCity(); router.toCity();
} }
}
} }
} }
@ -1315,9 +1313,7 @@ export function createProgramWork(this: IPlayer, numCycles: number): boolean {
export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): string { export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): string {
const programName = this.createProgramName; const programName = this.createProgramName;
if (cancelled === false) { if (cancelled === false) {
dialogBoxCreate( dialogBoxCreate(`You've finished creating ${programName}!<br>The new program can be found on your home computer.`);
"You've finished creating " + programName + "!<br>" + "The new program can be found on your home computer.",
);
this.getHomeComputer().programs.push(programName); this.getHomeComputer().programs.push(programName);
} else { } else {
@ -2244,8 +2240,7 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
if (!(fulcrumSecretServer instanceof Server)) throw new Error("Fulcrum Secret Technologies should be normal server"); if (!(fulcrumSecretServer instanceof Server)) throw new Error("Fulcrum Secret Technologies should be normal server");
if (fulcrumSecretServer == null) { if (fulcrumSecretServer == null) {
console.error("Could not find Fulcrum Secret Technologies Server"); console.error("Could not find Fulcrum Secret Technologies Server");
} else { } else if (
if (
!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isBanned &&
!fulcrumsecrettechonologiesFac.isMember && !fulcrumsecrettechonologiesFac.isMember &&
!fulcrumsecrettechonologiesFac.alreadyInvited && !fulcrumsecrettechonologiesFac.alreadyInvited &&
@ -2254,7 +2249,6 @@ export function checkForFactionInvitations(this: IPlayer): Faction[] {
) { ) {
invitedFactions.push(fulcrumsecrettechonologiesFac); invitedFactions.push(fulcrumsecrettechonologiesFac);
} }
}
//BitRunners //BitRunners
const bitrunnersFac = Factions["BitRunners"]; const bitrunnersFac = Factions["BitRunners"];

@ -34,7 +34,32 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
return false; return false;
} }
return true; const validMults = [
"hacking_mult",
"strength_mult",
"defense_mult",
"dexterity_mult",
"agility_mult",
"charisma_mult",
"hacking_exp_mult",
"strength_exp_mult",
"defense_exp_mult",
"dexterity_exp_mult",
"agility_exp_mult",
"charisma_exp_mult",
"company_rep_mult",
"faction_rep_mult",
"crime_money_mult",
"crime_success_mult",
"work_money_mult",
];
for (const mult of Object.keys(aug.mults)) {
if (validMults.includes(mult)) {
return true;
}
}
return false;
} }
// If player is in a gang, then we return all augs that the player // If player is in a gang, then we return all augs that the player

@ -1,9 +1,19 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { use } from "../../ui/Context"; import { find } from "lodash";
import { getAvailableCreatePrograms } from "../ProgramHelpers";
import { Box, Tooltip, Typography } from "@mui/material"; import {
import Button from "@mui/material/Button"; Box,
Typography,
Button,
Container,
Paper
} from "@mui/material";
import { Check, Lock, Create } from "@mui/icons-material";
import { use } from "../../ui/Context";
import { Settings } from "../../Settings/Settings";
import { Programs } from "../Programs";
export const ProgramsSeen: string[] = []; export const ProgramsSeen: string[] = [];
@ -15,7 +25,20 @@ export function ProgramsRoot(): React.ReactElement {
setRerender((old) => !old); setRerender((old) => !old);
} }
const programs = getAvailableCreatePrograms(player); const programs = [...Object.values(Programs)]
.filter(prog => {
const create = prog.create;
if (create === null) return false;
if (prog.name === "b1t_flum3.exe") {
return create.req(player);
}
return true;
})
.sort((a, b) => {
if (player.hasProgram(a.name)) return 1;
if (player.hasProgram(b.name)) return -1;
return (a.create?.level ?? 0) - (b.create?.level ?? 0);
})
useEffect(() => { useEffect(() => {
programs.forEach((p) => { programs.forEach((p) => {
@ -29,8 +52,27 @@ export function ProgramsRoot(): React.ReactElement {
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);
const getHackingLevelRemaining = (lvl: number): number => {
return Math.ceil(Math.max(lvl - (player.hacking + player.intelligence / 2), 0));
}
const getProgCompletion = (name: string): number => {
const programFile = find(player.getHomeComputer().programs, p => {
return (p.startsWith(name) && p.endsWith("%-INC"));
});
if (!programFile) return -1;
const res = programFile.split("-");
if (res.length != 3) return -1;
const percComplete = Number(res[1].slice(0, -1));
if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) {
return -1;
}
return percComplete;
}
return ( return (
<> <Container disableGutters maxWidth="lg" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Create program</Typography> <Typography variant="h4">Create program</Typography>
<Typography> <Typography>
This page displays any programs that you are able to create. Writing the code for a program takes time, which This page displays any programs that you are able to create. Writing the code for a program takes time, which
@ -38,30 +80,45 @@ export function ProgramsRoot(): React.ReactElement {
time. Your progress will be saved and you can continue later. time. Your progress will be saved and you can continue later.
</Typography> </Typography>
<Box sx={{ display: 'grid', width: 'fit-content' }}> <Box sx={{ display: 'grid', gridTemplateColumns: "repeat(3, 1fr)", my: 1 }}>
{programs.map((program) => { {programs.map((program) => {
const create = program.create; const create = program.create;
if (create === null) return <></>; if (create === null) return <></>;
const curCompletion = getProgCompletion(program.name);
return ( return (
<React.Fragment key={program.name}> <Box component={Paper} sx={{ p: 1, opacity: player.hasProgram(program.name) ? 0.75 : 1 }} key={program.name}>
<Tooltip title={create.tooltip}> <Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
<Button {player.hasProgram(program.name) && <Check sx={{ mr: 1 }} /> ||
sx={{ my: 1 }} (create.req(player) && <Create sx={{ mr: 1 }} /> || <Lock sx={{ mr: 1 }} />)}
onClick={(event) => { {program.name}
if (!event.isTrusted) return; </Typography>
player.startCreateProgramWork(program.name, create.time, create.level); {(!player.hasProgram(program.name) && create.req(player)) && <Button
player.startFocusing(); sx={{ my: 1, width: '100%' }}
router.toWork(); onClick={(event) => {
}} if (!event.isTrusted) return;
> player.startCreateProgramWork(program.name, create.time, create.level);
{program.name} player.startFocusing();
</Button> router.toWork();
</Tooltip> }}
</React.Fragment> >
Create program
</Button>}
{(player.hasProgram(program.name) || getHackingLevelRemaining(create.level) === 0) ||
<Typography color={Settings.theme.hack}>
<b>Unlocks in:</b> {getHackingLevelRemaining(create.level)} hacking levels
</Typography>}
{(curCompletion !== -1) &&
<Typography color={Settings.theme.infolight}>
<b>Current completion:</b> {curCompletion}%
</Typography>}
<Typography>
{create.tooltip}
</Typography>
</Box>
); );
})} })}
</Box> </Box>
</> </Container>
); );
} }

@ -39,7 +39,7 @@ function giveSourceFile(bitNodeNumber: number): void {
if (alreadyOwned && ownedSourceFile) { if (alreadyOwned && ownedSourceFile) {
if (ownedSourceFile.lvl >= 3 && ownedSourceFile.n !== 12) { if (ownedSourceFile.lvl >= 3 && ownedSourceFile.n !== 12) {
dialogBoxCreate( dialogBoxCreate(
"The Source-File for the BitNode you just destroyed, " + sourceFile.name + ", " + "is already at max level!", `The Source-File for the BitNode you just destroyed, ${sourceFile.name}, is already at max level!`,
); );
} else { } else {
++ownedSourceFile.lvl; ++ownedSourceFile.lvl;
@ -75,12 +75,10 @@ function giveSourceFile(bitNodeNumber: number): void {
export function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number): void { export function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number): void {
if (!flume) { if (!flume) {
giveSourceFile(destroyedBitNode); giveSourceFile(destroyedBitNode);
} else { } else if (SourceFileFlags[5] === 0 && newBitNode !== 5) {
if (SourceFileFlags[5] === 0 && newBitNode !== 5) {
Player.intelligence = 0; Player.intelligence = 0;
Player.intelligence_exp = 0; Player.intelligence_exp = 0;
} }
}
if (newBitNode === 5 && Player.intelligence === 0) { if (newBitNode === 5 && Player.intelligence === 0) {
Player.intelligence = 1; Player.intelligence = 1;
} }

@ -94,6 +94,7 @@ interface Player {
factions: string[]; factions: string[];
tor: boolean; tor: boolean;
hasCorporation: boolean; hasCorporation: boolean;
inBladeburner: boolean;
} }
/** /**
@ -2784,7 +2785,7 @@ export interface Bladeburner {
* *
* Note that this is meant to be used for Contracts and Operations. * Note that this is meant to be used for Contracts and Operations.
* This function will return Infinity for actions such as Training and Field Analysis. * This function will return Infinity for actions such as Training and Field Analysis.
* This function will return 1 for BlackOps not yet completed regardless of wether * This function will return 1 for BlackOps not yet completed regardless of whether
* the player has the required rank to attempt the mission or not. * the player has the required rank to attempt the mission or not.
* *
* @param type - Type of action. * @param type - Type of action.
@ -2824,7 +2825,7 @@ export interface Bladeburner {
getActionCurrentLevel(type: string, name: string): number; getActionCurrentLevel(type: string, name: string): number;
/** /**
* Get wether an action is set to autolevel. * Get whether an action is set to autolevel.
* @remarks * @remarks
* RAM cost: 4 GB * RAM cost: 4 GB
* *
@ -5983,19 +5984,25 @@ export interface NS extends Singularity {
tFormat(milliseconds: number, milliPrecision?: boolean): string; tFormat(milliseconds: number, milliPrecision?: boolean): string;
/** /**
* Prompt the player with a Yes/No modal. * Prompt the player with an input modal.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0 GB
* *
* Prompts the player with a dialog box with two options: Yes and No. * Prompts the player with a dialog box. If `options.type` is undefined or "boolean",
* This function will return true if the player click Yes and false if * the player is shown "Yes" and "No" prompts, which return true and false respectively.
* the player clicks No. The scripts execution is halted until the player * Passing a type of "text" will give the player a text field and a value of "select"
* selects one of the options. * will show a drop-down field. Choosing type "select" will require an array or object
* to be passed via the `options.choices` property.
* The scripts execution is halted until the player selects one of the options.
* *
* @param txt - Text to appear in the prompt dialog box. * @param txt - Text to appear in the prompt dialog box.
* @returns True if the player click Yes and false if the player clicks No. * @param options - Options to modify the prompt the player is shown.
* @returns True if the player click Yes; false if the player clicks No; or the value entered by the player.
*/ */
prompt(txt: string): Promise<boolean>; prompt(
txt: string,
options?: { type?: "boolean" | "text" | "select" | undefined; choices?: string[] },
): Promise<boolean | string>;
/** /**
* Open up a message box. * Open up a message box.
@ -6385,12 +6392,12 @@ export interface WarehouseAPI {
*/ */
buyMaterial(divisionName: string, cityName: string, materialName: string, amt: number): void; buyMaterial(divisionName: string, cityName: string, materialName: string, amt: number): void;
/** /**
* Set material to bulk buy * Set material to bulk buy
* @param divisionName - Name of the division * @param divisionName - Name of the division
* @param cityName - Name of the city * @param cityName - Name of the city
* @param materialName - Name of the material * @param materialName - Name of the material
* @param amt - Amount of material to buy * @param amt - Amount of material to buy
*/ */
bulkPurchase(divisionName: string, cityName: string, materialName: string, amt: number): void; bulkPurchase(divisionName: string, cityName: string, materialName: string, amt: number): void;
/** /**
* Get warehouse data * Get warehouse data

@ -17,6 +17,7 @@ import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculatio
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes"; import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import SearchIcon from "@mui/icons-material/Search";
import { NetscriptFunctions } from "../../NetscriptFunctions"; import { NetscriptFunctions } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript";
@ -42,7 +43,7 @@ import { PromptEvent } from "../../ui/React/PromptManager";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts"; import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
import { Tooltip } from "@mui/material"; import { TextField, Tooltip } from "@mui/material";
interface IProps { interface IProps {
// Map of filename -> code // Map of filename -> code
@ -53,7 +54,7 @@ interface IProps {
vim: boolean; vim: boolean;
} }
// TODO: try to removve global symbols // TODO: try to remove global symbols
let symbolsLoaded = false; let symbolsLoaded = false;
let symbols: string[] = []; let symbols: string[] = [];
export function SetupTextEditor(): void { export function SetupTextEditor(): void {
@ -113,6 +114,8 @@ export function Root(props: IProps): React.ReactElement {
const vimStatusRef = useRef<HTMLElement>(null); const vimStatusRef = useRef<HTMLElement>(null);
const [vimEditor, setVimEditor] = useState<any>(null); const [vimEditor, setVimEditor] = useState<any>(null);
const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null); const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null);
const [filter, setFilter] = useState("");
const [searchExpanded, setSearchExpanded] = useState(false);
const [ram, setRAM] = useState("RAM: ???"); const [ram, setRAM] = useState("RAM: ???");
const [ramEntries, setRamEntries] = useState<string[][]>([["???", ""]]); const [ramEntries, setRamEntries] = useState<string[][]>([["???", ""]]);
@ -232,7 +235,7 @@ export function Root(props: IProps): React.ReactElement {
MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" }); MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" });
editor.focus(); editor.focus();
}); });
} catch {} } catch { }
} else if (!options.vim) { } else if (!options.vim) {
// Whem vim mode is disabled // Whem vim mode is disabled
vimEditor?.dispose(); vimEditor?.dispose();
@ -478,7 +481,7 @@ export function Root(props: IProps): React.ReactElement {
} }
try { try {
infLoop(newCode); infLoop(newCode);
} catch (err) {} } catch (err) { }
} }
function saveScript(scriptToSave: OpenScript): void { function saveScript(scriptToSave: OpenScript): void {
@ -523,7 +526,7 @@ export function Root(props: IProps): React.ReactElement {
const textFile = new TextFile(scriptToSave.fileName, scriptToSave.code); const textFile = new TextFile(scriptToSave.fileName, scriptToSave.code);
server.textFiles.push(textFile); server.textFiles.push(textFile);
} else { } else {
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)"); dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or a text file (.txt)");
return; return;
} }
@ -607,7 +610,7 @@ export function Root(props: IProps): React.ReactElement {
const textFile = new TextFile(currentScript.fileName, currentScript.code); const textFile = new TextFile(currentScript.fileName, currentScript.code);
server.textFiles.push(textFile); server.textFiles.push(textFile);
} else { } else {
dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + " or text file (.txt)"); dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or a text file (.txt)");
return; return;
} }
@ -688,7 +691,7 @@ export function Root(props: IProps): React.ReactElement {
if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) { if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) {
PromptEvent.emit({ PromptEvent.emit({
txt: "Do you want to save changes to " + closingScript.fileName + "?", txt: "Do you want to save changes to " + closingScript.fileName + "?",
resolve: (result: boolean) => { resolve: (result: boolean | string) => {
if (result) { if (result) {
// Save changes // Save changes
closingScript.code = savedScriptCode; closingScript.code = savedScriptCode;
@ -745,7 +748,7 @@ export function Root(props: IProps): React.ReactElement {
"Do you want to overwrite the current editor content with the contents of " + "Do you want to overwrite the current editor content with the contents of " +
openScript.fileName + openScript.fileName +
" on the server? This cannot be undone.", " on the server? This cannot be undone.",
resolve: (result: boolean) => { resolve: (result: boolean | string) => {
if (result) { if (result) {
// Save changes // Save changes
openScript.code = serverScriptCode; openScript.code = serverScriptCode;
@ -787,6 +790,16 @@ export function Root(props: IProps): React.ReactElement {
const serverScript = server.scripts.find((s) => s.filename === openScript.fileName); const serverScript = server.scripts.find((s) => s.filename === openScript.fileName);
return serverScript?.code ?? null; return serverScript?.code ?? null;
} }
function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
setFilter(event.target.value);
}
function handleExpandSearch(): void {
setFilter("")
setSearchExpanded(!searchExpanded)
}
const filteredOpenScripts = Object.values(openScripts).filter(
(script) => (script.hostname.includes(filter) || script.fileName.includes(filter))
);
// Toolbars are roughly 112px: // Toolbars are roughly 112px:
// 8px body margin top // 8px body margin top
@ -797,7 +810,7 @@ export function Root(props: IProps): React.ReactElement {
const editorHeight = dimensions.height - (130 + (options.vim ? 34 : 0)); const editorHeight = dimensions.height - (130 + (options.vim ? 34 : 0));
const tabsMaxWidth = 1640; const tabsMaxWidth = 1640;
const tabMargin = 5; const tabMargin = 5;
const tabMaxWidth = openScripts.length ? tabsMaxWidth / openScripts.length - tabMargin : 0; const tabMaxWidth = filteredOpenScripts.length ? tabsMaxWidth / filteredOpenScripts.length - tabMargin : 0;
const tabIconWidth = 25; const tabIconWidth = 25;
const tabTextWidth = tabMaxWidth - tabIconWidth * 2; const tabTextWidth = tabMaxWidth - tabIconWidth * 2;
return ( return (
@ -821,23 +834,36 @@ export function Root(props: IProps): React.ReactElement {
overflowX: "scroll", overflowX: "scroll",
}} }}
> >
{openScripts.map(({ fileName, hostname }, index) => { <Tooltip title={"Search Open Scripts"}>
{searchExpanded ?
<TextField
value={filter}
onChange={handleFilterChange}
autoFocus
InputProps={{
startAdornment: <SearchIcon />,
spellCheck: false,
endAdornment: <CloseIcon onClick={handleExpandSearch} />
}}
/> : <Button onClick={handleExpandSearch} ><SearchIcon /></Button>}
</Tooltip>
{filteredOpenScripts.map(({ fileName, hostname }, index) => {
const iconButtonStyle = { const iconButtonStyle = {
maxWidth: `${tabIconWidth}px`, maxWidth: `${tabIconWidth}px`,
minWidth: `${tabIconWidth}px`, minWidth: `${tabIconWidth}px`,
minHeight: "38.5px", minHeight: "38.5px",
maxHeight: "38.5px", maxHeight: "38.5px",
...(currentScript?.fileName === openScripts[index].fileName ...(currentScript?.fileName === filteredOpenScripts[index].fileName
? { ? {
background: Settings.theme.button, background: Settings.theme.button,
borderColor: Settings.theme.button, borderColor: Settings.theme.button,
color: Settings.theme.primary, color: Settings.theme.primary,
} }
: { : {
background: Settings.theme.backgroundsecondary, background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary, borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary, color: Settings.theme.secondary,
}), }),
}; };
const scriptTabText = `${hostname}:~/${fileName} ${dirty(index)}`; const scriptTabText = `${hostname}:~/${fileName} ${dirty(index)}`;
@ -870,18 +896,19 @@ export function Root(props: IProps): React.ReactElement {
}} }}
style={{ style={{
maxWidth: `${tabTextWidth}px`, maxWidth: `${tabTextWidth}px`,
minHeight: '38.5px',
overflow: "hidden", overflow: "hidden",
...(currentScript?.fileName === openScripts[index].fileName ...(currentScript?.fileName === filteredOpenScripts[index].fileName
? { ? {
background: Settings.theme.button, background: Settings.theme.button,
borderColor: Settings.theme.button, borderColor: Settings.theme.button,
color: Settings.theme.primary, color: Settings.theme.primary,
} }
: { : {
background: Settings.theme.backgroundsecondary, background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary, borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary, color: Settings.theme.secondary,
}), }),
}} }}
> >
<span style={{ overflow: "hidden", direction: "rtl", textOverflow: "ellipsis" }}> <span style={{ overflow: "hidden", direction: "rtl", textOverflow: "ellipsis" }}>

@ -78,12 +78,11 @@ export function ipExists(ip: string): boolean {
} }
export function createUniqueRandomIp(): string { export function createUniqueRandomIp(): string {
const ip = createRandomIp(); let ip: string;
// Repeat generating ip, until unique one is found
// If the Ip already exists, recurse to create a new one do {
if (ipExists(ip)) { ip = createRandomIp();
return createRandomIp(); } while (ipExists(ip));
}
return ip; return ip;
} }

@ -76,7 +76,7 @@ export class BaseServer {
// Script files on this Server // Script files on this Server
scripts: Script[] = []; scripts: Script[] = [];
// Contains the IP Addresses of all servers that are immediately // Contains the hostnames of all servers that are immediately
// reachable from this one // reachable from this one
serversOnNetwork: string[] = []; serversOnNetwork: string[] = [];

@ -143,11 +143,6 @@ export function SidebarRoot(props: IProps): React.ReactElement {
const augmentationCount = props.player.queuedAugmentations.length; const augmentationCount = props.player.queuedAugmentations.length;
const invitationsCount = props.player.factionInvitations.filter((f) => !InvitationsSeen.includes(f)).length; const invitationsCount = props.player.factionInvitations.filter((f) => !InvitationsSeen.includes(f)).length;
const programCount = getAvailableCreatePrograms(props.player).length - ProgramsSeen.length; const programCount = getAvailableCreatePrograms(props.player).length - ProgramsSeen.length;
const canCreateProgram =
getAvailableCreatePrograms(props.player).length > 0 ||
props.player.augmentations.length > 0 ||
props.player.queuedAugmentations.length > 0 ||
props.player.sourceFiles.length > 0;
const canOpenFactions = const canOpenFactions =
props.player.factionInvitations.length > 0 || props.player.factionInvitations.length > 0 ||
@ -439,29 +434,27 @@ export function SidebarRoot(props: IProps): React.ReactElement {
</Typography> </Typography>
</ListItemText> </ListItemText>
</ListItem> </ListItem>
{canCreateProgram && ( <ListItem
<ListItem button
button key={"Create Program"}
key={"Create Program"} className={clsx({
className={clsx({ [classes.active]: props.page === Page.CreateProgram,
[classes.active]: props.page === Page.CreateProgram, })}
})} onClick={clickCreateProgram}
onClick={clickCreateProgram} >
> <ListItemIcon>
<ListItemIcon> <Badge badgeContent={programCount > 0 ? programCount : undefined} color="error">
<Badge badgeContent={programCount > 0 ? programCount : undefined} color="error"> <Tooltip title={!open ? "Create Program" : ""}>
<Tooltip title={!open ? "Create Program" : ""}> <BugReportIcon color={props.page !== Page.CreateProgram ? "secondary" : "primary"} />
<BugReportIcon color={props.page !== Page.CreateProgram ? "secondary" : "primary"} /> </Tooltip>
</Tooltip> </Badge>
</Badge> </ListItemIcon>
</ListItemIcon> <ListItemText>
<ListItemText> <Typography color={props.page !== Page.CreateProgram ? "secondary" : "primary"}>
<Typography color={props.page !== Page.CreateProgram ? "secondary" : "primary"}> Create Program
Create Program </Typography>
</Typography> </ListItemText>
</ListItemText> </ListItem>
</ListItem>
)}
{canStaneksGift && ( {canStaneksGift && (
<ListItem <ListItem
button button

@ -66,10 +66,9 @@ SourceFiles["SourceFile5"] = new SourceFile(
<> <>
This Source-File grants a special new stat called Intelligence. Intelligence is unique because it is permanent and This Source-File grants a special new stat called Intelligence. Intelligence is unique because it is permanent and
persistent (it never gets reset back to 1). However, gaining Intelligence experience is much slower than other persistent (it never gets reset back to 1). However, gaining Intelligence experience is much slower than other
stats, and it is also hidden (you won't know when you gain experience and how much). Higher Intelligence levels stats. Higher Intelligence levels will boost your production for many actions in the game. In addition, this
will boost your production for many actions in the game. In addition, this Source-File will unlock the Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and
getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and will raise all of your will raise all of your hacking-related multipliers by:
hacking-related multipliers by:
<br /> <br />
<br /> <br />
Level 1: 8% Level 1: 8%

@ -117,7 +117,7 @@ export function buyStock(
const resultTxt = const resultTxt =
`Bought ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol} for ${numeralWrapper.formatMoney( `Bought ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol} for ${numeralWrapper.formatMoney(
totalPrice, totalPrice,
)}. ` + `Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`; )}. Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`;
workerScript.log("stock.buy", () => resultTxt); workerScript.log("stock.buy", () => resultTxt);
} else if (opts.suppressDialog !== true) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate( dialogBoxCreate(

@ -165,8 +165,7 @@ function executeOrder(order: Order, refs: IProcessOrderRefs): void {
console.error("Could not find the following Order in Order Book: "); console.error("Could not find the following Order in Order Book: ");
console.error(order); console.error(order);
} else { } else if (isBuy) {
if (isBuy) {
dialogBoxCreate( dialogBoxCreate(
<> <>
Failed to execute {order.type} for {stock.symbol} @ <Money money={order.price} /> ({pos}). This is most likely Failed to execute {order.type} for {stock.symbol} @ <Money money={order.price} /> ({pos}). This is most likely
@ -174,5 +173,4 @@ function executeOrder(order: Order, refs: IProcessOrderRefs): void {
</>, </>,
); );
} }
}
} }

@ -213,13 +213,11 @@ export class Stock {
} else { } else {
this.otlkMag -= changeAmt; this.otlkMag -= changeAmt;
} }
} else { } else if (this.b) {
// Forecast decreases // Forecast decreases
if (this.b) { this.otlkMag -= changeAmt;
this.otlkMag -= changeAmt; } else {
} else { this.otlkMag += changeAmt;
this.otlkMag += changeAmt;
}
} }
this.otlkMag = Math.min(this.otlkMag, 50); this.otlkMag = Math.min(this.otlkMag, 50);

@ -113,11 +113,9 @@ export function StockTicker(props: IProps): React.ReactElement {
if (qty > stock.playerShares) { if (qty > stock.playerShares) {
return <>You do not have this many shares in the Long position</>; return <>You do not have this many shares in the Long position</>;
} }
} else { } else if (qty > stock.playerShortShares) {
if (qty > stock.playerShortShares) {
return <>You do not have this many shares in the Short position</>; return <>You do not have this many shares in the Short position</>;
} }
}
const cost = getSellTransactionGain(stock, qty, position); const cost = getSellTransactionGain(stock, qty, position);
if (cost == null) { if (cost == null) {

@ -190,7 +190,7 @@ export const HelpTexts: IMap<string[]> = {
connect: [ connect: [
"Usage: connect [hostname]", "Usage: connect [hostname]",
" ", " ",
"Connect to a remote server. The hostname or IP address of the remote server must be given as the argument ", "Connect to a remote server. The hostname of the remote server must be given as the argument ",
"to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ", "to this command. Note that only servers that are immediately adjacent to the current server in the network can be connected to. To ",
"see which servers can be connected to, use the 'scan' command.", "see which servers can be connected to, use the 'scan' command.",
" ", " ",

@ -68,13 +68,11 @@ export function ParseCommand(command: string): (string | number | boolean)[] {
} }
continue; continue;
} }
} else { } else if (inQuote === ``) {
if (inQuote === ``) {
inQuote = `"`; inQuote = `"`;
} else if (inQuote === `"`) { } else if (inQuote === `"`) {
inQuote = ``; inQuote = ``;
} }
}
} else if (c === "'") { } else if (c === "'") {
// Single quotes, same thing as above // Single quotes, same thing as above
if (!escaped && prevChar === " ") { if (!escaped && prevChar === " ") {
@ -88,13 +86,11 @@ export function ParseCommand(command: string): (string | number | boolean)[] {
} }
continue; continue;
} }
} else { } else if (inQuote === ``) {
if (inQuote === ``) {
inQuote = `'`; inQuote = `'`;
} else if (inQuote === `'`) { } else if (inQuote === `'`) {
inQuote = ``; inQuote = ``;
} }
}
} else if (c === " " && inQuote === ``) { } else if (c === " " && inQuote === ``) {
const arg = command.substr(start, i - start); const arg = command.substr(start, i - start);

@ -167,7 +167,7 @@ export function ls(
function postSegments(segments: string[], style?: any, linked?: boolean): void { function postSegments(segments: string[], style?: any, linked?: boolean): void {
const maxLength = Math.max(...segments.map((s) => s.length)) + 1; const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
const filesPerRow = Math.floor(80 / maxLength); const filesPerRow = Math.ceil(80 / maxLength);
for (let i = 0; i < segments.length; i++) { for (let i = 0; i < segments.length; i++) {
let row = ""; let row = "";
for (let col = 0; col < filesPerRow; col++) { for (let col = 0; col < filesPerRow; col++) {
@ -179,13 +179,11 @@ export function ls(
i--; i--;
if (!style) { if (!style) {
terminal.print(row); terminal.print(row);
} else { } else if (linked) {
if (linked) {
terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />); terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
} else { } else {
terminal.printRaw(<span style={style}>{row}</span>); terminal.printRaw(<span style={style}>{row}</span>);
} }
}
} }
} }

@ -14,11 +14,9 @@ export function unalias(
if (args.length !== 1) { if (args.length !== 1) {
terminal.error("Incorrect usage of unalias name. Usage: unalias [alias]"); terminal.error("Incorrect usage of unalias name. Usage: unalias [alias]");
return; return;
} else { } else if (removeAlias(args[0] + "")) {
if (removeAlias(args[0] + "")) {
terminal.print(`Removed alias ${args[0]}`); terminal.print(`Removed alias ${args[0]}`);
} else { } else {
terminal.error(`No such alias exists: ${args[0]}`); terminal.error(`No such alias exists: ${args[0]}`);
} }
}
} }

@ -66,28 +66,22 @@ export function tabCompletion(
if (arg === "") { if (arg === "") {
if (longestStartSubstr === command) { if (longestStartSubstr === command) {
return allPossibilities; return allPossibilities;
} else { } else if (semiColonIndex === -1) {
if (semiColonIndex === -1) {
// No semicolon, so replace the whole command // No semicolon, so replace the whole command
return longestStartSubstr; return longestStartSubstr;
} else { } else {
// Replace only after the last semicolon // Replace only after the last semicolon
return `${oldValue.slice(0, semiColonIndex + 1)} ${longestStartSubstr}`; return `${oldValue.slice(0, semiColonIndex + 1)} ${longestStartSubstr}`;
} }
} } else if (longestStartSubstr === arg) {
} else {
if (longestStartSubstr === arg) {
// List all possible options // List all possible options
return allPossibilities; return allPossibilities;
} else { } else if (semiColonIndex == -1) {
if (semiColonIndex == -1) {
// No semicolon, so replace the whole command // No semicolon, so replace the whole command
return `${command} ${longestStartSubstr}`; return `${command} ${longestStartSubstr}`;
} else { } else {
// Replace only after the last semicolon // Replace only after the last semicolon
return `${oldValue.slice(0, semiColonIndex + 1)} ${command} ${longestStartSubstr}`; return `${oldValue.slice(0, semiColonIndex + 1)} ${command} ${longestStartSubstr}`;
} }
}
}
} }
} }

@ -640,7 +640,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"of the path, you may only move to adjacent numbers in the row below.", "of the path, you may only move to adjacent numbers in the row below.",
"The triangle is represented as a 2D array of numbers:\n\n", "The triangle is represented as a 2D array of numbers:\n\n",
`${triangle}\n\n`, `${triangle}\n\n`,
"Example: If you are given the following triangle:\n\n" + "[\n", "Example: If you are given the following triangle:\n\n[\n",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[2],\n", "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[2],\n",
"&nbsp;&nbsp;&nbsp;&nbsp;[3,4],\n", "&nbsp;&nbsp;&nbsp;&nbsp;[3,4],\n",
"&nbsp;&nbsp;&nbsp;[6,5,7],\n", "&nbsp;&nbsp;&nbsp;[6,5,7],\n",

@ -56,7 +56,7 @@ export function ServerAccordions(props: IProps): React.ReactElement {
for (const ws of props.workerScripts.values()) { for (const ws of props.workerScripts.values()) {
const server = GetServer(ws.hostname); const server = GetServer(ws.hostname);
if (server == null) { if (server == null) {
console.warn(`WorkerScript has invalid IP address: ${ws.hostname}`); console.warn(`WorkerScript has invalid hostname: ${ws.hostname}`);
continue; continue;
} }

@ -86,6 +86,9 @@ function Intelligence(): React.ReactElement {
<TableCell align="right"> <TableCell align="right">
<Typography>{numeralWrapper.formatSkill(player.intelligence)}&nbsp;</Typography> <Typography>{numeralWrapper.formatSkill(player.intelligence)}&nbsp;</Typography>
</TableCell> </TableCell>
<TableCell align="right">
<Typography noWrap>({numeralWrapper.formatExp(player.intelligence_exp)} exp)</Typography>
</TableCell>
</TableRow> </TableRow>
); );
} }

@ -33,23 +33,34 @@ interface IProps {
} }
function Intelligence(): React.ReactElement { function Intelligence(): React.ReactElement {
const theme = useTheme();
const player = use.Player(); const player = use.Player();
const classes = useStyles(); const classes = useStyles();
if (player.intelligence === 0) return <></>; if (player.intelligence === 0) return <></>;
const progress = player.calculateSkillProgress(player.intelligence_exp);
return ( return (
<TableRow> <>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}> <TableRow>
<Typography classes={{ root: classes.int }}>Int&nbsp;</Typography> <TableCell component="th" scope="row" classes={{ root: classes.cell }}>
</TableCell> <Typography classes={{ root: classes.int }}>Int&nbsp;</Typography>
<TableCell align="right" classes={{ root: classes.cell }}> </TableCell>
<Typography classes={{ root: classes.int }}>{numeralWrapper.formatSkill(player.intelligence)}</Typography> <TableCell align="right" classes={{ root: classes.cell }}>
</TableCell> <Typography classes={{ root: classes.int }}>{numeralWrapper.formatSkill(player.intelligence)}</Typography>
<TableCell align="right" classes={{ root: classes.cell }}> </TableCell>
<Typography id="overview-int-hook" classes={{ root: classes.int }}> <TableCell align="right" classes={{ root: classes.cell }}>
{/*Hook for player scripts*/} <Typography id="overview-int-hook" classes={{ root: classes.int }}>
</Typography> {/*Hook for player scripts*/}
</TableCell> </Typography>
</TableRow> </TableCell>
</TableRow>
<TableRow>
{!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={progress} color={theme.colors.int} />
)}
</TableRow>
</>
); );
} }

@ -3,54 +3,150 @@ import { EventEmitter } from "../../utils/EventEmitter";
import { Modal } from "./Modal"; import { Modal } from "./Modal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; 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";
export const PromptEvent = new EventEmitter<[Prompt]>(); export const PromptEvent = new EventEmitter<[Prompt]>();
interface Prompt { interface Prompt {
txt: string; txt: string;
resolve: (result: boolean) => void; options?: { type?: string; choices?: string[] };
resolve: (result: boolean | string) => void;
} }
export function PromptManager(): React.ReactElement { export function PromptManager(): React.ReactElement {
const [prompt, setPrompt] = useState<Prompt | null>(null); const [prompt, setPrompt] = useState<Prompt | null>(null);
useEffect( useEffect(() => {
() => return PromptEvent.subscribe((p: Prompt) => {
PromptEvent.subscribe((p: Prompt) => { setPrompt(p);
setPrompt(p); });
}), }, []);
[],
); if (prompt === null) {
return <></>;
}
function close(): void { function close(): void {
if (prompt === null) return; if (prompt === null) return;
prompt.resolve(false); if (["text", "select"].includes(prompt?.options?.type ?? "")) {
prompt.resolve("");
} else {
prompt.resolve(false);
}
setPrompt(null); setPrompt(null);
} }
function yes(): void { const types: { [key: string]: any } = {
if (prompt === null) return; text: PromptMenuText,
prompt.resolve(true); select: PromptMenuSelect,
};
let PromptContent = PromptMenuBoolean;
if (prompt?.options?.type) PromptContent = types[prompt?.options?.type];
const resolve = (value: boolean | string): void => {
prompt.resolve(value);
setPrompt(null); setPrompt(null);
} };
function no(): void {
if (prompt === null) return; return (
prompt.resolve(false); <Modal open={true} onClose={close}>
setPrompt(null); <Typography>{prompt.txt}</Typography>
} <PromptContent prompt={prompt} resolve={resolve} />
</Modal>
);
}
interface IContentProps {
prompt: Prompt;
resolve: (value: boolean | string) => void;
}
function PromptMenuBoolean({ resolve }: IContentProps): React.ReactElement {
const yes = (): void => resolve(true);
const no = (): void => resolve(false);
return ( return (
<> <>
{prompt != null && ( <div style={{ display: "flex", justifyContent: "center", alignItems: "center", paddingTop: "10px" }}>
<Modal open={true} onClose={close}> <Button style={{ marginRight: "auto" }} onClick={yes}>
<pre> Yes
<Typography>{prompt.txt}</Typography> </Button>
</pre> <Button onClick={no}>No</Button>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}> </div>
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button> </>
<Button onClick={no}>No</Button> );
</div> }
</Modal>
)} function PromptMenuText({ resolve }: IContentProps): React.ReactElement {
const [value, setValue] = useState("");
const submit = (): void => resolve(value);
const onInput = (event: React.ChangeEvent<HTMLInputElement>): void => {
setValue(event.target.value);
};
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
event.stopPropagation();
if (event.key === "Enter") {
event.preventDefault();
submit();
}
};
return (
<>
<div style={{ display: "flex", alignItems: "center", paddingTop: "10px" }}>
<TextField
autoFocus
value={value}
onInput={onInput}
onKeyDown={onKeyDown}
style={{ flex: "1 0 auto" }}
InputProps={{
endAdornment: <Button onClick={submit}>Confirm</Button>,
}}
/>
</div>
</>
);
}
function PromptMenuSelect({ prompt, resolve }: IContentProps): React.ReactElement {
const [value, setValue] = useState("");
const submit = (): void => resolve(value);
const onChange = (event: SelectChangeEvent<string>): void => {
setValue(event.target.value);
};
const getItems = (choices: string[]): React.ReactElement[] => {
const content = [];
for (const i of choices) {
// @ts-ignore
content.push(
<MenuItem key={i} value={i}>
{i}
</MenuItem>,
);
}
return content;
};
return (
<>
<div style={{ display: "flex", alignItems: "center", paddingTop: "10px" }}>
<Select onChange={onChange} value={value} style={{ flex: "1 0 auto" }}>
{getItems(prompt?.options?.choices || [])}
</Select>
<Button onClick={submit} disabled={value === ""}>
Confirm
</Button>
</div>
</> </>
); );
} }

@ -17,8 +17,7 @@ export function validateObject<Type extends Record<string, unknown>, Key extends
if (paramValidator !== undefined) { if (paramValidator !== undefined) {
if (typeof paramValidator === 'function') { if (typeof paramValidator === 'function') {
paramValidator(obj, key); paramValidator(obj, key);
} else { } else if (paramValidator.func !== undefined) {
if (paramValidator.func !== undefined) {
paramValidator.func(obj, validator, key); paramValidator.func(obj, validator, key);
} else { } else {
if ((typeof obj[key]) !== (typeof paramValidator.default)) { if ((typeof obj[key]) !== (typeof paramValidator.default)) {
@ -31,7 +30,6 @@ export function validateObject<Type extends Record<string, unknown>, Key extends
if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as Type[Key]; if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as Type[Key];
} }
} }
}
} }
} }
} }

@ -94,9 +94,9 @@ export function v1APIBreak(): void {
if (s.length === 0) continue; if (s.length === 0) continue;
txt += `// Detected change ${change[0]}, reason: ${change[1]}` + "\n"; txt += `// Detected change ${change[0]}, reason: ${change[1]}\n`;
for (const fl of s) { for (const fl of s) {
txt += `${fl.file}:${fl.line}` + "\n"; txt += `${fl.file}:${fl.line}\n`;
} }
} }
} }

@ -59,7 +59,7 @@ async function main(version, versionNumber, changelog) {
join('\n').replaceAll('`', '\\`'); join('\n').replaceAll('`', '\\`');
modifiedConstants = modifiedConstants. modifiedConstants = modifiedConstants.
replace(/(^\s*?LatestUpdate:\s`\n)(.*)`,$/ms, `$1${paddedChangelog}\n` + "`,"); replace(/(^\s*?LatestUpdate:\s`\n)(.*)`,$/ms, `$1${paddedChangelog}\n\`,`);
} }
await fs.writeFile(appPaths.constants, modifiedConstants); await fs.writeFile(appPaths.constants, modifiedConstants);
console.log(`Modified ${appPaths.constants}`); console.log(`Modified ${appPaths.constants}`);