Merge branch 'dev' into bugfix/2877-2

This commit is contained in:
hydroflame 2022-03-17 12:05:38 -04:00 committed by GitHub
commit 561219a0ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 669 additions and 418 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:

@ -542,28 +542,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,9 +337,8 @@ 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) {
@ -354,7 +353,6 @@ async function restoreIfNewerExists(window) {
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) {
// We add a few seconds to the currentSave's lastSave to prioritize it // We add a few seconds to the currentSave's lastSave to prioritize it

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;
@ -231,7 +230,6 @@ export function SellProduct(product: Product, city: string, amt: string, price:
} }
} }
} }
}
export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void { export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void {
warehouse.smartSupplyEnabled = smartSupply; warehouse.smartSupplyEnabled = smartSupply;

@ -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);
@ -47,7 +46,6 @@ export function ThrowPartyModal(props: IProps): React.ReactElement {
props.onClose(); props.onClose();
} }
} }
}
function EffectText(): React.ReactElement { function EffectText(): React.ReactElement {
if (isNaN(cost) || cost < 0) return <Typography>Invalid value entered!</Typography>; if (isNaN(cost) || cost < 0) return <Typography>Invalid value entered!</Typography>;

@ -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,9 +52,8 @@ 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);
@ -68,7 +67,6 @@ export function SpecialLocation(props: IProps): React.ReactElement {
dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)"); dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)");
} }
} }
}
/** /**
* Click handler for Resleeving button at New Tokyo VitaLife * Click handler for Resleeving button at New Tokyo VitaLife

@ -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,

@ -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,10 +2140,12 @@ 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"`);
@ -2120,6 +2153,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
SnackbarEvents.emit(message, variant, duration); SnackbarEvents.emit(message, variant, duration);
}, },
prompt: function (txt: any): any { prompt: function (txt: any): any {
updateDynamicRam("prompt", getRamCost(Player, "prompt"));
if (!isString(txt)) { if (!isString(txt)) {
txt = JSON.stringify(txt); txt = JSON.stringify(txt);
} }
@ -2132,6 +2166,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}); });
}, },
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 +2208,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 +2219,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 +2308,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,14 +169,17 @@ 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;
} }
@ -174,7 +191,8 @@ export function NetscriptHacknet(player: IPlayer, workerScript: WorkerScript, he
} }
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>;

@ -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

@ -608,13 +608,11 @@ 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();
} }
} }
} }
}
export function cancelationPenalty(this: IPlayer): number { export function cancelationPenalty(this: IPlayer): number {
const data = serverMetadata.find((s) => s.specialName === this.companyName); const data = serverMetadata.find((s) => s.specialName === this.companyName);
@ -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,8 +34,33 @@ export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentat
return false; return false;
} }
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 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
// has enough reputation for (since that gang offers all augs) // has enough reputation for (since that gang offers all augs)

@ -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,16 +80,21 @@ 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 }} />)}
{program.name}
</Typography>
{(!player.hasProgram(program.name) && create.req(player)) && <Button
sx={{ my: 1, width: '100%' }}
onClick={(event) => { onClick={(event) => {
if (!event.isTrusted) return; if (!event.isTrusted) return;
player.startCreateProgramWork(program.name, create.time, create.level); player.startCreateProgramWork(program.name, create.time, create.level);
@ -55,13 +102,23 @@ export function ProgramsRoot(): React.ReactElement {
router.toWork(); router.toWork();
}} }}
> >
{program.name} Create program
</Button> </Button>}
</Tooltip> {(player.hasProgram(program.name) || getHackingLevelRemaining(create.level) === 0) ||
</React.Fragment> <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;
} }
/** /**

@ -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[][]>([["???", ""]]);
@ -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;
} }
@ -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,13 +834,26 @@ 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,
@ -870,8 +896,9 @@ 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,

@ -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;
} }

@ -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,7 +434,6 @@ export function SidebarRoot(props: IProps): React.ReactElement {
</Typography> </Typography>
</ListItemText> </ListItemText>
</ListItem> </ListItem>
{canCreateProgram && (
<ListItem <ListItem
button button
key={"Create Program"} key={"Create Program"}
@ -461,7 +455,6 @@ export function SidebarRoot(props: IProps): React.ReactElement {
</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
@ -175,4 +174,3 @@ function executeOrder(order: Order, refs: IProcessOrderRefs): void {
); );
} }
} }
}

@ -213,14 +213,12 @@ 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);
if (this.otlkMag < 0) { if (this.otlkMag < 0) {

@ -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) {

@ -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,15 +179,13 @@ 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>);
} }
} }
} }
}
const groups = [ const groups = [
{ segments: folders, style: { color: "cyan" } }, { segments: folders, style: { color: "cyan" } },

@ -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,21 +66,17 @@ 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 {
@ -89,5 +85,3 @@ export function tabCompletion(
} }
} }
} }
}
}

@ -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",

@ -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,10 +33,14 @@ 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> <TableRow>
<TableCell component="th" scope="row" classes={{ root: classes.cell }}> <TableCell component="th" scope="row" classes={{ root: classes.cell }}>
<Typography classes={{ root: classes.int }}>Int&nbsp;</Typography> <Typography classes={{ root: classes.int }}>Int&nbsp;</Typography>
@ -50,6 +54,13 @@ function Intelligence(): React.ReactElement {
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow>
{!Settings.DisableOverviewProgressBars && (
<StatsProgressOverviewCell progress={progress} color={theme.colors.int} />
)}
</TableRow>
</>
); );
} }

@ -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)) {
@ -34,7 +33,6 @@ export function validateObject<Type extends Record<string, unknown>, Key extends
} }
} }
} }
}
export function minMax<Type, Key extends keyof Type>(def: number, min: number, max: number): (obj: Type, key: Key & keyof Type) => void { export function minMax<Type, Key extends keyof Type>(def: number, min: number, max: number): (obj: Type, key: Key & keyof Type) => void {
return (obj, key) => { return (obj, 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}`);