Merge branch 'dev' of github.com:danielyxie/bitburner into improvement/sleeve-ui

This commit is contained in:
nickofolas
2022-03-16 16:48:13 -05:00
59 changed files with 680 additions and 444 deletions

View File

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

View File

@ -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).
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
of gaining Intelligence exp is also hidden. You won't know when you gain
experience and how much. It is a stat that gradually builds up as you continue
to play the game.
back to 1. However, gaining Intelligence experience is extremely slow. 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:

View File

@ -335,7 +335,7 @@ Then to kill this script the same arguments would have to be used::
$ kill foo.script 50e3 sigma-cosmetics
If you are killing the script using its PID, then the PID argument must be numeric.
If you are killing the script using its PID, then the PID argument must be numeric.
killall
^^^^^^^
@ -542,28 +542,6 @@ Then in order to check its logs with 'tail' the same arguments must be used::
$ 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
^^^

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

38
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "bitburner",
"version": "1.4.0",
"version": "1.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitburner",
"version": "1.4.0",
"version": "1.5.0",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
@ -17,9 +17,11 @@
"@mui/icons-material": "^5.0.3",
"@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1",
"@types/bcrypt": "^5.0.0",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^5.0.0",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^1.0.3",
"clsx": "^1.1.1",
"date-fns": "^2.25.0",
@ -3977,6 +3979,14 @@
"@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/escodegen": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
@ -4068,8 +4078,7 @@
"node_modules/@types/node": {
"version": "16.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.4.tgz",
"integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA==",
"dev": true
"integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA=="
},
"node_modules/@types/numeral": {
"version": "0.0.25",
@ -5448,6 +5457,11 @@
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-1.0.3.tgz",
@ -25344,6 +25358,14 @@
"@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/escodegen": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
@ -25435,8 +25457,7 @@
"@types/node": {
"version": "16.10.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.4.tgz",
"integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA==",
"dev": true
"integrity": "sha512-EITwVTX5B4nDjXjGeQAfXOrm+Jn+qNjDmyDRtWoD+wZsl/RDPRTFRKivs4Mt74iOFlLOrE5+Kf+p5yjyhm3+cA=="
},
"@types/numeral": {
"version": "0.0.25",
@ -26527,6 +26548,11 @@
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-1.0.3.tgz",

View File

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

View File

@ -481,7 +481,16 @@
"ID": "DEVMENU",
"Name": "Exploit: you're not meant to access this",
"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."
}
}
}

View File

@ -553,7 +553,8 @@ export const achievements: IMap<Achievement> = {
...achievementData["MAX_CACHE"],
Icon: "HASHNETCAP",
Visible: () => hasAccessToSF(Player, 9),
Condition: () => hasHacknetServers(Player) &&
Condition: () =>
hasHacknetServers(Player) &&
Player.hashManager.hashes === Player.hashManager.capacity &&
Player.hashManager.capacity > 0,
},
@ -729,6 +730,18 @@ export const achievements: IMap<Achievement> = {
Secret: true,
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.

View File

@ -0,0 +1,43 @@
import React, { useState } from "react";
import { BBCabinetRoot } from "./BBCabinet";
import Button from "@mui/material/Button";
import { use } from "../../ui/Context";
import { AlertEvents } from "../../ui/React/AlertManager";
enum Page {
None,
Megabyteburner2000,
}
export function ArcadeRoot(): React.ReactElement {
const player = use.Player();
const [page, setPage] = useState(Page.None);
function mbBurner2000(): void {
if (player.sourceFileLvl(1) === 0) {
AlertEvents.emit("This machine is broken.");
} else {
setPage(Page.Megabyteburner2000);
}
}
if (page === Page.None) {
return (
<>
<Button onClick={mbBurner2000}>Megabyte burner 2000</Button>
</>
);
}
let currentGame = <></>;
switch (page) {
case Page.Megabyteburner2000:
currentGame = <BBCabinetRoot />;
}
return (
<>
<Button onClick={() => setPage(Page.None)}>Back</Button>
{currentGame}
</>
);
}

View File

@ -0,0 +1,62 @@
import React, { useEffect } from "react";
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 style = {
width: "1060px",
height: "800px",
border: "0px",
} as any;
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
const joystick =
<>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ,'" "', .-. </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> / \ ( ) </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | .-. '-' .-. </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ / ( ) ( )</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> '.___.' '-' .-. '-'</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ||| ( ) </Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ||| '-' </Typography>
</>;
return (
<>
<div
style={{
width: "1060px",
height: "800px",
padding: "0",
overflow: "hidden",
borderColor: "white",
borderStyle: "solid",
borderWidth: "5px",
}}
>
<iframe src={metaBB} style={style} />
</div>
<div
style={{
width: "1060px",
borderColor: "white",
borderStyle: "solid",
borderWidth: "5px",
}}
>
{joystick}
</div>
</>
);
}

View File

@ -148,7 +148,7 @@ function initAugmentations(): void {
name: AugmentationNames.HemoRecirculator,
moneyCost: 4.5e7,
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,
defense_mult: 1.08,
agility_mult: 1.08,
@ -430,7 +430,7 @@ function initAugmentations(): void {
repCost: 1.125e6,
moneyCost: 4.25e9,
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,
defense_mult: 1.7,
});
@ -1085,7 +1085,7 @@ function initAugmentations(): void {
name: AugmentationNames.FocusWire,
repCost: 7.5e4,
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,
strength_exp_mult: 1.05,
defense_exp_mult: 1.05,
@ -1486,7 +1486,7 @@ function initAugmentations(): void {
name: AugmentationNames.SmartSonar,
repCost: 2.25e4,
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_exp_mult: 1.15,
crime_money_mult: 1.25,
@ -1703,7 +1703,7 @@ function initAugmentations(): void {
"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. " +
"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.",
strength_mult: 2.7,
});
@ -2015,7 +2015,7 @@ function initAugmentations(): void {
repCost: 6.25e4,
moneyCost: 2.75e8,
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,
dexterity_mult: 1.3,
});

View File

@ -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
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
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 will boost your production for many actions in the game. <br />
experience is much slower than other stats. Higher Intelligence levels will boost your production for many actions
in the game. <br />
<br />
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:

View File

@ -135,7 +135,7 @@ export class Action implements IAction {
if (this.decays.hasOwnProperty(decay)) {
if (this.decays[decay] > 1) {
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`,
);
}
}

View File

@ -8,7 +8,7 @@ export const Skills: IMap<Skill> = {};
Skills[SkillNames.BladesIntuition] = new Skill({
name: SkillNames.BladesIntuition,
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,
costInc: 2.1,
successChanceAll: 3,
@ -33,14 +33,14 @@ export const Skills: IMap<Skill> = {};
});
Skills[SkillNames.DigitalObserver] = new Skill({
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,
costInc: 2.1,
successChanceOperation: 4,
});
Skills[SkillNames.Tracer] = new Skill({
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,
costInc: 2.1,
successChanceContract: 4,
@ -67,7 +67,7 @@ export const Skills: IMap<Skill> = {};
});
Skills[SkillNames.EvasiveSystem] = new Skill({
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,
costInc: 2.1,
effDex: 4,

View File

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

View File

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

View File

@ -106,7 +106,7 @@ export const researchMetadata: IConstructorParams[] = [
{
name: "JoyWire",
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",
@ -160,7 +160,7 @@ export const researchMetadata: IConstructorParams[] = [
{
name: "sudo.Assist",
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",

View File

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

View File

@ -19,6 +19,8 @@ export enum Exploit {
RealityAlteration = "RealityAlteration",
N00dles = "N00dles",
YoureNotMeantToAccessThis = "YoureNotMeantToAccessThis",
TrueRecursion = "TrueRecursion",
INeedARainbow = "INeedARainbow",
// 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
// that's not the point.
@ -37,6 +39,8 @@ const names: {
RealityAlteration: "by altering reality to suit your whims.",
N00dles: "by harnessing the power of the n00dles.",
YoureNotMeantToAccessThis: "by accessing the dev menu.",
TrueRecursion: "by truly recursing.",
INeedARainbow: "by using the power of the rainbow.",
};
export function ExploitName(exploit: string): string {

View File

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

View File

@ -28,7 +28,7 @@ type IProps = {
};
// 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 =
"Complete hacking contracts for your faction. " +
"Your effectiveness, which determines how much " +

View File

@ -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 {
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 { Table, TableCell } from "../../ui/React/Table";
import { IRouter } from "../../ui/Router";
import { Faction } from "../Faction";
import { joinFaction } from "../FactionHelpers";
import { Factions } from "../Factions";
@ -51,6 +58,28 @@ export function FactionsRoot(props: IProps): React.ReactElement {
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 (
<Container disableGutters maxWidth="md" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Factions</Typography>
@ -82,11 +111,7 @@ export function FactionsRoot(props: IProps): React.ReactElement {
<TableCell align="right">
<Box ml={1} mb={1}>
<Button sx={{ width: '100%' }} onClick={() => openFactionAugPage(Factions[faction])}>
Augmentations Left: {Factions[faction]
.augmentations
.filter((augmentation: string) =>
!props.player.hasAugmentation(augmentation))
.length}
Augmentations Left: {getAugsLeft(Factions[faction], props.player)}
</Button>
</Box>
</TableCell>

View File

@ -271,7 +271,7 @@ export function EquipmentsSubpage(): React.ReactElement {
sx={{ m: 1, width: '15%' }}
/>
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: 'fit-content' }}>
<Box display="grid" sx={{ gridTemplateColumns: '1fr 1fr', width: '100%' }}>
{members.map((member: GangMember) => (
<GangMemberUpgradePanel key={member.name} member={member} />
))}

View File

@ -127,10 +127,10 @@ Cities[CityName.NewTokyo].asciiArt = `
o
\\
\\ [defcomm]
[arcade] E [defcomm]
\\
o--x---A--x--o [travel agency]
7 8 10 G
7 8 10 H
[vitalife] o 12 [global pharmaceuticals]
|
o--D-x----x-------x-C-+--------x--x-B-x---x-o
@ -141,14 +141,14 @@ Cities[CityName.NewTokyo].asciiArt = `
\\
[hospital] o 15 [world stock exchange]
|
o--x--E--x-----x-----x---+---x----x--H--x-o
o--x--F--x-----x-----x---+---x----x--I--x-o
|
|
o 17
F [the slums]
G [the slums]
`;
Cities[CityName.Sector12].asciiArt = `
78 o 97

View File

@ -54,6 +54,7 @@ export enum LocationName {
NewTokyoGlobalPharmaceuticals = "Global Pharmaceuticals",
NewTokyoNoodleBar = "Noodle Bar",
NewTokyoVitaLife = "VitaLife",
NewTokyoArcade = "Arcade",
// Ishima
IshimaNovaMedical = "Nova Medical",

View File

@ -215,6 +215,11 @@ export const LocationsMetadata: IConstructorParams[] = [
name: LocationName.NewTokyoVitaLife,
types: [LocationType.Company, LocationType.Special],
},
{
city: CityName.NewTokyo,
name: LocationName.NewTokyoArcade,
types: [LocationType.Special],
},
{
city: CityName.Sector12,
infiltrationData: {

View File

@ -34,10 +34,9 @@ const useStyles = makeStyles((theme: Theme) =>
padding: "0px",
cursor: "pointer",
},
})
}),
);
function toLocation(router: IRouter, location: Location): void {
if (location.name === LocationName.TravelAgency) {
router.toTravel();
@ -132,12 +131,14 @@ function ASCIICity(props: IProps): React.ReactElement {
const elems: JSX.Element[] = [];
const lines = props.city.asciiArt.split("\n");
let i = 0;
for (const line of lines) {
elems.push(
<Typography key={line} sx={{ lineHeight: "1em", whiteSpace: "pre" }}>
<Typography key={i} sx={{ lineHeight: "1em", whiteSpace: "pre" }}>
{lineElems(line)}
</Typography>,
);
i++;
}
return <>{elems}</>;

View File

@ -32,6 +32,7 @@ import { CorruptableText } from "../../ui/React/CorruptableText";
import { HacknetNode } from "../../Hacknet/HacknetNode";
import { HacknetServer } from "../../Hacknet/HacknetServer";
import { GetServer } from "../../Server/AllServers";
import { ArcadeRoot } from "../../Arcade/ui/ArcadeRoot";
type IProps = {
loc: Location;
@ -51,21 +52,19 @@ export function SpecialLocation(props: IProps): React.ReactElement {
if (p.inBladeburner()) {
// Enter Bladeburner division
router.toBladeburner();
} else {
} else if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {
// Apply for Bladeburner division
if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) {
p.startBladeburner({ new: true });
dialogBoxCreate("You have been accepted into the Bladeburner division!");
setRerender((old) => !old);
p.startBladeburner({new: true});
dialogBoxCreate("You have been accepted into the Bladeburner division!");
setRerender((old) => !old);
const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) {
worldHeader.click();
worldHeader.click();
}
} else {
dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)");
const worldHeader = document.getElementById("world-menu-header");
if (worldHeader instanceof HTMLElement) {
worldHeader.click();
worldHeader.click();
}
} else {
dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)");
}
}
@ -81,7 +80,12 @@ export function SpecialLocation(props: IProps): React.ReactElement {
return <></>;
}
const text = inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division";
return <><br/><Button onClick={handleBladeburner}>{text}</Button></>;
return (
<>
<br />
<Button onClick={handleBladeburner}>{text}</Button>
</>
);
}
function renderNoodleBar(): React.ReactElement {
@ -311,6 +315,9 @@ export function SpecialLocation(props: IProps): React.ReactElement {
case LocationName.IshimaGlitch: {
return renderGlitch();
}
case LocationName.NewTokyoArcade: {
return <ArcadeRoot />;
}
default:
console.error(`Location ${props.loc.name} doesn't have any special properties`);
return <></>;

View File

@ -550,6 +550,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculatePercentMoneyHacked(server, Player);
},
hackAnalyzeSecurity: function (threads: any): number {
updateDynamicRam("hackAnalyzeSecurity", getRamCost(Player, "hackAnalyzeSecurity"));
return CONSTANTS.ServerFortifyAmount * threads;
},
hackAnalyzeChance: function (hostname: any): any {
@ -564,6 +565,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return calculateHackingChance(server, Player);
},
sleep: function (time: any): any {
updateDynamicRam("sleep", getRamCost(Player, "sleep"));
if (time === undefined) {
throw makeRuntimeErrorMsg("sleep", "Takes 1 argument.");
}
@ -573,6 +575,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
});
},
asleep: function (time: any): any {
updateDynamicRam("asleep", getRamCost(Player, "asleep"));
if (time === undefined) {
throw makeRuntimeErrorMsg("asleep", "Takes 1 argument.");
}
@ -650,6 +653,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return numCycleForGrowth(server, Number(growth), Player, cores);
},
growthAnalyzeSecurity: function (threads: any): number {
updateDynamicRam("growthAnalyzeSecurity", getRamCost(Player, "growthAnalyzeSecurity"));
return 2 * CONSTANTS.ServerFortifyAmount * threads;
},
weaken: function (hostname: any, { threads: requestedThreads }: any = {}): any {
@ -701,10 +705,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
});
},
weakenAnalyze: function (threads: any, cores: any = 1): number {
updateDynamicRam("weakenAnalyze", getRamCost(Player, "weakenAnalyze"));
const coreBonus = 1 + (cores - 1) / 16;
return CONSTANTS.ServerWeakenAmount * threads * coreBonus * BitNodeMultipliers.ServerWeakenRate;
},
share: function (): Promise<void> {
updateDynamicRam("share", getRamCost(Player, "share"));
workerScript.log("share", () => "Sharing this computer.");
const end = StartSharing(workerScript.scriptRef.threads * calculateIntelligenceBonus(Player.intelligence, 2));
return netscriptDelay(10000, workerScript).finally(function () {
@ -713,21 +719,25 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
});
},
getSharePower: function (): number {
updateDynamicRam("getSharePower", getRamCost(Player, "getSharePower"));
return CalculateShareMult();
},
print: function (...args: any[]): void {
updateDynamicRam("print", getRamCost(Player, "print"));
if (args.length === 0) {
throw makeRuntimeErrorMsg("print", "Takes at least 1 argument.");
}
workerScript.print(argsToString(args));
},
printf: function (format: string, ...args: any[]): void {
updateDynamicRam("printf", getRamCost(Player, "printf"));
if (typeof format !== "string") {
throw makeRuntimeErrorMsg("printf", "First argument must be string for the format.");
}
workerScript.print(vsprintf(format, args));
},
tprint: function (...args: any[]): void {
updateDynamicRam("tprint", getRamCost(Player, "tprint"));
if (args.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
}
@ -751,6 +761,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
Terminal.print(`${workerScript.scriptRef.filename}: ${str}`);
},
tprintf: function (format: any, ...args: any): any {
updateDynamicRam("tprintf", getRamCost(Player, "tprintf"));
if (typeof format !== "string") {
throw makeRuntimeErrorMsg("tprintf", "First argument must be string for the format.");
}
@ -775,9 +786,11 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
Terminal.print(`${str}`);
},
clearLog: function (): any {
updateDynamicRam("clearLog", getRamCost(Player, "clearLog"));
workerScript.scriptRef.clearLog();
},
disableLog: function (fn: any): any {
updateDynamicRam("disableLog", getRamCost(Player, "disableLog"));
if (fn === "ALL") {
for (fn of Object.keys(possibleLogs)) {
workerScript.disableLogs[fn] = true;
@ -791,6 +804,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
},
enableLog: function (fn: any): any {
updateDynamicRam("enableLog", getRamCost(Player, "enableLog"));
if (fn === "ALL") {
for (fn of Object.keys(possibleLogs)) {
delete workerScript.disableLogs[fn];
@ -803,12 +817,14 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
workerScript.log("enableLog", () => `Enabled logging for ${fn}`);
},
isLogEnabled: function (fn: any): any {
updateDynamicRam("isLogEnabled", getRamCost(Player, "isLogEnabled"));
if (possibleLogs[fn] === undefined) {
throw makeRuntimeErrorMsg("isLogEnabled", `Invalid argument: ${fn}.`);
}
return !workerScript.disableLogs[fn];
},
getScriptLogs: function (fn: any, hostname: any, ...scriptArgs: any): any {
updateDynamicRam("getScriptLogs", getRamCost(Player, "getScriptLogs"));
const runningScriptObj = getRunningScript(fn, hostname, "getScriptLogs", scriptArgs);
if (runningScriptObj == null) {
workerScript.log("getScriptLogs", () => getCannotFindRunningScriptErrorMessage(fn, hostname, scriptArgs));
@ -818,6 +834,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return runningScriptObj.logs.slice();
},
tail: function (fn: any, hostname: any = workerScript.hostname, ...scriptArgs: any): any {
updateDynamicRam("tail", getRamCost(Player, "tail"));
let runningScriptObj;
if (arguments.length === 0) {
runningScriptObj = workerScript.scriptRef;
@ -1084,6 +1101,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return scriptsRunning;
},
exit: function (): any {
updateDynamicRam("exit", getRamCost(Player, "exit"));
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(workerScript)) {
workerScript.log("exit", () => "Exiting...");
@ -1769,6 +1787,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return res;
},
writePort: function (port: any, data: any = ""): any {
updateDynamicRam("writePort", getRamCost(Player, "writePort"));
if (typeof data !== "string" && typeof data !== "number") {
throw makeRuntimeErrorMsg(
"writePort",
@ -1857,6 +1876,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
},
readPort: function (port: any): any {
updateDynamicRam("readPort", getRamCost(Player, "readPort"));
// Read from port
const iport = helper.getValidPort("readPort", port);
const x = iport.read();
@ -1916,6 +1936,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return 0;
},
clearPort: function (port: any): any {
updateDynamicRam("clearPort", getRamCost(Player, "clearPort"));
// Clear port
const iport = helper.getValidPort("clearPort", port);
return iport.clear();
@ -1964,6 +1985,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return suc;
},
getScriptName: function (): any {
updateDynamicRam("getScriptName", getRamCost(Player, "getScriptName"));
return workerScript.name;
},
getScriptRam: function (scriptname: any, hostname: any = workerScript.hostname): any {
@ -2095,6 +2117,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
}
},
nFormat: function (n: any, format: any): any {
updateDynamicRam("nFormat", getRamCost(Player, "nFormat"));
if (isNaN(n) || isNaN(parseFloat(n)) || typeof format !== "string") {
return "";
}
@ -2102,6 +2125,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return numeralWrapper.format(parseFloat(n), format);
},
tFormat: function (milliseconds: any, milliPrecision: any = false): any {
updateDynamicRam("tFormat", getRamCost(Player, "tFormat"));
return convertTimeMsToTimeElapsedString(milliseconds, milliPrecision);
},
getTimeSinceLastAug: function (): any {
@ -2109,10 +2133,12 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return Player.playtimeSinceLastAug;
},
alert: function (message: any): void {
updateDynamicRam("alert", getRamCost(Player, "alert"));
message = argsToString([message]);
dialogBoxCreate(message);
},
toast: function (message: any, variant: any = "success", duration: any = 2000): void {
updateDynamicRam("toast", getRamCost(Player, "toast"));
if (!["success", "info", "warning", "error"].includes(variant))
throw new Error(`variant must be one of "success", "info", "warning", or "error"`);
@ -2120,6 +2146,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
SnackbarEvents.emit(message, variant, duration);
},
prompt: function (txt: any): any {
updateDynamicRam("prompt", getRamCost(Player, "prompt"));
if (!isString(txt)) {
txt = JSON.stringify(txt);
}
@ -2132,6 +2159,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
});
},
wget: async function (url: any, target: any, hostname: any = workerScript.hostname): Promise<boolean> {
updateDynamicRam("wget", getRamCost(Player, "wget"));
if (!isScriptFilename(target) && !target.endsWith(".txt")) {
workerScript.log("wget", () => `Invalid target file: '${target}'. Must be a script or text file.`);
return Promise.resolve(false);
@ -2173,7 +2201,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction);
},
getOwnedSourceFiles: function (): SourceFileLvl[] {
helper.updateDynamicRam("getOwnedSourceFiles", getRamCost(Player, "getOwnedSourceFiles"));
updateDynamicRam("getOwnedSourceFiles", getRamCost(Player, "getOwnedSourceFiles"));
const res: SourceFileLvl[] = [];
for (let i = 0; i < Player.sourceFiles.length; ++i) {
res.push({
@ -2184,7 +2212,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return res;
},
getPlayer: function (): INetscriptPlayer {
helper.updateDynamicRam("getPlayer", getRamCost(Player, "getPlayer"));
updateDynamicRam("getPlayer", getRamCost(Player, "getPlayer"));
const data = {
hacking: Player.hacking,
@ -2273,12 +2301,14 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
jobs: {},
factions: Player.factions.slice(),
tor: Player.hasTorRouter(),
inBladeburner: Player.inBladeburner(),
hasCorporation: Player.hasCorporation(),
};
Object.assign(data.jobs, Player.jobs);
return data;
},
atExit: function (f: any): void {
updateDynamicRam("atExit", getRamCost(Player, "atExit"));
if (typeof f !== "function") {
throw makeRuntimeErrorMsg("atExit", "argument should be function");
}

View File

@ -1,6 +1,7 @@
import { WorkerScript } from "../Netscript/WorkerScript";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Exploit } from "../Exploits/Exploit";
import * as bcrypt from "bcryptjs";
export interface INetscriptExtra {
heart: {
@ -9,6 +10,7 @@ export interface INetscriptExtra {
exploit(): void;
bypass(doc: Document): void;
alterReality(): void;
rainbow(guess: string): void;
}
export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INetscriptExtra {
@ -50,5 +52,17 @@ export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript): INe
player.giveExploit(Exploit.RealityAlteration);
}
},
rainbow: function (guess: unknown): void {
async function tryGuess(): Promise<void> {
const verified = await bcrypt.compare(
guess + "",
"$2a$10$aertxDEkgor8baVtQDZsLuMwwGYmkRM/ohcA6FjmmzIHQeTCsrCcO",
);
if (verified) {
player.giveExploit(Exploit.INeedARainbow);
}
}
tryGuess();
},
};
}

View File

@ -375,7 +375,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): any
});
//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
fnDeclarations.forEach((fn: any) => {
@ -390,7 +390,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): any
});
//Finish
generatedCode += "})(" + namespace + " || " + "(" + namespace + " = {}));\n";
generatedCode += `})(${namespace} || (" + namespace + " = {}));\n`;
} else {
//import {...} from script

View File

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

View File

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

View File

@ -1,9 +1,19 @@
import React, { useState, useEffect } from "react";
import { use } from "../../ui/Context";
import { getAvailableCreatePrograms } from "../ProgramHelpers";
import { find } from "lodash";
import { Box, Tooltip, Typography } from "@mui/material";
import Button from "@mui/material/Button";
import {
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[] = [];
@ -15,7 +25,20 @@ export function ProgramsRoot(): React.ReactElement {
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(() => {
programs.forEach((p) => {
@ -29,8 +52,27 @@ export function ProgramsRoot(): React.ReactElement {
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 (
<>
<Container disableGutters maxWidth="lg" sx={{ mx: 0, mb: 10 }}>
<Typography variant="h4">Create program</Typography>
<Typography>
This page displays any programs that you are able to create. Writing the code for a program takes time, which
@ -38,30 +80,45 @@ export function ProgramsRoot(): React.ReactElement {
time. Your progress will be saved and you can continue later.
</Typography>
<Box sx={{ display: 'grid', width: 'fit-content' }}>
<Box sx={{ display: 'grid', gridTemplateColumns: "repeat(3, 1fr)", my: 1 }}>
{programs.map((program) => {
const create = program.create;
if (create === null) return <></>;
const curCompletion = getProgCompletion(program.name);
return (
<React.Fragment key={program.name}>
<Tooltip title={create.tooltip}>
<Button
sx={{ my: 1 }}
onClick={(event) => {
if (!event.isTrusted) return;
player.startCreateProgramWork(program.name, create.time, create.level);
player.startFocusing();
router.toWork();
}}
>
{program.name}
</Button>
</Tooltip>
</React.Fragment>
<Box component={Paper} sx={{ p: 1, opacity: player.hasProgram(program.name) ? 0.75 : 1 }} key={program.name}>
<Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap' }}>
{player.hasProgram(program.name) && <Check sx={{ mr: 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) => {
if (!event.isTrusted) return;
player.startCreateProgramWork(program.name, create.time, create.level);
player.startFocusing();
router.toWork();
}}
>
Create program
</Button>}
{(player.hasProgram(program.name) || getHackingLevelRemaining(create.level) === 0) ||
<Typography color={Settings.theme.hack}>
<b>Unlocks in:</b> {getHackingLevelRemaining(create.level)} hacking levels
</Typography>}
{(curCompletion !== -1) &&
<Typography color={Settings.theme.infolight}>
<b>Current completion:</b> {curCompletion}%
</Typography>}
<Typography>
{create.tooltip}
</Typography>
</Box>
);
})}
</Box>
</>
</Container>
);
}

View File

@ -39,7 +39,7 @@ function giveSourceFile(bitNodeNumber: number): void {
if (alreadyOwned && ownedSourceFile) {
if (ownedSourceFile.lvl >= 3 && ownedSourceFile.n !== 12) {
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 {
++ownedSourceFile.lvl;
@ -75,12 +75,10 @@ function giveSourceFile(bitNodeNumber: number): void {
export function enterBitNode(router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number): void {
if (!flume) {
giveSourceFile(destroyedBitNode);
} else {
if (SourceFileFlags[5] === 0 && newBitNode !== 5) {
} else if (SourceFileFlags[5] === 0 && newBitNode !== 5) {
Player.intelligence = 0;
Player.intelligence_exp = 0;
}
}
if (newBitNode === 5 && Player.intelligence === 0) {
Player.intelligence = 1;
}

View File

@ -94,6 +94,7 @@ interface Player {
factions: string[];
tor: boolean;
hasCorporation: boolean;
inBladeburner: boolean;
}
/**

View File

@ -17,6 +17,7 @@ import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculatio
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { numeralWrapper } from "../../ui/numeralFormat";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import SearchIcon from "@mui/icons-material/Search";
import { NetscriptFunctions } from "../../NetscriptFunctions";
import { WorkerScript } from "../../Netscript/WorkerScript";
@ -42,7 +43,7 @@ import { PromptEvent } from "../../ui/React/PromptManager";
import { Modal } from "../../ui/React/Modal";
import libSource from "!!raw-loader!../NetscriptDefinitions.d.ts";
import { Tooltip } from "@mui/material";
import { TextField, Tooltip } from "@mui/material";
interface IProps {
// Map of filename -> code
@ -53,7 +54,7 @@ interface IProps {
vim: boolean;
}
// TODO: try to removve global symbols
// TODO: try to remove global symbols
let symbolsLoaded = false;
let symbols: string[] = [];
export function SetupTextEditor(): void {
@ -113,6 +114,8 @@ export function Root(props: IProps): React.ReactElement {
const vimStatusRef = useRef<HTMLElement>(null);
const [vimEditor, setVimEditor] = useState<any>(null);
const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null);
const [filter, setFilter] = useState("");
const [searchExpanded, setSearchExpanded] = useState(false);
const [ram, setRAM] = useState("RAM: ???");
const [ramEntries, setRamEntries] = useState<string[][]>([["???", ""]]);
@ -232,7 +235,7 @@ export function Root(props: IProps): React.ReactElement {
MonacoVim.VimMode.Vim.mapCommand("gT", "action", "prevTabs", {}, { context: "normal" });
editor.focus();
});
} catch {}
} catch { }
} else if (!options.vim) {
// Whem vim mode is disabled
vimEditor?.dispose();
@ -478,7 +481,7 @@ export function Root(props: IProps): React.ReactElement {
}
try {
infLoop(newCode);
} catch (err) {}
} catch (err) { }
}
function saveScript(scriptToSave: OpenScript): void {
@ -523,7 +526,7 @@ export function Root(props: IProps): React.ReactElement {
const textFile = new TextFile(scriptToSave.fileName, scriptToSave.code);
server.textFiles.push(textFile);
} 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;
}
@ -607,7 +610,7 @@ export function Root(props: IProps): React.ReactElement {
const textFile = new TextFile(currentScript.fileName, currentScript.code);
server.textFiles.push(textFile);
} 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;
}
@ -787,6 +790,16 @@ export function Root(props: IProps): React.ReactElement {
const serverScript = server.scripts.find((s) => s.filename === openScript.fileName);
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:
// 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 tabsMaxWidth = 1640;
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 tabTextWidth = tabMaxWidth - tabIconWidth * 2;
return (
@ -821,23 +834,36 @@ export function Root(props: IProps): React.ReactElement {
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 = {
maxWidth: `${tabIconWidth}px`,
minWidth: `${tabIconWidth}px`,
minHeight: "38.5px",
maxHeight: "38.5px",
...(currentScript?.fileName === openScripts[index].fileName
...(currentScript?.fileName === filteredOpenScripts[index].fileName
? {
background: Settings.theme.button,
borderColor: Settings.theme.button,
color: Settings.theme.primary,
}
background: Settings.theme.button,
borderColor: Settings.theme.button,
color: Settings.theme.primary,
}
: {
background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
}),
background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
}),
};
const scriptTabText = `${hostname}:~/${fileName} ${dirty(index)}`;
@ -870,18 +896,19 @@ export function Root(props: IProps): React.ReactElement {
}}
style={{
maxWidth: `${tabTextWidth}px`,
minHeight: '38.5px',
overflow: "hidden",
...(currentScript?.fileName === openScripts[index].fileName
...(currentScript?.fileName === filteredOpenScripts[index].fileName
? {
background: Settings.theme.button,
borderColor: Settings.theme.button,
color: Settings.theme.primary,
}
background: Settings.theme.button,
borderColor: Settings.theme.button,
color: Settings.theme.primary,
}
: {
background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
}),
background: Settings.theme.backgroundsecondary,
borderColor: Settings.theme.backgroundsecondary,
color: Settings.theme.secondary,
}),
}}
>
<span style={{ overflow: "hidden", direction: "rtl", textOverflow: "ellipsis" }}>

View File

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

View File

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

View File

@ -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
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
will boost your production for many actions in the game. In addition, this Source-File will unlock the
getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and will raise all of your
hacking-related multipliers by:
stats. Higher Intelligence levels will boost your production for many actions in the game. In addition, this
Source-File will unlock the getBitNodeMultipliers() Netscript function and let you start with Formulas.exe, and
will raise all of your hacking-related multipliers by:
<br />
<br />
Level 1: 8%

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -179,13 +179,11 @@ export function ls(
i--;
if (!style) {
terminal.print(row);
} else {
if (linked) {
} else if (linked) {
terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
} else {
terminal.printRaw(<span style={style}>{row}</span>);
}
}
}
}

View File

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

View File

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

View File

@ -640,7 +640,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"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",
`${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;[3,4],\n",
"&nbsp;&nbsp;&nbsp;[6,5,7],\n",

View File

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

View File

@ -110,107 +110,45 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
const uninitialized = (): any => {
throw new Error("Router called before initialization");
};
export let Router: IRouter = {
isInitialized: false,
page: () => {
throw new Error("Router called before initialization");
},
allowRouting: () => {
throw new Error("Router called before initialization");
},
toActiveScripts: () => {
throw new Error("Router called before initialization");
},
toAugmentations: () => {
throw new Error("Router called before initialization");
},
toBitVerse: () => {
throw new Error("Router called before initialization");
},
toBladeburner: () => {
throw new Error("Router called before initialization");
},
toStats: () => {
throw new Error("Router called before initialization");
},
toCity: () => {
throw new Error("Router called before initialization");
},
toCorporation: () => {
throw new Error("Router called before initialization");
},
toCreateProgram: () => {
throw new Error("Router called before initialization");
},
toDevMenu: () => {
throw new Error("Router called before initialization");
},
toFaction: () => {
throw new Error("Router called before initialization");
},
toFactions: () => {
throw new Error("Router called before initialization");
},
toGameOptions: () => {
throw new Error("Router called before initialization");
},
toGang: () => {
throw new Error("Router called before initialization");
},
toHacknetNodes: () => {
throw new Error("Router called before initialization");
},
toInfiltration: () => {
throw new Error("Router called before initialization");
},
toJob: () => {
throw new Error("Router called before initialization");
},
toMilestones: () => {
throw new Error("Router called before initialization");
},
toResleeves: () => {
throw new Error("Router called before initialization");
},
toScriptEditor: () => {
throw new Error("Router called before initialization");
},
toSleeves: () => {
throw new Error("Router called before initialization");
},
toStockMarket: () => {
throw new Error("Router called before initialization");
},
toTerminal: () => {
throw new Error("Router called before initialization");
},
toTravel: () => {
throw new Error("Router called before initialization");
},
toTutorial: () => {
throw new Error("Router called before initialization");
},
toWork: () => {
throw new Error("Router called before initialization");
},
toBladeburnerCinematic: () => {
throw new Error("Router called before initialization");
},
toLocation: () => {
throw new Error("Router called before initialization");
},
toStaneksGift: () => {
throw new Error("Router called before initialization");
},
toAchievements: () => {
throw new Error("Router called before initialization");
},
toThemeBrowser: () => {
throw new Error("Router called before initialization");
},
toImportSave: () => {
throw new Error("Router called before initialization");
},
page: uninitialized,
allowRouting: uninitialized,
toActiveScripts: uninitialized,
toAugmentations: uninitialized,
toBitVerse: uninitialized,
toBladeburner: uninitialized,
toStats: uninitialized,
toCity: uninitialized,
toCorporation: uninitialized,
toCreateProgram: uninitialized,
toDevMenu: uninitialized,
toFaction: uninitialized,
toFactions: uninitialized,
toGameOptions: uninitialized,
toGang: uninitialized,
toHacknetNodes: uninitialized,
toInfiltration: uninitialized,
toJob: uninitialized,
toMilestones: uninitialized,
toResleeves: uninitialized,
toScriptEditor: uninitialized,
toSleeves: uninitialized,
toStockMarket: uninitialized,
toTerminal: uninitialized,
toTravel: uninitialized,
toTutorial: uninitialized,
toWork: uninitialized,
toBladeburnerCinematic: uninitialized,
toLocation: uninitialized,
toStaneksGift: uninitialized,
toAchievements: uninitialized,
toThemeBrowser: uninitialized,
toImportSave: uninitialized,
};
function determineStartPage(player: IPlayer): Page {
@ -346,12 +284,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
},
};
useEffect(() => {
// Wrap Router navigate functions to be able to disable the execution
_functions(Router).
filter((fnName) => fnName.startsWith('to')).
forEach((fnName) => {
_functions(Router)
.filter((fnName) => fnName.startsWith("to"))
.forEach((fnName) => {
// @ts-ignore - tslint does not like this, couldn't find a way to make it cooperate
Router[fnName] = _wrap(Router[fnName], (func, ...args) => {
if (!allowRoutingCalls) {
@ -360,7 +297,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
return;
}
// Call the function normally
// Call the function normally
return func(...args);
});
});
@ -568,13 +505,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
break;
}
case Page.ImportSave: {
mainPage = (
<ImportSaveRoot
importString={importString}
automatic={importAutomatic}
router={Router}
/>
);
mainPage = <ImportSaveRoot importString={importString} automatic={importAutomatic} router={Router} />;
withSidebar = false;
withPopups = false;
bypassGame = true;

View File

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

View File

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

View File

@ -94,9 +94,9 @@ export function v1APIBreak(): void {
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) {
txt += `${fl.file}:${fl.line}` + "\n";
txt += `${fl.file}:${fl.line}\n`;
}
}
}

View File

@ -59,7 +59,7 @@ async function main(version, versionNumber, changelog) {
join('\n').replaceAll('`', '\\`');
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);
console.log(`Modified ${appPaths.constants}`);